Re: [PAHOLE v4 2/3] dwarf_loader: Add support for DW_TAG_GNU_annotation
Alan Maguire <[email protected]> Sun, 7 Jun 2026 10:54:36 +0100
| Newsgroups | org.kernel.vger.dwarves,org.kernel.vger.bpf |
|---|---|
| Message-ID | <[email protected]> |
On 02/06/2026 20:55, Vineet Gupta wrote: > gcc 16 was the first release to support DW_TAG_GNU_annotations and this > patch enables the same in pahole. Bulk of changes are dwarf_loader but > btf_encoder also gains support with minimal changes. > > GCC encodes btf_type_tag and btf_decl_tag annotations differently from > LLVM. While LLVM uses DW_TAG_LLVM_annotation (0x6000) as child DIEs, > GCC uses DW_TAG_GNU_annotation (0x6001) as standalone sibling DIEs > referenced via DW_AT_GNU_annotation (0x2139) attributes, with chaining > through the same attribute on annotation DIEs themselves. > > Handle both encoding styles: > > For btf_type_tag (pointer annotations): > - Recognize DW_TAG_GNU_annotation alongside DW_TAG_LLVM_annotation in > child annotation scanning. > - Follow DW_AT_GNU_annotation attribute chains on pointer types for > GCC-style btf_type_tag resolution. > - Normalize DW_TAG_GNU_annotation to DW_TAG_LLVM_annotation in the > internal representation so downstream code works unchanged. > > For btf_decl_tag (function/struct/member annotations): > - Add add_gnu_annotation_chain() to follow DW_AT_GNU_annotation > attribute chains on function, struct, and member DIEs. > - GCC puts DW_AT_GNU_annotation on the function/struct DIE itself > (not as child DIEs), referencing sibling annotation DIEs that chain > via the same attribute. > > Also: > - Silently skip standalone DW_TAG_GNU_annotation DIEs at CU level. > - Add tag__is_annotation() helper macro for annotation tag checks. > - Rename add_llvm_annotation -> add_tag_annotation, > skip_llvm_annotations -> skip_tag_annotations since these now > handle both LLVM and GNU annotation formats. > > Signed-off-by: Vineet Gupta <[email protected]> Thanks for the respin! Sorry for the delay took me a while to get set up with latest gcc. Two things below, but in summary there are a few things we need to line things up between clang and gcc representations I think. To test these completely there are some existing btf_loader/pfunct issues below that I've outlined; I can send patches for those fixes as prerequisites, or feel free to roll into a respin, whatever you'd prefer. > --- > Changes since v3 [3] > - Add helper tag__is_annotation [Emil] > - Rename some functions containing "llvm" as they are common to LLVM/GCC tags [Emil] > - Deduplicate checks before loop and inside loop in add_gnu_annotation_chain() and check_gnu_attr [Arnaldo] > - Fix some typos and move some comments [Emi] > > Changes since v2 [2] > - Removed loop detection logic [Alan] > - Move test changes to different patch [Alan] > I don't want to introduce scope creep - and I may be missing something - but it seems like the current changes don't cover decl tags for function parameters. On this topic, there is an existing bug where decl tags that have a component_idx reference are not rendered correctly by pfunct which makes testing that properly impossible today. Parameter decl tags are prepended to the function declaration rather than tied to the parameter referenced by the component_idx, so we should fix that first. The root cause is btf_loader.c always attaches attributes to the function, whereas it should use the component_idx to attach them to the parameter tag where component_idx >= 0. The following changes are needed to fix that I think: diff --git a/btf_loader.c b/btf_loader.c index b591219..9bb52c3 100644 --- a/btf_loader.c +++ b/btf_loader.c @@ -477,12 +477,56 @@ static struct attributes *attributes__realloc(struct attributes *attributes, con return result; } +static struct tag *ftype__parameter(const struct ftype *ftype, int component_idx) +{ + struct parameter *pos; + int idx = 0; + + ftype__for_each_parameter(ftype, pos) { + if (idx == component_idx) + return &pos->tag; + ++idx; + } + + return NULL; +} + +static struct tag *function__parameter(const struct function *func, struct cu *cu, + int component_idx) +{ + struct tag *tag; + + if (component_idx < 0) + return NULL; + + tag = cu__type(cu, func->proto.tag.type); + if (tag == NULL) + return NULL; + + return ftype__parameter(tag__ftype(tag), component_idx); +} + static int process_decl_tag(struct cu *cu, const struct btf_type *tp) { + int component_idx = btf_decl_tag(tp)->component_idx; struct tag *tag = cu__type(cu, tp->type); struct attributes *tmp; - if (tag == NULL) + if (component_idx >= 0) { + struct tag *func_tag = cu__function(cu, tp->type); + + if (func_tag != NULL) { + tag = function__parameter(tag__function(func_tag), cu, + component_idx); + if (tag == NULL) { + fprintf(stderr, "WARNING: BTF_KIND_DECL_TAG for unknown parameter %d in BTF id %d\n", + component_idx, tp->type); + return 0; + } + } + } + + if (tag == NULL && component_idx < 0) tag = cu__function(cu, tp->type); if (tag == NULL) diff --git a/dwarves_fprintf.c b/dwarves_fprintf.c index 757e499..ab1c381 100644 --- a/dwarves_fprintf.c +++ b/dwarves_fprintf.c @@ -1203,6 +1203,18 @@ const char *function__prototype(const struct function *func, bf, len); } +static size_t tag__attributes_fprintf(const struct tag *tag, FILE *fp) +{ + size_t printed = 0; + int i; + + if (tag->attributes) + for (i = 0; i < tag->attributes->cnt; ++i) + printed += fprintf(fp, "%s ", tag->attributes->values[i]); + + return printed; +} + size_t ftype__fprintf_parms(const struct ftype *ftype, const struct cu *cu, int indent, const struct conf_fprintf *conf, FILE *fp) @@ -1244,6 +1256,7 @@ size_t ftype__fprintf_parms(const struct ftype *ftype, if (n) return printed + n; if (ptype->tag == DW_TAG_subroutine_type) { + printed += tag__attributes_fprintf(&pos->tag, fp); printed += ftype__fprintf(tag__ftype(ptype), cu, name, 0, 1, 0, @@ -1252,12 +1265,14 @@ size_t ftype__fprintf_parms(const struct ftype *ftype, } } } else if (type->tag == DW_TAG_subroutine_type) { + printed += tag__attributes_fprintf(&pos->tag, fp); printed += ftype__fprintf(tag__ftype(type), cu, name, true, 0, 0, 0, conf, fp); continue; } stype = tag__name(type, cu, sbf, sizeof(sbf), conf); print_it: + printed += tag__attributes_fprintf(&pos->tag, fp); printed += fprintf(fp, "%s%s%s", stype, name ? " " : "", name ?: ""); } @@ -1409,11 +1424,8 @@ static size_t function__fprintf(const struct tag *tag, const struct cu *cu, struct ftype *ftype = func->btf ? tag__ftype(cu__type(cu, func->proto.tag.type)) : &func->proto; size_t printed = 0; bool inlined = !conf->strip_inline && function__declared_inline(func); - int i; - if (tag->attributes) - for (i = 0; i < tag->attributes->cnt; ++i) - printed += fprintf(fp, "%s ", tag->attributes->values[i]); + printed += tag__attributes_fprintf(tag, fp); if (func->virtuality == DW_VIRTUALITY_virtual || func->virtuality == DW_VIRTUALITY_pure_virtual) Now with that fix in place, we can add a decl tag parameter case to the pfunct test: diff --git a/tests/pfunct-btf-decl-tags.sh b/tests/pfunct-btf-decl-tags.sh index a46aa1f..520148b 100755 --- a/tests/pfunct-btf-decl-tags.sh +++ b/tests/pfunct-btf-decl-tags.sh @@ -41,6 +41,7 @@ src=$(cat <<EOF __tag(a) __tag(b) __tag(c) void foo(void) {} __tag(a) __tag(b) void bar(void) {} __tag(a) void buz(void) {} +void qux(int __tag(param_a) arg) {} EOF ) @@ -48,13 +49,17 @@ EOF # tags order is not guaranteed sort_tags=$(cat <<EOF { -match(\$0,/^(.*) (void .*)/,tags_and_proto); -tags = tags_and_proto[1]; -proto = tags_and_proto[2]; -split(tags, tags_arr ,/ /); -asort(tags_arr); -for (t in tags_arr) printf "%s ", tags_arr[t]; -print proto; +delete tags_arr; +if (match(\$0,/^(.*) (void .*)/,tags_and_proto)) { + tags = tags_and_proto[1]; + proto = tags_and_proto[2]; + split(tags, tags_arr ,/ /); + asort(tags_arr); + for (t in tags_arr) printf "%s ", tags_arr[t]; + print proto; +} else { + print \$0; +} } EOF ) @@ -63,6 +68,7 @@ expected=$(cat <<EOF a b c void foo(void); a b void bar(void); a void buz(void); +void qux(param_a int arg); EOF ) @@ -98,7 +104,8 @@ fi if [ "$use_clang" -eq 1 ]; then tmpobj=$(make_tmpobj) - echo "$src" | $CLANG --target=bpf -c -g -x c -o $tmpobj - + echo "$src" | $CLANG -c -g -x c -o $tmpobj - + pahole -J $tmpobj 2>/dev/null run_test "$CLANG" "$tmpobj" || failed=1 fi Running this we see: ./pfunct-btf-decl-tags.sh Check that pfunct can print btf_decl_tags read from BTF. Testing with gcc (version 17) pfunct output does not match expected (gcc (version 17)): --- /dev/fd/63 2026-06-07 10:42:19.711753983 +0100 +++ /dev/fd/62 2026-06-07 10:42:19.711753983 +0100 @@ -1,4 +1,4 @@ a b c void foo(void); a b void bar(void); a void buz(void); -void qux(param_a int arg); +void qux(int arg); Complete output: a b c void foo(void); a b void bar(void); a void buz(void); void qux(int arg); Testing with clang passed Test ./pfunct-btf-decl-tags.sh failed Test data is in /tmp/pfunct-btf-decl-tags.sh.hXPG6r The following change to dwarf_loader.c fixes this: diff --git a/dwarf_loader.c b/dwarf_loader.c index c492b56..7f5b44d 100644 --- a/dwarf_loader.c +++ b/dwarf_loader.c @@ -1910,6 +1910,8 @@ static struct tag *die__create_new_parameter(Dwarf_Die *die, if (param_idx >= 0) { if (add_child_llvm_annotations(die, param_idx, conf, &(tag__function(&ftype->tag)->annots))) return NULL; + if (add_gnu_annotation_chain(die, param_idx, conf, &(tag__function(&ftype->tag)->annots))) + return NULL; } } else { /* > Changes since v1 [1] > - NFC Reduce indentation with early exits (Alexei offlist) > > [3] https://lore.kernel.org/bpf/[email protected]/ > [2] https://lore.kernel.org/bpf/[email protected]/ > [1] https://lore.kernel.org/bpf/[email protected]/ > --- > btf_encoder.c | 1 + > dutil.h | 11 +++++ > dwarf_loader.c | 105 +++++++++++++++++++++++++++++++++++++++------- > dwarves.h | 2 +- > dwarves_fprintf.c | 12 ++++-- > 5 files changed, 110 insertions(+), 21 deletions(-) > > diff --git a/btf_encoder.c b/btf_encoder.c > index 633bc6162ce0..d5af706d7638 100644 > --- a/btf_encoder.c > +++ b/btf_encoder.c > @@ -1831,6 +1831,7 @@ static int btf_encoder__encode_tag(struct btf_encoder *encoder, struct tag *tag, > name = namespace__name(tag__namespace(tag)); > return btf_encoder__add_ref_type(encoder, BTF_KIND_TYPEDEF, ref_type_id, name, false); > case DW_TAG_LLVM_annotation: > + case DW_TAG_GNU_annotation: > name = tag__btf_type_tag(tag)->value; > return btf_encoder__add_ref_type(encoder, BTF_KIND_TYPE_TAG, ref_type_id, name, false); > case DW_TAG_structure_type: > diff --git a/dutil.h b/dutil.h > index ff78aa6dfd10..be02c68cf4a7 100644 > --- a/dutil.h > +++ b/dutil.h > @@ -35,6 +35,17 @@ > #define DW_TAG_LLVM_annotation 0x6000 > #endif > > +#ifndef DW_TAG_GNU_annotation > +#define DW_TAG_GNU_annotation 0x6001 > +#endif > + > +#ifndef DW_AT_GNU_annotation > +#define DW_AT_GNU_annotation 0x2139 > +#endif > + > +#define tag__is_annotation(tag) \ > + ((tag) == DW_TAG_LLVM_annotation || (tag) == DW_TAG_GNU_annotation) > + > static inline __attribute__((const)) bool is_power_of_2(unsigned long n) > { > return (n != 0 && ((n & (n - 1)) == 0)); > diff --git a/dwarf_loader.c b/dwarf_loader.c > index 42a0f3f74ce3..14f71c9043aa 100644 > --- a/dwarf_loader.c > +++ b/dwarf_loader.c > @@ -908,7 +908,12 @@ static int tag__recode_dwarf_bitfield(struct tag *tag, struct cu *cu, uint16_t b > return -ENOMEM; > } > > -static int add_llvm_annotation(Dwarf_Die *die, int component_idx, struct conf_load *conf, > +static bool die__tag_is_annotation(Dwarf_Die *die) > +{ > + return tag__is_annotation(dwarf_tag(die)); > +} > + > +static int add_tag_annotation(Dwarf_Die *die, int component_idx, struct conf_load *conf, > struct list_head *head) > { > struct llvm_annotation *annot; > @@ -943,16 +948,40 @@ static int add_child_llvm_annotations(Dwarf_Die *die, int component_idx, > > die = &child; > do { > - if (dwarf_tag(die) == DW_TAG_LLVM_annotation) { > - ret = add_llvm_annotation(die, component_idx, conf, head); > - if (ret) > - return ret; > - } > + if (!die__tag_is_annotation(die)) > + continue; > + > + ret = add_tag_annotation(die, component_idx, conf, head); > + if (ret) > + return ret; > } while (dwarf_siblingof(die, die) == 0); > > return 0; > } > > +/* > + * Handle gcc style btf_decl_tag annotations for functions/struct/member tags. > + * Pointers are handled separately, inline in die__create_new_pointer_tag() > + */ > +static int add_gnu_annotation_chain(Dwarf_Die *die, int component_idx, > + struct conf_load *conf, struct list_head *head) > +{ > + Dwarf_Attribute attr; > + Dwarf_Die annot_die; > + > + while (dwarf_attr(die, DW_AT_GNU_annotation, &attr) != NULL && > + dwarf_formref_die(&attr, &annot_die) != NULL && > + dwarf_tag(&annot_die) == DW_TAG_GNU_annotation) { > + int ret = add_tag_annotation(&annot_die, component_idx, conf, head); > + if (ret) > + return ret; > + > + die = &annot_die; > + } > + > + return 0; > +} > + > int class_member__dwarf_recode_bitfield(struct class_member *member, > struct cu *cu) > { > @@ -1596,6 +1625,8 @@ static struct btf_type_tag_type *die__create_new_btf_type_tag_type(Dwarf_Die *di > return NULL; > > tag__init(&tag->tag, cu, die); > + /* Normalize DW_TAG_GNU_annotation to DW_TAG_LLVM_annotation internally */ > + tag->tag.tag = DW_TAG_LLVM_annotation; > tag->value = attr_string(die, DW_AT_const_value, conf); > return tag; > } > @@ -1637,19 +1668,21 @@ static struct tag *die__create_new_pointer_tag(Dwarf_Die *die, struct cu *cu, > { > struct btf_type_tag_ptr_type *tag = NULL; > Dwarf_Die *cdie, child; > + Dwarf_Attribute attr; > + Dwarf_Die annot_die; > const char *name; > > - /* If no child tags or skipping btf_type_tag encoding, just create a new tag > - * and return > - */ > - if (!dwarf_haschildren(die) || dwarf_child(die, &child) != 0 || > - conf->skip_encoding_btf_type_tag) > + /* If skipping btf_type_tag encoding, just create a new tag, return */ > + if (conf->skip_encoding_btf_type_tag) > return tag__new(die, cu); > > - /* Otherwise, check DW_TAG_LLVM_annotation child tags */ > + if (!dwarf_haschildren(die) || dwarf_child(die, &child) != 0) > + goto check_gnu_attr; > + > + /* Handle LLVM style annotation tags if present */ > cdie = &child; > do { > - if (dwarf_tag(cdie) != DW_TAG_LLVM_annotation) > + if (!die__tag_is_annotation(cdie)) > continue; > > /* Only check btf_type_tag annotations */ > @@ -1662,6 +1695,26 @@ static struct tag *die__create_new_pointer_tag(Dwarf_Die *die, struct cu *cu, > return NULL; > } while (dwarf_siblingof(cdie, cdie) == 0); > > +check_gnu_attr: > + if (tag != NULL) > + goto out; > + > + /* Handle GCC-style DW_AT_GNU_annotation attribute */ > + while (dwarf_attr(die, DW_AT_GNU_annotation, &attr) != NULL && > + dwarf_formref_die(&attr, &annot_die) != NULL && > + dwarf_tag(&annot_die) == DW_TAG_GNU_annotation) { > + name = attr_string(&annot_die, DW_AT_name, conf); > + if (strcmp(name, "btf_type_tag") != 0) > + break; > + > + tag = die__add_btf_type_tag(tag, die, &annot_die, cu, conf); I think there may be an issue here. die__add_btf_type_tag() will prepend discovered tags to give us the BTF representation we want. However a proof-of-concept test shows that gcc tested with latest master branch as of commit 0e634b961123280c17c1c651b1d8b1567b9b523c (HEAD -> master, origin/trunk, origin/master, origin/HEAD) Author: Marc Poulhiès <[email protected]> Date: Fri Mar 27 16:29:16 2026 +0100 ada: Fix bug when reading multibyte utf-8 character ...shows that the DWARF representation already has the BTF-desired order, so we wind up reversing it. Test below, but I _think_ something like this may be needed: diff --git a/dwarf_loader.c b/dwarf_loader.c index 14f71c9..c492b56 100644 --- a/dwarf_loader.c +++ b/dwarf_loader.c @@ -1633,7 +1633,8 @@ static struct btf_type_tag_type *die__create_new_btf_type_tag_type(Dwarf_Die *di static struct btf_type_tag_ptr_type *die__add_btf_type_tag(struct btf_type_tag_ptr_type *tag, Dwarf_Die *die, Dwarf_Die *adie, - struct cu *cu, struct conf_load *conf) + struct cu *cu, struct conf_load *conf, + bool prepend) { struct btf_type_tag_type *annot; uint32_t id; @@ -1656,10 +1657,38 @@ static struct btf_type_tag_ptr_type *die__add_btf_type_tag(struct btf_type_tag_p cu__hash(cu, &annot->tag); /* - * Prepends: for annotations tag1 -> tag2 -> tag3, - * the tag->tags list ends up as tag3 -> tag2 -> tag1. + * Consider the following set of type tags: + * + * #define __tag(x) __attribute__((btf_type_tag(#x))) + * + * struct sample { + * int value; + * }; + * + * struct sample __tag(outer) __tag(inner) *global_ptr; + * + * The BTF chain should describe the pointer as: + * + * PTR -> TYPE_TAG "inner" -> TYPE_TAG "outer" -> STRUCT "sample" + * + * The tag nearest *global_ptr applies to the pointer’s immediate pointee + * type first. So inner wraps outer struct sample. + * + * For clang DWARF, child annotation DIEs are seen as "outer, inner" + * so to get the right final order we need to prepend. + * + * For GCC DWARF, the explicit DW_AT_GNU_annotation chain starts at the outermost + * BTF wrapper: + * + * pointer DIE -> inner annotation -> outer annotation -> struct sample + * + * so in this case we preserve that traversal order and append. */ - list_add(&annot->node, &tag->tags); + if (prepend) + list_add(&annot->node, &tag->tags); + else + list_add_tail(&annot->node, &tag->tags); + return tag; } @@ -1690,7 +1719,7 @@ static struct tag *die__create_new_pointer_tag(Dwarf_Die *die, struct cu *cu, if (strcmp(name, "btf_type_tag") != 0) continue; - tag = die__add_btf_type_tag(tag, die, cdie, cu, conf); + tag = die__add_btf_type_tag(tag, die, cdie, cu, conf, true); if (tag == NULL) return NULL; } while (dwarf_siblingof(cdie, cdie) == 0); @@ -1707,7 +1736,7 @@ check_gnu_attr: if (strcmp(name, "btf_type_tag") != 0) break; - tag = die__add_btf_type_tag(tag, die, &annot_die, cu, conf); + tag = die__add_btf_type_tag(tag, die, &annot_die, cu, conf, false); if (tag == NULL) : Here's a test (feel free to adapt it if needed) that fails until the above is applied: #!/bin/bash # SPDX-License-Identifier: GPL-2.0-only # Check that pahole preserves btf_type_tag order when emitting BTF from DWARF. source test_lib.sh outdir=$(make_tmpdir) # Comment this out to save test data. trap cleanup EXIT title_log "Check BTF type tag order." GCC=${GCC:-gcc} CLANG=${CLANG:-clang} PAHOLE=${PAHOLE:-pahole} BPFTOOL=${BPFTOOL:-bpftool} if ! command -v "$BPFTOOL" > /dev/null; then info_log "skip: bpftool not available" test_skip fi compiler_has_btf_type_tag() { local compiler=$1 if ! command -v "$compiler" > /dev/null; then return 1 fi "$compiler" -x c -E -P - <<'EOF' 2>/dev/null | grep -qx 1 #ifndef __has_attribute #define __has_attribute(x) 0 #endif #if __has_attribute(btf_type_tag) 1 #else 0 #endif EOF } use_gcc=0 if compiler_has_btf_type_tag "$GCC"; then use_gcc=1 fi use_clang=0 if compiler_has_btf_type_tag "$CLANG"; then use_clang=1 fi if [ "$use_gcc" -eq 0 ] && [ "$use_clang" -eq 0 ]; then error_log "Need gcc or clang with btf_type_tag support for test $0" test_fail fi src=$(cat <<EOF #define __tag(x) __attribute__((btf_type_tag(#x))) struct sample { int value; }; struct sample __tag(outer) __tag(inner) *global_ptr; EOF ) check_type_tag_order() { local btf=$1 local dump if ! dump=$("$BPFTOOL" btf dump file "$btf"); then return 1 fi printf '%s\n' "$dump" | awk ' function parse_id(line, m) { if (match(line, /^\[([0-9]+)\]/, m)) return m[1] return 0 } function parse_name(line, m) { if (match(line, /\047([^\047]*)\047/, m)) return m[1] return "" } function parse_type(line, m) { if (match(line, /type_id=([0-9]+)/, m)) return m[1] return 0 } function check_ptr(ptr, id, tags, seen) { id = type[ptr] while (id != 0 && !seen[id]) { seen[id] = 1 if (kind[id] == "TYPE_TAG") { tags = tags (tags == "" ? "" : " -> ") name[id] id = type[id] continue } if (kind[id] == "STRUCT" && name[id] == "sample") { if (tags == "inner -> outer") exit 0 candidates = candidates (candidates == "" ? "" : ", ") tags } return } } /^\[[0-9]+\]/ { id = parse_id($0) kind[id] = $2 name[id] = parse_name($0) type[id] = parse_type($0) if (kind[id] == "PTR") ptrs[++nr_ptrs] = id } END { for (i = 1; i <= nr_ptrs; i++) check_ptr(ptrs[i]) if (candidates != "") { print "type tag order mismatch; expected inner -> outer, found " candidates > "/dev/stderr" exit 1 } print "could not find tagged pointer to struct sample" > "/dev/stderr" exit 1 }' } run_test() { local compiler=$1 local tmpobj=$2 local btf=$3 info_log "Testing with $compiler" if ! echo "$src" | "$compiler" -g -c -x c -o "$tmpobj" - 2>/dev/null; then error_log "Could not compile type tag order test with $compiler" return 1 fi if ! "$PAHOLE" --btf_features=+type_tag --btf_encode_detached="$btf" "$tmpobj" 2>/dev/null; then error_log "Could not encode BTF for $tmpobj" return 1 fi if check_type_tag_order "$btf"; then info_log " passed" return 0 fi error_log "BTF type tag order does not match expected order ($compiler)" return 1 } failed=0 if [ "$use_gcc" -eq 1 ]; then tmpobj=$(make_tmpobj) btf=${tmpobj%.o}.btf run_test "$GCC" "$tmpobj" "$btf" || failed=1 fi if [ "$use_clang" -eq 1 ]; then tmpobj=$(make_tmpobj) btf=${tmpobj%.o}.btf run_test "$CLANG" "$tmpobj" "$btf" || failed=1 fi if [ "$failed" -eq 0 ]; then test_pass else test_fail fi