btf_find_struct_member() traverses into nested anonymous structures and unions to find a struct member. However, get_bitoffset_of_field() in trace_probe.c checked btf_type_kflag(type) using the outer parent type instead of the actual anonymous structure/union that directly contains the found member. If the parent structure and anonymous structure have mismatched kflags (e.g., the parent has kflag=0 while the anonymous structure has kflag=1 because it contains bitfields), the bitfield size encoded in the upper 8 bits of member->offset is erroneously treated as part of the byte/bit offset, corrupting the resolved offset and failing to set last_bitsize. Similarly, btf_find_struct_member() pushed anonymous member offsets onto anon_stack without masking BTF_MEMBER_BIT_OFFSET() when kflag is set. To fix this problem, update btf_find_struct_member() to return actual containing structure/union type via member_type, use appropriate __btf_member_bit_offset() to get bit offset, and use member_type for btf_type_kflag() in get_bitoffset_of_field(). Link: https://lore.kernel.org/all/178827250904.123716.17452648791331881284.stgit@devnote2/
Unified · Split
| @@ -61,16 +61,17 @@ struct btf_anon_stack { · 60 unmodified lines | ||
| 61 | 61 | |
| 62 | 62 | /* |
| 63 | 63 | * Find a member of data structure/union by name and return it. |
| 64 | − * Return NULL if not found, or -EINVAL if parameter is invalid. | |
| 65 | − * If the member is an member of anonymous union/structure, the offset | |
| 66 | − * of that anonymous union/structure is stored into @anon_offset. Caller | |
| 67 | − * can calculate the correct offset from the root data structure by | |
| 68 | − * adding anon_offset to the member's offset. | |
| 64 | + * Return NULL if not found, or ERR_PTR(-EINVAL) if parameter is invalid. | |
| 65 | + * If the member is a member of an anonymous union/structure, the bit offset | |
| 66 | + * of that anonymous union/structure is stored into @anon_offset. | |
| 67 | + * If @member_type is non-NULL, the actual containing structure/union type | |
| 68 | + * of the found member is stored into @member_type. | |
| 69 | 69 | */ |
| 70 | 70 | const struct btf_member *btf_find_struct_member(struct btf *btf, |
| 71 | 71 | const struct btf_type *type, |
| 72 | 72 | const char *member_name, |
| 73 | − u32 *anon_offset) | |
| 73 | + u32 *anon_offset, | |
| 74 | + const struct btf_type **member_type) | |
| 74 | 75 | { |
| 75 | 76 | struct btf_anon_stack *anon_stack; |
| 76 | 77 | const struct btf_member *member; |
| @@ -94,14 +95,16 @@ retry: · 17 unmodified lines | ||
| 94 | 95 | if (mtype && btf_type_is_struct(mtype) && |
| 95 | 96 | top < BTF_ANON_STACK_MAX) { |
| 96 | 97 | anon_stack[top].tid = tid; |
| 97 | − anon_stack[top++].offset = | |
| 98 | − cur_offset + member->offset; | |
| 98 | + anon_stack[top++].offset = cur_offset + | |
| 99 | + __btf_member_bit_offset(type, member); | |
| 99 | 100 | } |
| 100 | 101 | } else { |
| 101 | 102 | name = btf_name_by_offset(btf, member->name_off); |
| 102 | 103 | if (name && !strcmp(member_name, name)) { |
| 103 | 104 | if (anon_offset) |
| 104 | 105 | *anon_offset = cur_offset; |
| 106 | + if (member_type) | |
| 107 | + *member_type = type; | |
| 105 | 108 | goto out; |
| 106 | 109 | } |
| 107 | 110 | } |
| @@ -8,4 +8,5 @@ const struct btf_param *btf_get_func_param(const struct btf_type *func_proto, · 7 unmodified lines | ||
| 8 | 8 | const struct btf_member *btf_find_struct_member(struct btf *btf, |
| 9 | 9 | const struct btf_type *type, |
| 10 | 10 | const char *member_name, |
| 11 | − u32 *anon_offset); | |
| 11 | + u32 *anon_offset, | |
| 12 | + const struct btf_type **member_type); | |
| @@ -625,6 +625,7 @@ static int get_bitoffset_of_field(char **pfieldname, const struct btf_type **pty · 624 unmodified lines | ||
| 625 | 625 | { |
| 626 | 626 | const struct btf_type *type = *ptype; |
| 627 | 627 | const struct btf_member *field; |
| 628 | + const struct btf_type *mtype; | |
| 628 | 629 | struct btf *btf = ctx_btf(ctx); |
| 629 | 630 | char *fieldname = *pfieldname; |
| 630 | 631 | int bitoffs = 0; |
| @@ -640,7 +641,7 @@ static int get_bitoffset_of_field(char **pfieldname, const struct btf_type **pty · 9 unmodified lines | ||
| 640 | 641 | |
| 641 | 642 | anon_offs = 0; |
| 642 | 643 | field = btf_find_struct_member(btf, type, fieldname, |
| 643 | − &anon_offs); | |
| 644 | + &anon_offs, &mtype); | |
| 644 | 645 | if (IS_ERR(field)) { |
| 645 | 646 | trace_probe_log_err(ctx->offset, BAD_BTF_TID); |
| 646 | 647 | return PTR_ERR(field); |
| @@ -653,7 +654,7 @@ static int get_bitoffset_of_field(char **pfieldname, const struct btf_type **pty · 6 unmodified lines | ||
| 653 | 654 | bitoffs += anon_offs; |
| 654 | 655 | |
| 655 | 656 | /* Accumulate the bit-offsets of the dot-connected fields */ |
| 656 | − if (btf_type_kflag(type)) { | |
| 657 | + if (btf_type_kflag(mtype)) { | |
| 657 | 658 | bitoffs += BTF_MEMBER_BIT_OFFSET(field->offset); |
| 658 | 659 | ctx->last_bitsize = BTF_MEMBER_BITFIELD_SIZE(field->offset); |
| 659 | 660 | } else { |