core: Use unnatural alignment of struct embedded in another to infer __packed__
Since we don't have something like DW_AT_alignment for
__attribute__((__packed__)), we need to use whatever hints that are
there in the alignments to figure out if a naturally packed struct has
the __attribute__((packed)) in the original sources, because that is
needed to waiver its natural alignment requisites.
For instance,
/* Used at: btrfs.c */
/* <1e7b> /home/acme/git/pahole/btrfs.c:199 */
struct btrfs_block_group_cache {
struct btrfs_key key; /* 0 17 */
struct btrfs_block_group_item item; /* 17 24 */
/* XXX 7 bytes hole, try to pack */
struct btrfs_fs_info * fs_info; /* 48 8 */
struct inode * inode; /* 56 8 */
In the original source code, btrfs_block_group_item is marked
__packed__, and being so, even seemingly unnecessarily, makes it, when
embedded in another struct, like the above, forfeit its natural
alingment, that would be 8 bytes, and instead appear right at the 17th
byte offset...
struct btrfs_block_group_item {
__le64 used; /* 0 8 */
__le64 chunk_objectid; /* 8 8 */
__le64 flags; /* 16 8 */
/* size: 24, cachelines: 1, members: 3 */
/* last cacheline: 24 bytes */
} __attribute__((__packed__));
So we need to, seeing its use at a unnatural offset, go backwards to the
btrfs_block_group_item pahole internal data structure, 'struct type' and
mark is_packed field as 'true', despite it not looking like a packed
struct.
Same thing with:
struct ieee80211_mcs_info {
u8 rx_mask[10]; /* 0 10 */
__le16 rx_highest; /* 10 2 */
u8 tx_params; /* 12 1 */
u8 reserved[3]; /* 13 3 */
/* size: 16, cachelines: 1, members: 4 */
/* last cacheline: 16 bytes */
};
That is naturally aligned and as 16 bytes, a power of two, then when it appears at the end of:
$ pahole -IC ieee80211_sta_ht_cap vht.o
/* Used at: vht.c */
/* <31ea> /home/acme/git/pahole/vht.c:1769 */
struct ieee80211_sta_ht_cap {
u16 cap; /* 0 2 */
bool ht_supported; /* 2 1 */
u8 ampdu_factor; /* 3 1 */
u8 ampdu_density; /* 4 1 */
/* XXX 1 byte hole, try to pack */
struct ieee80211_mcs_info mcs; /* 6 16 */
/* size: 22, cachelines: 1, members: 5 */
/* sum members: 21, holes: 1, sum holes: 1 */
/* last cacheline: 22 bytes */
};
$
We get that one byte hole if ieee80211_mcs_info isn't marked __packed__, as soon as we mark it:
$ pahole -IC ieee80211_sta_ht_cap vht.o
/* Used at: vht.c */
/* <31ea> /home/acme/git/pahole/vht.c:1769 */
struct ieee80211_sta_ht_cap {
u16 cap; /* 0 2 */
bool ht_supported; /* 2 1 */
u8 ampdu_factor; /* 3 1 */
u8 ampdu_density; /* 4 1 */
struct ieee80211_mcs_info mcs; /* 5 16 */
/* size: 22, cachelines: 1, members: 5 */
/* padding: 1 */
/* last cacheline: 22 bytes */
};
[acme@quaco pahole]$
It works, so __packed__ in this case just says: trow away the natural
alignment, make it 1 in whatever container structs.
So, before emitting the types for some struct, we go back looking at
each of its members and checking for such unnatural offsets, marking the
types as __packed__. Now:
$ pfunct --compile /home/acme/git/build/v5.1-rc4+/net/mac80211/vht.o | grep "^struct ieee80211_mcs_info" -A8
struct ieee80211_mcs_info {
u8 rx_mask[10]; /* 0 10 */
__le16 rx_highest; /* 10 2 */
u8 tx_params; /* 12 1 */
u8 reserved[3]; /* 13 3 */
/* size: 16, cachelines: 1, members: 4 */
/* last cacheline: 16 bytes */
} __attribute__((__packed__));
$
$ pfunct --compile /home/acme/git/build/v5.1-rc4+/fs/btrfs/free-space-tree.o | grep "^struct btrfs_block_group_item" -A7
struct btrfs_block_group_item {
__le64 used; /* 0 8 */
__le64 chunk_objectid; /* 8 8 */
__le64 flags; /* 16 8 */
/* size: 24, cachelines: 1, members: 3 */
/* last cacheline: 24 bytes */
} __attribute__((__packed__));
$
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
diff --git a/dwarves.c b/dwarves.c
index a9467f7..9542394 100644
--- a/dwarves.c
+++ b/dwarves.c
@@ -1346,6 +1346,34 @@
return type->natural_alignment;
}
+/*
+ * Sometimes the only indication that a struct is __packed__ is for it to
+ * appear embedded in another and at an offset that is not natural for it,
+ * so, in !__packed__ parked struct, check for that and mark the types of
+ * members at unnatural alignments.
+ */
+void type__check_structs_at_unnatural_alignments(struct type *type, const struct cu *cu)
+{
+ struct class_member *member;
+
+ type__for_each_member(type, member) {
+ struct tag *member_type = tag__strip_typedefs_and_modifiers(&member->tag, cu);
+
+ if (!tag__is_struct(member_type))
+ continue;
+
+ size_t natural_alignment = tag__natural_alignment(member_type, cu);
+
+ /* Would this break the natural alignment */
+ if ((member->byte_offset % natural_alignment) != 0) {
+ struct class *cls = tag__class(member_type);
+
+ cls->is_packed = true;
+ cls->type.packed_attributes_inferred = true;
+ }
+ }
+}
+
bool class__infer_packed_attributes(struct class *cls, const struct cu *cu)
{
struct type *ctype = &cls->type;
@@ -1361,6 +1389,7 @@
class__find_holes(cls);
if (cls->padding != 0 || cls->nr_holes != 0) {
+ type__check_structs_at_unnatural_alignments(ctype, cu);
cls->is_packed = false;
goto out;
}
diff --git a/dwarves.h b/dwarves.h
index ed83e69..1c6e3a3 100644
--- a/dwarves.h
+++ b/dwarves.h
@@ -1113,6 +1113,8 @@
void union__infer_packed_attributes(struct type *type, const struct cu *cu);
+void type__check_structs_at_unnatural_alignments(struct type *type, const struct cu *cu);
+
size_t class__fprintf(struct class *cls, const struct cu *cu, FILE *fp);
void class__add_vtable_entry(struct class *cls, struct function *vtable_entry);
diff --git a/dwarves_emit.c b/dwarves_emit.c
index 2f40b33..bd9b316 100644
--- a/dwarves_emit.c
+++ b/dwarves_emit.c
@@ -299,6 +299,8 @@
type_emissions__add_definition(emissions, ctype);
+ type__check_structs_at_unnatural_alignments(ctype, cu);
+
type__for_each_member(ctype, pos)
if (tag__emit_definitions(&pos->tag, cu, emissions, fp))
fputc('\n', fp);