gcc 16 was first release to support DW_TAG_GNU_annotation and this patch enables the same in pahole. Bulk of changes are in 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, with cycle detection. - 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. - Update pfunct-btf-decl-tags.sh test to use GCC 16+ when available and now passes. Signed-off-by: Vineet Gupta --- btf_encoder.c | 1 + dutil.h | 8 +++ dwarf_loader.c | 129 +++++++++++++++++++++++++++++----- dwarves.h | 3 +- dwarves_fprintf.c | 8 ++- tests/pfunct-btf-decl-tags.sh | 32 ++++++--- 6 files changed, 152 insertions(+), 29 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..abe0e62b412f 100644 --- a/dutil.h +++ b/dutil.h @@ -35,6 +35,14 @@ #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 + 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 365cc24e212d..b34fbed8b670 100644 --- a/dwarf_loader.c +++ b/dwarf_loader.c @@ -908,6 +908,12 @@ static int tag__recode_dwarf_bitfield(struct tag *tag, struct cu *cu, uint16_t b return -ENOMEM; } +static bool die__tag_is_annotation(Dwarf_Die *die) +{ + unsigned int tag = dwarf_tag(die); + return tag == DW_TAG_LLVM_annotation || tag == DW_TAG_GNU_annotation; +} + static int add_llvm_annotation(Dwarf_Die *die, int component_idx, struct conf_load *conf, struct list_head *head) { @@ -943,7 +949,7 @@ static int add_child_llvm_annotations(Dwarf_Die *die, int component_idx, die = &child; do { - if (dwarf_tag(die) == DW_TAG_LLVM_annotation) { + if (die__tag_is_annotation(die)) { ret = add_llvm_annotation(die, component_idx, conf, head); if (ret) return ret; @@ -953,6 +959,32 @@ static int add_child_llvm_annotations(Dwarf_Die *die, int component_idx, return 0; } +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; + + if (dwarf_attr(die, DW_AT_GNU_annotation, &attr) == NULL || + dwarf_formref_die(&attr, &annot_die) == NULL) + return 0; + + for (;;) { + if (dwarf_tag(&annot_die) != DW_TAG_GNU_annotation) + break; + + int ret = add_llvm_annotation(&annot_die, component_idx, conf, head); + if (ret) + return ret; + + if (dwarf_attr(&annot_die, DW_AT_GNU_annotation, &attr) == NULL || + dwarf_formref_die(&attr, &annot_die) == NULL) + break; + } + + return 0; +} + int class_member__dwarf_recode_bitfield(struct class_member *member, struct cu *cu) { @@ -1596,6 +1628,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; } @@ -1635,26 +1669,65 @@ static struct tag *die__create_new_pointer_tag(Dwarf_Die *die, struct cu *cu, Dwarf_Die *cdie, child; 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 (conf->skip_encoding_btf_type_tag) return tag__new(die, cu); - /* Otherwise, check DW_TAG_LLVM_annotation child tags */ - cdie = &child; - do { - if (dwarf_tag(cdie) != DW_TAG_LLVM_annotation) - continue; + /* Check for LLVM-style child annotation tags */ + if (dwarf_haschildren(die) && dwarf_child(die, &child) == 0) { + cdie = &child; + do { + if (!die__tag_is_annotation(cdie)) + continue; - name = attr_string(cdie, DW_AT_name, conf); - if (strcmp(name, "btf_type_tag") != 0) - continue; + name = attr_string(cdie, DW_AT_name, conf); + if (strcmp(name, "btf_type_tag") != 0) + continue; - if (die__add_btf_type_tag(&tag, die, cdie, cu, conf)) - return NULL; - } while (dwarf_siblingof(cdie, cdie) == 0); + if (die__add_btf_type_tag(&tag, die, cdie, cu, conf)) + return NULL; + } while (dwarf_siblingof(cdie, cdie) == 0); + } + + /* Check for GCC-style DW_AT_GNU_annotation attribute */ + if (tag == NULL) { + Dwarf_Attribute attr; + Dwarf_Die annot_die; + Dwarf_Off visited[256]; + int nr_visited = 0; + + if (dwarf_attr(die, DW_AT_GNU_annotation, &attr) != NULL && + dwarf_formref_die(&attr, &annot_die) != NULL) { + for (;;) { + Dwarf_Off off = dwarf_dieoffset(&annot_die); + bool cycle = false; + int i; + + for (i = 0; i < nr_visited; i++) { + if (visited[i] == off) { + cycle = true; + break; + } + } + if (cycle || nr_visited >= (int)ARRAY_SIZE(visited)) + break; + visited[nr_visited++] = off; + + if (dwarf_tag(&annot_die) != DW_TAG_GNU_annotation) + break; + + name = attr_string(&annot_die, DW_AT_name, conf); + if (strcmp(name, "btf_type_tag") != 0) + break; + + if (die__add_btf_type_tag(&tag, die, &annot_die, cu, conf)) + return NULL; + + if (dwarf_attr(&annot_die, DW_AT_GNU_annotation, &attr) == NULL || + dwarf_formref_die(&attr, &annot_die) == NULL) + break; + } + } + } return tag ? &tag->tag : tag__new(die, cu); } @@ -1684,6 +1757,12 @@ static struct tag *die__create_new_class(Dwarf_Die *die, struct cu *cu, struct c } } + if (class != NULL && + add_gnu_annotation_chain(die, -1, conf, &class->type.namespace.annots) != 0) { + class__delete(class, cu); + class = NULL; + } + return class ? &class->type.namespace.tag : NULL; } @@ -2045,10 +2124,13 @@ static int die__process_class(Dwarf_Die *die, struct type *class, cu__hash(cu, &member->tag); if (add_child_llvm_annotations(die, member_idx, conf, &class->namespace.annots)) return -ENOMEM; + if (add_gnu_annotation_chain(die, member_idx, conf, &class->namespace.annots)) + return -ENOMEM; member_idx++; } continue; case DW_TAG_LLVM_annotation: + case DW_TAG_GNU_annotation: if (add_llvm_annotation(die, -1, conf, &class->namespace.annots)) return -ENOMEM; continue; @@ -2354,6 +2436,7 @@ static int die__process_function(Dwarf_Die *die, struct ftype *ftype, goto out_enomem; continue; case DW_TAG_LLVM_annotation: + case DW_TAG_GNU_annotation: if (add_llvm_annotation(die, -1, conf, &(tag__function(&ftype->tag)->annots))) goto out_enomem; continue; @@ -2402,6 +2485,12 @@ static struct tag *die__create_new_function(Dwarf_Die *die, struct cu *cu, struc function = NULL; } + if (function != NULL && + add_gnu_annotation_chain(die, -1, conf, &function->annots) != 0) { + function__delete(function, cu); + function = NULL; + } + return function ? &function->proto.tag : NULL; } @@ -2463,6 +2552,9 @@ static struct tag *__die__process_tag(Dwarf_Die *die, struct cu *cu, */ tag = &unsupported_tag; break; + case DW_TAG_GNU_annotation: + tag = &unsupported_tag; + break; case DW_TAG_label: if (conf->ignore_labels) tag = &unsupported_tag; // callers will assume conf->ignore_labels is true @@ -2488,7 +2580,8 @@ static int die__process_unit(Dwarf_Die *die, struct cu *cu, struct conf_load *co // XXX special case DW_TAG_dwarf_procedure, appears when looking at a recent ~/bin/perf // Investigate later how to properly support this... if (dwarf_tag(die) != DW_TAG_dwarf_procedure && - dwarf_tag(die) != DW_TAG_label) // conf->ignore_labels == true, see die__process_tag() + dwarf_tag(die) != DW_TAG_label && // conf->ignore_labels == true, see die__process_tag() + dwarf_tag(die) != DW_TAG_GNU_annotation) tag__print_not_supported(die); continue; } diff --git a/dwarves.h b/dwarves.h index 5ec16e750e83..42b8e39aa2dd 100644 --- a/dwarves.h +++ b/dwarves.h @@ -670,7 +670,8 @@ static inline int tag__is_tag_type(const struct tag *tag) tag->tag == DW_TAG_volatile_type || tag->tag == DW_TAG_atomic_type || tag->tag == DW_TAG_unspecified_type || - tag->tag == DW_TAG_LLVM_annotation; + tag->tag == DW_TAG_LLVM_annotation || + tag->tag == DW_TAG_GNU_annotation; } static inline const char *tag__decl_file(const struct tag *tag, diff --git a/dwarves_fprintf.c b/dwarves_fprintf.c index 1ec478c2a027..a514d7e98923 100644 --- a/dwarves_fprintf.c +++ b/dwarves_fprintf.c @@ -140,6 +140,8 @@ const char *dwarf_tag_name(const uint32_t tag) return dwarf_gnu_tag_names[tag - DW_TAG_MIPS_loop]; else if (tag == DW_TAG_LLVM_annotation) return "LLVM_annotation"; + else if (tag == DW_TAG_GNU_annotation) + return "GNU_annotation"; return "INVALID"; } @@ -658,6 +660,7 @@ static const char *__tag__name(const struct tag *tag, const struct cu *cu, snprintf(bf, len, "%s", variable__name(tag__variable(tag))); break; case DW_TAG_LLVM_annotation: + case DW_TAG_GNU_annotation: type = cu__type(cu, tag->type); if (type == NULL && tag->type != 0) tag__id_not_found_snprintf(bf, len, tag->type); @@ -731,7 +734,7 @@ static type_id_t skip_llvm_annotations(const struct cu *cu, type_id_t id) if (id == 0) break; type = cu__type(cu, id); - if (type == NULL || type->tag != DW_TAG_LLVM_annotation || type->type == id) + if (type == NULL || (type->tag != DW_TAG_LLVM_annotation && type->tag != DW_TAG_GNU_annotation) || type->type == id) break; id = type->type; } @@ -936,7 +939,8 @@ print_modifier: { else printed += enumeration__fprintf(type, &tconf, fp); break; - case DW_TAG_LLVM_annotation: { + case DW_TAG_LLVM_annotation: + case DW_TAG_GNU_annotation: { struct tag *ttype = cu__type(cu, type->type); if (ttype) { type = ttype; diff --git a/tests/pfunct-btf-decl-tags.sh b/tests/pfunct-btf-decl-tags.sh index 35884b4e8687..5fdddc09f179 100755 --- a/tests/pfunct-btf-decl-tags.sh +++ b/tests/pfunct-btf-decl-tags.sh @@ -13,17 +13,21 @@ trap cleanup EXIT title_log "Check that pfunct can print btf_decl_tags read from BTF." -# gcc now also supports decl tags as of gcc commit 43dcea48b8c, -# in upstream version 16. -# UPTODO: add a check here for that. +# gcc 16+ supports decl tags via DW_TAG_GNU_annotation (gcc commit ac7027f180b). +# Use gcc if available and version >= 16, otherwise fall back to clang. +GCC=${GCC:-gcc} CLANG=${CLANG:-clang} -if ! command -v $CLANG > /dev/null; then - error_log "Need clang for test $0" - test_fail + +use_gcc=0 +if command -v $GCC > /dev/null; then + gcc_ver=$($GCC -dumpversion 2>/dev/null | cut -d. -f1) + if [ "$gcc_ver" -ge 16 ] 2>/dev/null; then + use_gcc=1 + fi fi -(cat </dev/null + pahole -J $tmpobj 2>/dev/null +elif command -v $CLANG > /dev/null; then + info_log "Using $CLANG for btf_decl_tag test" + echo "$src" | $CLANG --target=bpf -c -g -x c -o $tmpobj - +else + error_log "Need gcc >= 16 or clang for test $0" + test_fail +fi # tags order is not guaranteed sort_tags=$(cat <