* [PATCHES v3 0/7] Initial support for some Rust tags, DW_TAG_imported_unit
@ 2026-06-22 20:24 Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 01/16] dwarf_loader: Initial support for DW_TAG_variant_part Arnaldo Carvalho de Melo
` (15 more replies)
0 siblings, 16 replies; 17+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-22 20:24 UTC (permalink / raw)
To: Alan Maguire; +Cc: Jiri Olsa, Clark Williams, dwarves, Arnaldo Carvalho de Melo
Hi,
Here is a series with some initial support for some Rust DWARF
tags and support for DW_TAG_imported_unit in the same file, which makes
pahole support lots more modern userspace DWARF files, including DWARF
for Rust object files.
This makes one of the regression tests to pass again as perf now
has some rust source files and thus Rust CUs in a perf binary built with
DWARF, which is used to test pahole's pretty printing features, where it
uses the DWARF in a perf binary to decode perf.data records.
The --features=force_cu_merging, now is not strictly needed, but
remains as it may be useful at some point.
There is more work to be done to support references to a
separate file, that should come next.
- Arnaldo
Arnaldo Carvalho de Melo (16):
dwarf_loader: Initial support for DW_TAG_variant_part
dwarf_loader: Allow forcing the merge of CUs for solving inter CU tag references
dwarf_loader: Initial support for DW_TAG_subprogram in DW_TAG_enumeration
encoders: Fix diagnostic messages for unexpected tags in enumerations
dwarves_fprintf: Accumulate function__fprintf return value in enumeration printing
dwarves: Use tag__delete for enumeration children
btf_encoder: Fix types__match parameter comparison in BTF_KIND_FUNC_PROTO
encoders: Handle DW_TAG_subprogram in enumerations during BTF/CTF encoding
dwarf_loader: Populate DW_TAG_variant children in DW_TAG_variant_part
btf_encoder: Encode variant parts as union members in BTF
dwarf_loader: Handle DW_FORM_block in attr_numeric for Rust discriminant values
dwarf_loader: Support DW_TAG_imported_unit for same-file partial units
dwarf_loader: Fix cus__merging_cu failing to detect DW_FORM_ref_addr
tests: Add inter-CU type reference comparison test
tests: Guard cleanup() against empty outdir to prevent rm /*
tests: Source test_lib.sh via dirname so tests run from any directory
btf_encoder.c | 68 ++++++--
ctf_encoder.c | 23 ++-
dwarf_loader.c | 311 ++++++++++++++++++++++++++++++----
dwarves.c | 77 ++++++++-
dwarves.h | 43 ++++-
dwarves_emit.c | 10 +-
dwarves_fprintf.c | 54 ++++--
man-pages/pahole.1 | 17 +-
pahole.c | 20 ++-
tests/btf_functions.sh | 2 +-
tests/btf_type_tag_order.sh | 2 +-
tests/default_vmlinux_btf.sh | 2 +-
tests/flexible_arrays.sh | 2 +-
tests/gcc_true_signatures.sh | 2 +-
tests/inter_cu_refs.sh | 50 ++++++
tests/pfunct-btf-decl-tags.sh | 2 +-
tests/prettify_perf.data.sh | 6 +-
tests/reproducible_build.sh | 2 +-
tests/test_lib.sh | 6 +-
19 files changed, 604 insertions(+), 95 deletions(-)
create mode 100755 tests/inter_cu_refs.sh
--
2.54.0
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 01/16] dwarf_loader: Initial support for DW_TAG_variant_part
2026-06-22 20:24 [PATCHES v3 0/7] Initial support for some Rust tags, DW_TAG_imported_unit Arnaldo Carvalho de Melo
@ 2026-06-22 20:24 ` Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 02/16] dwarf_loader: Allow forcing the merge of CUs for solving inter CU tag references Arnaldo Carvalho de Melo
` (14 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-22 20:24 UTC (permalink / raw)
To: Alan Maguire; +Cc: Jiri Olsa, Clark Williams, dwarves, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Still doesn't handle its sub hierarchy, i.e. the DW_TAG_variant entries
and its underlying DW_TAG_member entries.
This was noticed when running the regression test that uses a debug
build of perf to process a perf.data file and test pahole's pretty
printing features, as now perf has a synthetic workload that is written
in rust:
<0><20c95a>: Abbrev Number: 1 (DW_TAG_compile_unit)
<20c95b> DW_AT_producer : (indirect string, offset: 0x4ee5a): clang LLVM (rustc version 1.93.1 (01f6ddf75 2026-02-11) (Fedora 1.93.1-1.fc43))
<20c95f> DW_AT_language : 28 (Rust)
<20c961> DW_AT_name : (indirect string, offset: 0x4eeaa): tests/workloads/code_with_type.rs/@/code_with_type.d6e680867bfb8b27-cgu.0
<20c965> DW_AT_stmt_list : 0x5e1ed
<20c969> DW_AT_comp_dir : (indirect string, offset: 0x487f1): /home/acme/git/perf-tools/tools/perf
<20c96d> DW_AT_low_pc : 0
<20c975> DW_AT_ranges : 0x2d0
⬢ [acme@toolbx pahole]$
So lets add some scaffolding for the Rust DWARF constructs involved for
us to be able to continue using perf with DWARF to test the pretty
printing features.
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
dwarf_loader.c | 26 +++++++++++++++++++++++++-
dwarves.c | 27 +++++++++++++++++++++++++++
dwarves.h | 16 ++++++++++++++++
3 files changed, 68 insertions(+), 1 deletion(-)
diff --git a/dwarf_loader.c b/dwarf_loader.c
index 8ce34cbb8ed60aca..438ebd52893fdb8f 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -508,6 +508,8 @@ static void tag__init(struct tag *tag, struct cu *cu, Dwarf_Die *die)
if (tag->tag == DW_TAG_imported_module || tag->tag == DW_TAG_imported_declaration)
dwarf_tag__set_attr_type(dtag, type, die, DW_AT_import);
+ else if (tag->tag == DW_TAG_variant_part)
+ dwarf_tag__set_attr_type(dtag, type, die, DW_AT_discr);
else
dwarf_tag__set_attr_type(dtag, type, die, DW_AT_type);
@@ -1207,6 +1209,18 @@ static struct template_parameter_pack *template_parameter_pack__new(Dwarf_Die *d
return pack;
}
+static struct variant_part *variant_part__new(Dwarf_Die *die, struct cu *cu, struct conf_load *conf)
+{
+ struct variant_part *vpart = tag__alloc(cu, sizeof(*vpart));
+
+ if (vpart != NULL) {
+ tag__init(&vpart->tag, cu, die);
+ INIT_LIST_HEAD(&vpart->variants);
+ }
+
+ return vpart;
+}
+
/* Returns number of locations found or negative value for errors. */
static ptrdiff_t __dwarf_getlocations(Dwarf_Attribute *attr,
ptrdiff_t offset, Dwarf_Addr *basep,
@@ -2085,9 +2099,19 @@ static int die__process_class(Dwarf_Die *die, struct type *class,
case DW_TAG_GNU_template_template_param:
#endif
case DW_TAG_subrange_type: // XXX: ADA stuff, its a type tho, will have other entries referencing it...
- case DW_TAG_variant_part: // XXX: Rust stuff
tag__print_not_supported(die);
continue;
+ case DW_TAG_variant_part: {
+ struct variant_part *vpart = variant_part__new(die, cu, conf);
+
+ if (vpart == NULL)
+ return -ENOMEM;
+
+ // For rust it seems we have just one, but DWARF, according to Gemini, support having
+ // more than one DW_TAG_variant_part for a given DW_TAG_structure_type, so future proof it
+ type__add_variant_part(class, vpart);
+ continue;
+ }
case DW_TAG_template_type_parameter: {
struct template_type_param *ttparm = template_type_param__new(die, cu, conf);
diff --git a/dwarves.c b/dwarves.c
index ef93239d26827711..07acf00aaea42607 100644
--- a/dwarves.c
+++ b/dwarves.c
@@ -428,6 +428,7 @@ void __type__init(struct type *type)
INIT_LIST_HEAD(&type->type_enum);
INIT_LIST_HEAD(&type->template_type_params);
INIT_LIST_HEAD(&type->template_value_params);
+ INIT_LIST_HEAD(&type->variant_parts);
type->template_parameter_pack = NULL;
type->sizeof_member = NULL;
type->member_prefix = NULL;
@@ -1288,12 +1289,31 @@ static void type__delete_class_members(struct type *type, struct cu *cu)
}
}
+static void variant_part__delete(struct variant_part *vpart, struct cu *cu)
+{
+ if (vpart == NULL)
+ return;
+
+ cu__tag_free(cu, &vpart->tag);
+}
+
+static void type__delete_variant_parts(struct type *type, struct cu *cu)
+{
+ struct variant_part *pos, *next;
+
+ type__for_each_variant_part_safe_reverse(type, pos, next) {
+ list_del_init(&pos->tag.node);
+ variant_part__delete(pos, cu);
+ }
+}
+
void class__delete(struct class *class, struct cu *cu)
{
if (class == NULL)
return;
type__delete_class_members(&class->type, cu);
+ type__delete_variant_parts(&class->type, cu);
cu__tag_free(cu, class__tag(class));
}
@@ -1303,6 +1323,7 @@ void type__delete(struct type *type, struct cu *cu)
return;
type__delete_class_members(type, cu);
+ type__delete_variant_parts(type, cu);
if (type->suffix_disambiguation)
zfree(&type->namespace.name);
@@ -1366,6 +1387,11 @@ void type__add_template_value_param(struct type *type, struct template_value_par
list_add_tail(&tvparam->tag.node, &type->template_value_params);
}
+void type__add_variant_part(struct type *type, struct variant_part *vpart)
+{
+ list_add_tail(&vpart->tag.node, &type->variant_parts);
+}
+
struct class_member *type__last_member(struct type *type)
{
struct class_member *pos;
@@ -1382,6 +1408,7 @@ static int type__clone_members(struct type *type, const struct type *from, struc
type->nr_members = type->nr_static_members = 0;
INIT_LIST_HEAD(&type->namespace.tags);
+ INIT_LIST_HEAD(&type->variant_parts);
type__for_each_member(from, pos) {
struct class_member *clone = class_member__clone(pos, cu);
diff --git a/dwarves.h b/dwarves.h
index 75c311a2f8ee4027..7fbda6cd11020572 100644
--- a/dwarves.h
+++ b/dwarves.h
@@ -1015,6 +1015,11 @@ static inline struct formal_parameter_pack *tag__formal_parameter_pack(const str
void formal_parameter_pack__add(struct formal_parameter_pack *pack, struct parameter *param);
+struct variant_part {
+ struct tag tag;
+ struct list_head variants;
+};
+
/*
* tag.tag can be DW_TAG_subprogram_type or DW_TAG_subroutine_type.
*/
@@ -1285,6 +1290,7 @@ struct type {
uint8_t is_signed_enum:1;
struct list_head template_type_params;
struct list_head template_value_params;
+ struct list_head variant_parts;
struct template_parameter_pack *template_parameter_pack;
};
@@ -1402,9 +1408,19 @@ static inline struct class_member *class_member__next(struct class_member *membe
#define type__for_each_tag_safe_reverse(type, pos, n) \
list_for_each_entry_safe_reverse(pos, n, &(type)->namespace.tags, tag.node)
+/**
+ * type__for_each_variant_part_safe_reverse - safely iterate thru all variant_parts in a type, in reverse order
+ * @type: struct type instance to iterate
+ * @pos: struct variant_part iterator
+ * @n: struct variant_part temp iterator
+ */
+#define type__for_each_variant_part_safe_reverse(type, pos, n) \
+ list_for_each_entry_safe_reverse(pos, n, &(type)->variant_parts, tag.node)
+
void type__add_member(struct type *type, struct class_member *member);
void type__add_template_type_param(struct type *type, struct template_type_param *ttparm);
void type__add_template_value_param(struct type *type, struct template_value_param *tvparam);
+void type__add_variant_part(struct type *type, struct variant_part *vpart);
struct class_member *
type__find_first_biggest_size_base_type_member(struct type *type,
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 02/16] dwarf_loader: Allow forcing the merge of CUs for solving inter CU tag references
2026-06-22 20:24 [PATCHES v3 0/7] Initial support for some Rust tags, DW_TAG_imported_unit Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 01/16] dwarf_loader: Initial support for DW_TAG_variant_part Arnaldo Carvalho de Melo
@ 2026-06-22 20:24 ` Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 03/16] dwarf_loader: Initial support for DW_TAG_subprogram in DW_TAG_enumeration Arnaldo Carvalho de Melo
` (13 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-22 20:24 UTC (permalink / raw)
To: Alan Maguire; +Cc: Jiri Olsa, Clark Williams, dwarves, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
The Linux perf tool now includes some Rust code that then gets linked
into perf and comes with its DWARF that has tags referencing tags in
different CUs, and as the current DWARF loading algorithm uses
parallelization and recodes the big DWARF types (DWARF_off, usually
64-bit) into smaller ones as a step into converting to CTF (initially)
and later BTF, the resolution fails.
There is a case where this inter CU happens, LTO builds, and so there is
an alternative algorithm for that case, that serializes DWARF CU loading
and merges all the CUs into just one meta/mega-CU, which then has all
the types and thus doesn't have a problem with inter CU references, as
the recoding into smaller ids is done only after all CUs are loaded.
So while we don't refactor the loading in a way that allows for inter CU
while allowing parallelization, maybe by doing the recoding just at the
end of parallel loading, add minimal code to force this CU merging for
experimentation in such cases, getting back the regression test
prettify_perf.data.sh to work, making it force CU merging.
$ pahole ~/bin/perf > unmerged.txt
<Suppress lots of warnings when recoding DWARF types.>
$ pahole --features=force_cu_merging ~/bin/perf > merged.txt
$
With the current set of Rust types that are representable with the
pahole data structures and then pretty printed as if they were C we see
12 differences:
$ diff -u unmerged.txt merged.txt | grep ^@@ | wc -l
12
$ diff -u unmerged.txt merged.txt | wc -l
198
Of this kind, due to some types not being resolved as tags are
referencing tags in other CUs.
$ diff -u unmerged.txt merged.txt | head
--- unmerged.txt 2026-03-23 17:56:54.971785023 -0300
+++ merged.txt 2026-03-23 17:56:59.826872178 -0300
@@ -9643,10 +9643,11 @@
u64 __0 __attribute__((__aligned__(8))); /* 0 8 */
struct Abbreviation __1 __attribute__((__aligned__(8))); /* 8 112 */
- /* XXX last struct has 5 bytes of padding */
+ /* XXX last struct has 16 bytes of padding, 1 hole */
/* size: 120, cachelines: 2, members: 2 */
$
Now the pretty printing perf.data test case passes:
⬢ [acme@toolbx tests]$ ./prettify_perf.data.sh
Pretty printing of files using DWARF type information.
Test ./prettify_perf.data.sh passed
⬢ [acme@toolbx tests]$
This was implemented reusing the --btf_features mechanism that now can
be accessed as well via --features, as this is not strictly a BTF
feature but, as Alan Maguire suggested, it is desirable to ask for that
feature to be enabled when we know it is needed bug can't guarantee that
the available pahole version has the feature and not have it fail
because it doesn't implement --force_cu_merging, which the
--btf_features=force_cu_merging, now also avaialbe as
--features=force_cu_merging, allows as it ignores unknown features.
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
dwarf_loader.c | 2 +-
dwarves.h | 1 +
man-pages/pahole.1 | 17 +++++++++++++++--
pahole.c | 11 ++++++++++-
tests/prettify_perf.data.sh | 4 ++--
5 files changed, 29 insertions(+), 6 deletions(-)
diff --git a/dwarf_loader.c b/dwarf_loader.c
index 438ebd52893fdb8f..a29caccead6d1eb1 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -4054,7 +4054,7 @@ static int cus__load_module(struct cus *cus, struct conf_load *conf,
}
}
- if (cus__merging_cu(dw, elf)) {
+ if (conf->force_cu_merging || cus__merging_cu(dw, elf)) {
res = cus__merge_and_process_cu(cus, conf, mod, dw, elf, filename,
build_id, build_id_len,
type_cu ? &type_dcu : NULL);
diff --git a/dwarves.h b/dwarves.h
index 7fbda6cd11020572..3b6f624716998fd7 100644
--- a/dwarves.h
+++ b/dwarves.h
@@ -112,6 +112,7 @@ struct conf_load {
const char *kabi_prefix;
struct btf *base_btf;
struct conf_fprintf *conf_fprintf;
+ bool force_cu_merging;
};
/** struct conf_fprintf - hints to the __fprintf routines
diff --git a/man-pages/pahole.1 b/man-pages/pahole.1
index b1be472de2b1e0c8..2fa82111c0f6305f 100644
--- a/man-pages/pahole.1
+++ b/man-pages/pahole.1
@@ -303,8 +303,9 @@ Generate BTF for functions with optimization-related suffixes (.isra, .constprop
Allow using all the BTF features supported by pahole.
.TP
-.B \-\-btf_features=FEATURE_LIST
-Encode BTF using the specified feature list, or specify 'default' for all standard features supported. This option can be used as an alternative to using multiple BTF-related options, and 'default' represents the standard set of BTF features that are in use for kernel BTF generation, so is useful as a shortcut for testing the latest set of standard features. However kernel builds will call out specific features rather than using 'default' to ensure that the desired features are enabled regardless of pahole version and associated 'default' set. Supported standard features are
+.B \-\-features=FEATURE_LIST
+This is also available as \-\-btf_features=FEATURE_LIST, unknown features, like those added in newer versions of pahole, are ignored.
+BTF encoding configuration is the major user. Using 'default' will enable all standard features supported. This option can be used as an alternative to using multiple BTF-related options, and 'default' represents the standard set of BTF features that are in use for kernel BTF generation, so is useful as a shortcut for testing the latest set of standard features. However kernel builds will call out specific features rather than using 'default' to ensure that the desired features are enabled regardless of pahole version and associated 'default' set. Supported standard features are
.nf
encode_force Ignore invalid symbols when encoding BTF; for example
@@ -344,6 +345,18 @@ Supported non-standard features (not enabled for 'default')
layout Encode information about BTF kinds available at encoding
time in layout section in BTF.
+Non-standard, non-BTF related features:
+
+ force_cu_merging Force merging all CUs into one. Use when there are
+ references across CUs. This happens in some LTO cases
+ and was observed with Rust CUs, where types tags
+ (function parameters, abstract origins for inlines, etc)
+ reference types in another CU.
+ For LTO this is being autodetected and the merging of
+ cus is done automatically, but for the Rust case, and
+ maybe others this is needed with the current DWARF
+ loading algorithm.
+
.fi
So for example, specifying \-\-btf_encode=var,enum64 will result in a BTF encoding that (as well as encoding basic BTF information) will contain variables and enum64 values.
diff --git a/pahole.c b/pahole.c
index 033baedcc602cc75..28d8f8832773520b 100644
--- a/pahole.c
+++ b/pahole.c
@@ -1153,6 +1153,7 @@ ARGP_PROGRAM_VERSION_HOOK_DEF = dwarves_print_version;
#define ARG_padding 348
#define ARGP_with_embedded_flexible_array 349
#define ARGP_btf_attributes 350
+#define ARGP_features 351
/* --btf_features=feature1[,feature2,..] allows us to specify
* a list of requested BTF features or "default" to enable all default
@@ -1240,7 +1241,8 @@ struct btf_feature {
BTF_NON_DEFAULT_FEATURE_CHECK(attributes, btf_attributes, false,
attributes_check),
BTF_NON_DEFAULT_FEATURE(true_signature, true_signature, false),
- BTF_NON_DEFAULT_FEATURE_CHECK(layout, btf_gen_layout, false, layout_check)
+ BTF_NON_DEFAULT_FEATURE_CHECK(layout, btf_gen_layout, false, layout_check),
+ BTF_NON_DEFAULT_FEATURE(force_cu_merging, force_cu_merging, false),
};
#define BTF_MAX_FEATURE_STR 1024
@@ -1798,6 +1800,12 @@ static const struct argp_option pahole__options[] = {
.arg = "FEATURE_LIST",
.doc = "Specify supported BTF features in FEATURE_LIST or 'default' for default set of supported features. See the pahole manual page for the list of supported, default features."
},
+ {
+ .name = "features",
+ .key = ARGP_features,
+ .arg = "FEATURE_LIST",
+ .doc = "Specify supported features in FEATURE_LIST or 'default' for default set of supported features. See the pahole manual page for the list of supported, default features."
+ },
{
.name = "supported_btf_features",
.key = ARGP_supported_btf_features,
@@ -2012,6 +2020,7 @@ static error_t pahole__options_parser(int key, char *arg,
conf_load.reproducible_build = true; break;
case ARGP_running_kernel_vmlinux:
show_running_kernel_vmlinux = true; break;
+ case ARGP_features:
case ARGP_btf_features:
parse_btf_features(arg, false); break;
case ARGP_supported_btf_features:
diff --git a/tests/prettify_perf.data.sh b/tests/prettify_perf.data.sh
index 1fae95154d710aae..384c250ff4e01a4c 100755
--- a/tests/prettify_perf.data.sh
+++ b/tests/prettify_perf.data.sh
@@ -25,7 +25,7 @@ fi
perf_lacks_type_info() {
local type_keyword=$1
local type_name=$2
- if ! pahole -C $type_name $perf | grep -q "^$type_keyword $type_name {"; then
+ if ! pahole --features=force_cu_merging -C $type_name $perf | grep -q "^$type_keyword $type_name {"; then
info_log "skip: $perf doesn't have '$type_keyword $type_name' type info"
test_skip
fi
@@ -41,7 +41,7 @@ $perf record --quiet -o $perf_data sleep 0.00001
number_of_filtered_perf_record_metadata() {
local metadata_record=$1
- local count=$(pahole -F dwarf -V $perf --header=perf_file_header --seek_bytes '$header.data.offset' --size_bytes='$header.data.size' -C "perf_event_header(sizeof,type,type_enum=perf_event_type+perf_user_event_type,filter=type==PERF_RECORD_$metadata_record)" --prettify $perf_data | grep ".type = PERF_RECORD_$metadata_record," | wc -l)
+ local count=$(pahole --features=force_cu_merging -F dwarf -V $perf --header=perf_file_header --seek_bytes '$header.data.offset' --size_bytes='$header.data.size' -C "perf_event_header(sizeof,type,type_enum=perf_event_type+perf_user_event_type,filter=type==PERF_RECORD_$metadata_record)" --prettify $perf_data | grep ".type = PERF_RECORD_$metadata_record," | wc -l)
echo "$count"
}
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 03/16] dwarf_loader: Initial support for DW_TAG_subprogram in DW_TAG_enumeration
2026-06-22 20:24 [PATCHES v3 0/7] Initial support for some Rust tags, DW_TAG_imported_unit Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 01/16] dwarf_loader: Initial support for DW_TAG_variant_part Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 02/16] dwarf_loader: Allow forcing the merge of CUs for solving inter CU tag references Arnaldo Carvalho de Melo
@ 2026-06-22 20:24 ` Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 04/16] encoders: Fix diagnostic messages for unexpected tags in enumerations Arnaldo Carvalho de Melo
` (12 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-22 20:24 UTC (permalink / raw)
To: Alan Maguire; +Cc: Jiri Olsa, Clark Williams, dwarves, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
In Rust enums can have subprograms, add initial support for it.
Example of a Rust enumeration with a DW_TAG_subprogram tag.
$ pahole -C ProgramKind /tmp/build/perf-tools-next/tests/workloads/code_with_type.a
enum ProgramKind {
PathLookup = 0,
Relative = 1,
Absolute = 2,
enum ProgramKind new(struct &std::ffi::os_str::OsStr),
} __attribute__((__packed__));
$
Reviewed-by: Alan Maguire <alan.maguire@oracle.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
btf_encoder.c | 13 ++++++++----
ctf_encoder.c | 16 +++++++++-----
dwarf_loader.c | 39 +++++++++++++++++++++++++++-------
dwarves.c | 3 +++
dwarves.h | 2 +-
dwarves_emit.c | 10 ++++-----
dwarves_fprintf.c | 54 +++++++++++++++++++++++++++++++++++------------
pahole.c | 9 +++++---
8 files changed, 106 insertions(+), 40 deletions(-)
diff --git a/btf_encoder.c b/btf_encoder.c
index d5af706d7638470d..b6814f6a92899d07 100644
--- a/btf_encoder.c
+++ b/btf_encoder.c
@@ -1786,7 +1786,7 @@ static uint32_t array_type__nelems(struct tag *tag)
}
static int32_t btf_encoder__add_enum_type(struct btf_encoder *encoder, struct tag *tag,
- struct conf_load *conf_load)
+ const struct cu *cu, struct conf_load *conf_load)
{
struct type *etype = tag__type(tag);
struct enumerator *pos;
@@ -1798,6 +1798,11 @@ static int32_t btf_encoder__add_enum_type(struct btf_encoder *encoder, struct ta
return type_id;
type__for_each_enumerator(etype, pos) {
+ if (pos->tag.tag != DW_TAG_enumerator) {
+ fprintf(stderr, "Unexpected DW_TAG_%s <%llx>, skipping it...\n",
+ dwarf_tag_name(tag->tag), tag__orig_id(tag, cu));
+ continue;
+ }
name = enumerator__name(pos);
if (btf_encoder__add_enum_val(encoder, name, pos->value, etype, conf_load))
return -1;
@@ -1807,7 +1812,7 @@ static int32_t btf_encoder__add_enum_type(struct btf_encoder *encoder, struct ta
}
static int btf_encoder__encode_tag(struct btf_encoder *encoder, struct tag *tag,
- struct conf_load *conf_load)
+ const struct cu *cu, struct conf_load *conf_load)
{
/* single out type 0 as it represents special type "void" */
uint32_t ref_type_id = tag->type == 0 ? 0 : encoder->type_id_off + tag->type;
@@ -1847,7 +1852,7 @@ static int btf_encoder__encode_tag(struct btf_encoder *encoder, struct tag *tag,
encoder->need_index_type = true;
return btf_encoder__add_array(encoder, ref_type_id, encoder->array_index_id, array_type__nelems(tag));
case DW_TAG_enumeration_type:
- return btf_encoder__add_enum_type(encoder, tag, conf_load);
+ return btf_encoder__add_enum_type(encoder, tag, cu, conf_load);
case DW_TAG_subroutine_type:
return btf_encoder__add_func_proto_for_ftype(encoder, tag__ftype(tag));
case DW_TAG_unspecified_type:
@@ -2906,7 +2911,7 @@ int btf_encoder__encode_cu(struct btf_encoder *encoder, struct cu *cu, struct co
}
cu__for_each_type(cu, core_id, pos) {
- btf_type_id = btf_encoder__encode_tag(encoder, pos, conf_load);
+ btf_type_id = btf_encoder__encode_tag(encoder, pos, cu, conf_load);
if (btf_type_id == 0) {
++skipped_types;
diff --git a/ctf_encoder.c b/ctf_encoder.c
index b761287d45348c59..1c61c76ba20e06bc 100644
--- a/ctf_encoder.c
+++ b/ctf_encoder.c
@@ -142,7 +142,7 @@ static int subroutine_type__encode(struct tag *tag, uint32_t core_id, struct ctf
return 0;
}
-static int enumeration_type__encode(struct tag *tag, uint32_t core_id, struct ctf *ctf)
+static int enumeration_type__encode(struct tag *tag, const struct cu *cu, uint32_t core_id, struct ctf *ctf)
{
struct type *etype = tag__type(tag);
int64_t position;
@@ -154,13 +154,19 @@ static int enumeration_type__encode(struct tag *tag, uint32_t core_id, struct ct
return -1;
struct enumerator *pos;
- type__for_each_enumerator(etype, pos)
+ type__for_each_enumerator(etype, pos) {
+ if (pos->tag.tag != DW_TAG_enumerator) {
+ fprintf(stderr, "Unexpected DW_TAG_%s <%llx>, skipping it...\n",
+ dwarf_tag_name(tag->tag), tag__orig_id(tag, cu));
+ continue;
+ }
ctf__add_enumerator(ctf, pos->name, pos->value, &position);
+ }
return 0;
}
-static void tag__encode_ctf(struct tag *tag, uint32_t core_id, struct ctf *ctf)
+static void tag__encode_ctf(struct tag *tag, const struct cu *cu, uint32_t core_id, struct ctf *ctf)
{
switch (tag->tag) {
case DW_TAG_base_type:
@@ -190,7 +196,7 @@ static void tag__encode_ctf(struct tag *tag, uint32_t core_id, struct ctf *ctf)
subroutine_type__encode(tag, core_id, ctf);
break;
case DW_TAG_enumeration_type:
- enumeration_type__encode(tag, core_id, ctf);
+ enumeration_type__encode(tag, cu, core_id, ctf);
break;
}
}
@@ -253,7 +259,7 @@ int cu__encode_ctf(struct cu *cu, int verbose)
uint32_t id;
struct tag *pos;
cu__for_each_type(cu, id, pos)
- tag__encode_ctf(pos, id, ctf);
+ tag__encode_ctf(pos, cu, id, ctf);
struct hlist_head hash_addr[HASHADDR__SIZE];
diff --git a/dwarf_loader.c b/dwarf_loader.c
index a29caccead6d1eb1..956532bbc5a35410 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -2038,6 +2038,8 @@ out_delete:
return NULL;
}
+static struct tag *die__create_new_function(Dwarf_Die *die, struct cu *cu, struct conf_load *conf);
+
static struct tag *die__create_new_enumeration(Dwarf_Die *die, struct cu *cu, struct conf_load *conf)
{
Dwarf_Die child;
@@ -2059,18 +2061,39 @@ static struct tag *die__create_new_enumeration(Dwarf_Die *die, struct cu *cu, st
die = &child;
do {
- struct enumerator *enumerator;
+ switch (dwarf_tag(die)) {
+ case DW_TAG_enumerator: {
+ struct enumerator *enumerator = enumerator__new(die, cu, conf);
+
+ if (enumerator == NULL)
+ goto out_delete;
+
+ enumeration__add(enumeration, enumerator);
+ cu__hash(cu, &enumerator->tag);
+ }
+ continue;
+ case DW_TAG_subprogram: {
+ struct tag *tag = die__create_new_function(die, cu, conf);
+ uint32_t id;
+
+ if (tag == NULL)
+ goto out_delete;
+
+ if (cu__table_add_tag(cu, tag, &id) < 0) {
+ tag__delete(tag, cu);
+ goto out_delete;
+ }
- if (dwarf_tag(die) != DW_TAG_enumerator) {
+ struct dwarf_tag *dtag = tag__dwarf(tag);
+ dtag->small_id = id;
+ namespace__add_tag(&enumeration->namespace, tag);
+ cu__hash(cu, tag);
+ break;
+ }
+ default:
cu__tag_not_handled(cu, die);
continue;
}
- enumerator = enumerator__new(die, cu, conf);
- if (enumerator == NULL)
- goto out_delete;
-
- enumeration__add(enumeration, enumerator);
- cu__hash(cu, &enumerator->tag);
} while (dwarf_siblingof(die, die) == 0);
out:
return &enumeration->namespace.tag;
diff --git a/dwarves.c b/dwarves.c
index 07acf00aaea42607..e530627073f7352e 100644
--- a/dwarves.c
+++ b/dwarves.c
@@ -2017,6 +2017,9 @@ static void enumeration__calc_prefix(struct type *enumeration)
struct enumerator *entry;
type__for_each_enumerator(enumeration, entry) {
+ if (entry->tag.tag != DW_TAG_enumerator)
+ continue;
+
const char *curr_name = enumerator__name(entry);
if (previous_name) {
diff --git a/dwarves.h b/dwarves.h
index 3b6f624716998fd7..638a469b11535061 100644
--- a/dwarves.h
+++ b/dwarves.h
@@ -1646,7 +1646,7 @@ static inline const char *enumerator__name(const struct enumerator *enumerator)
void enumeration__delete(struct type *type, struct cu *cu);
void enumeration__add(struct type *type, struct enumerator *enumerator);
-size_t enumeration__fprintf(const struct tag *tag_enum,
+size_t enumeration__fprintf(const struct tag *tag_enum, const struct cu *cu,
const struct conf_fprintf *conf, FILE *fp);
int dwarves__init(void);
diff --git a/dwarves_emit.c b/dwarves_emit.c
index 01b33b7ec41eb947..aaf0f8f9a7ea815a 100644
--- a/dwarves_emit.c
+++ b/dwarves_emit.c
@@ -100,7 +100,7 @@ static struct type *type_emissions__find_fwd_decl(const struct type_emissions *e
return NULL;
}
-static int enumeration__emit_definitions(struct tag *tag,
+static int enumeration__emit_definitions(struct tag *tag, const struct cu *cu,
struct type_emissions *emissions,
const struct conf_fprintf *conf,
FILE *fp)
@@ -121,7 +121,7 @@ static int enumeration__emit_definitions(struct tag *tag,
return 0;
}
- enumeration__fprintf(tag, conf, fp);
+ enumeration__fprintf(tag, cu, conf, fp);
fputs(";\n", fp);
// See comment on enumeration__fprintf(), it seems this happens with DWARF as well
@@ -198,10 +198,10 @@ static int typedef__emit_definitions(struct tag *tdef, struct cu *cu,
if (type__name(ctype) == NULL) {
fputs("typedef ", fp);
conf.suffix = type__name(def);
- enumeration__emit_definitions(type, emissions, &conf, fp);
+ enumeration__emit_definitions(type, cu, emissions, &conf, fp);
goto out;
} else
- enumeration__emit_definitions(type, emissions, &conf, fp);
+ enumeration__emit_definitions(type, cu, emissions, &conf, fp);
}
break;
case DW_TAG_structure_type:
@@ -380,7 +380,7 @@ next_indirection:
struct conf_fprintf conf = {
.suffix = NULL,
};
- return enumeration__emit_definitions(type, emissions, &conf, fp);
+ return enumeration__emit_definitions(type, cu, emissions, &conf, fp);
}
break;
case DW_TAG_structure_type:
diff --git a/dwarves_fprintf.c b/dwarves_fprintf.c
index ab1c381db64651c6..acbfbc091c9f86dc 100644
--- a/dwarves_fprintf.c
+++ b/dwarves_fprintf.c
@@ -155,6 +155,8 @@ const char tabs[] = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
static size_t union__fprintf(struct type *type, const struct cu *cu,
const struct conf_fprintf *conf, FILE *fp);
+static size_t function__fprintf(const struct tag *tag, const struct cu *cu,
+ const struct conf_fprintf *conf, FILE *fp);
/*
* In dwarves_emit.c we can call type__emit() using a locally setup conf_fprintf for which
@@ -406,7 +408,7 @@ next_type:
struct conf_fprintf tconf = *pconf;
tconf.suffix = type__name(type);
- return printed + enumeration__fprintf(tag_type, &tconf, fp);
+ return printed + enumeration__fprintf(tag_type, cu, &tconf, fp);
}
}
@@ -450,7 +452,15 @@ static int enumeration__max_entry_name_len(struct type *type)
struct enumerator *pos;
type__for_each_enumerator(type, pos) {
- int len = strlen(enumerator__name(pos));
+ int len = 0;
+
+ if (pos->tag.tag == DW_TAG_enumerator)
+ len = strlen(enumerator__name(pos));
+ else if (pos->tag.tag == DW_TAG_subprogram) {
+ const char *fname = function__name(tag__function(&pos->tag));
+ if (fname)
+ len = strlen(fname);
+ }
if (type->max_tag_name_len < len)
type->max_tag_name_len = len;
@@ -459,7 +469,8 @@ out:
return type->max_tag_name_len;
}
-size_t enumeration__fprintf(const struct tag *tag, const struct conf_fprintf *conf, FILE *fp)
+size_t enumeration__fprintf(const struct tag *tag, const struct cu *cu,
+ const struct conf_fprintf *conf, FILE *fp)
{
struct type *type = tag__type(tag);
struct enumerator *pos;
@@ -480,13 +491,25 @@ size_t enumeration__fprintf(const struct tag *tag, const struct conf_fprintf *co
}
type__for_each_enumerator(type, pos) {
- printed += fprintf(fp, "%.*s\t%-*s = ", indent, tabs,
- max_entry_name_len, enumerator__name(pos));
- if (conf->hex_fmt)
- printed += fprintf(fp, "%#llx", (unsigned long long)pos->value);
- else
- printed += fprintf(fp, type->is_signed_enum ? "%lld" : "%llu",
- (unsigned long long)pos->value);
+ printed += fprintf(fp, "%.*s\t", indent, tabs);
+
+ switch (pos->tag.tag) {
+ case DW_TAG_subprogram:
+ function__fprintf(&pos->tag, cu, conf, fp);
+ break;
+ case DW_TAG_enumerator:
+ printed += fprintf(fp, "%-*s = ", max_entry_name_len, enumerator__name(pos));
+ if (conf->hex_fmt)
+ printed += fprintf(fp, "%#llx", (unsigned long long)pos->value);
+ else
+ printed += fprintf(fp, type->is_signed_enum ? "%lld" : "%llu",
+ (unsigned long long)pos->value);
+ break;
+ default:
+ printed += fprintf(fp, "/* Unexpected %s <%llx> */\n", dwarf_tag_name(tag->tag),
+ tag__orig_id(tag, cu));
+ continue;
+ }
printed += fprintf(fp, ",\n");
}
@@ -586,9 +609,12 @@ static const char *__tag__name(const struct tag *tag, const struct cu *cu,
strncpy(bf, name, len);
}
break;
- case DW_TAG_subprogram:
- strncpy(bf, function__name(tag__function(tag)), len);
+ case DW_TAG_subprogram: {
+ const char *fname = function__name(tag__function(tag));
+ if (fname)
+ strncpy(bf, fname, len);
break;
+ }
case DW_TAG_pointer_type:
return tag__ptr_name(tag, cu, bf, len, "*", conf);
case DW_TAG_reference_type:
@@ -937,7 +963,7 @@ print_modifier: {
if (type__name(ctype) != NULL && !expand_types)
printed += fprintf(fp, "enum %-*s %s", tconf.type_spacing - 5, type__name(ctype), name ?: "");
else
- printed += enumeration__fprintf(type, &tconf, fp);
+ printed += enumeration__fprintf(type, cu, &tconf, fp);
break;
case DW_TAG_LLVM_annotation:
case DW_TAG_GNU_annotation: {
@@ -2174,7 +2200,7 @@ size_t tag__fprintf(struct tag *tag, const struct cu *cu,
printed += array_type__fprintf(tag, cu, "array", pconf, fp);
break;
case DW_TAG_enumeration_type:
- printed += enumeration__fprintf(tag, pconf, fp);
+ printed += enumeration__fprintf(tag, cu, pconf, fp);
break;
case DW_TAG_typedef:
printed += typedef__fprintf(tag, cu, pconf, fp);
diff --git a/pahole.c b/pahole.c
index 28d8f8832773520b..765da575f3dab39a 100644
--- a/pahole.c
+++ b/pahole.c
@@ -2155,7 +2155,7 @@ static const char *enumeration__lookup_value(struct type *enumeration, uint64_t
struct enumerator *entry;
type__for_each_enumerator(enumeration, entry) {
- if (entry->value == value)
+ if (entry->tag.tag == DW_TAG_enumerator && entry->value == value)
return enumerator__name(entry);
}
@@ -2180,7 +2180,7 @@ static struct enumerator *enumeration__lookup_entry_from_value(struct type *enum
struct enumerator *entry;
type__for_each_enumerator(enumeration, entry) {
- if (entry->value == value)
+ if (entry->tag.tag == DW_TAG_enumerator && entry->value == value)
return entry;
}
@@ -2206,6 +2206,9 @@ static struct enumerator *enumeration__find_enumerator(struct type *enumeration,
struct enumerator *entry;
type__for_each_enumerator(enumeration, entry) {
+ if (entry->tag.tag != DW_TAG_enumerator)
+ continue;
+
const char *entry_name = enumerator__name(entry);
if (!strcmp(entry_name, name))
@@ -3204,7 +3207,7 @@ static bool print_enumeration_with_enumerator(struct cu *cu, const char *name)
cu__for_each_enumeration(cu, id, enumeration) {
if (enumeration__find_enumerator(enumeration, name) != NULL) {
- enumeration__fprintf(type__tag(enumeration), &conf, stdout);
+ enumeration__fprintf(type__tag(enumeration), cu, &conf, stdout);
fputc('\n', stdout);
return true;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 04/16] encoders: Fix diagnostic messages for unexpected tags in enumerations
2026-06-22 20:24 [PATCHES v3 0/7] Initial support for some Rust tags, DW_TAG_imported_unit Arnaldo Carvalho de Melo
` (2 preceding siblings ...)
2026-06-22 20:24 ` [PATCH 03/16] dwarf_loader: Initial support for DW_TAG_subprogram in DW_TAG_enumeration Arnaldo Carvalho de Melo
@ 2026-06-22 20:24 ` Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 05/16] dwarves_fprintf: Accumulate function__fprintf return value in enumeration printing Arnaldo Carvalho de Melo
` (11 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-22 20:24 UTC (permalink / raw)
To: Alan Maguire; +Cc: Jiri Olsa, Clark Williams, dwarves, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Print the actual unexpected child tag's name and id instead of the
parent enumeration's, so the diagnostic message is useful for debugging.
Fixes: 5c0162ee40f06c95 ("dwarf_loader: Initial support for DW_TAG_subprogram in DW_TAG_enumeration")
Assisted-by: Claude:claude-opus-4-6-1m
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
btf_encoder.c | 2 +-
ctf_encoder.c | 2 +-
dwarves_fprintf.c | 4 ++--
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/btf_encoder.c b/btf_encoder.c
index b6814f6a92899d07..243064695f6cf171 100644
--- a/btf_encoder.c
+++ b/btf_encoder.c
@@ -1800,7 +1800,7 @@ static int32_t btf_encoder__add_enum_type(struct btf_encoder *encoder, struct ta
type__for_each_enumerator(etype, pos) {
if (pos->tag.tag != DW_TAG_enumerator) {
fprintf(stderr, "Unexpected DW_TAG_%s <%llx>, skipping it...\n",
- dwarf_tag_name(tag->tag), tag__orig_id(tag, cu));
+ dwarf_tag_name(pos->tag.tag), tag__orig_id(&pos->tag, cu));
continue;
}
name = enumerator__name(pos);
diff --git a/ctf_encoder.c b/ctf_encoder.c
index 1c61c76ba20e06bc..f2c63c2b039026f8 100644
--- a/ctf_encoder.c
+++ b/ctf_encoder.c
@@ -157,7 +157,7 @@ static int enumeration_type__encode(struct tag *tag, const struct cu *cu, uint32
type__for_each_enumerator(etype, pos) {
if (pos->tag.tag != DW_TAG_enumerator) {
fprintf(stderr, "Unexpected DW_TAG_%s <%llx>, skipping it...\n",
- dwarf_tag_name(tag->tag), tag__orig_id(tag, cu));
+ dwarf_tag_name(pos->tag.tag), tag__orig_id(&pos->tag, cu));
continue;
}
ctf__add_enumerator(ctf, pos->name, pos->value, &position);
diff --git a/dwarves_fprintf.c b/dwarves_fprintf.c
index acbfbc091c9f86dc..e0887e14daa16c8c 100644
--- a/dwarves_fprintf.c
+++ b/dwarves_fprintf.c
@@ -506,8 +506,8 @@ size_t enumeration__fprintf(const struct tag *tag, const struct cu *cu,
(unsigned long long)pos->value);
break;
default:
- printed += fprintf(fp, "/* Unexpected %s <%llx> */\n", dwarf_tag_name(tag->tag),
- tag__orig_id(tag, cu));
+ printed += fprintf(fp, "/* Unexpected %s <%llx> */\n", dwarf_tag_name(pos->tag.tag),
+ tag__orig_id(&pos->tag, cu));
continue;
}
printed += fprintf(fp, ",\n");
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 05/16] dwarves_fprintf: Accumulate function__fprintf return value in enumeration printing
2026-06-22 20:24 [PATCHES v3 0/7] Initial support for some Rust tags, DW_TAG_imported_unit Arnaldo Carvalho de Melo
` (3 preceding siblings ...)
2026-06-22 20:24 ` [PATCH 04/16] encoders: Fix diagnostic messages for unexpected tags in enumerations Arnaldo Carvalho de Melo
@ 2026-06-22 20:24 ` Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 06/16] dwarves: Use tag__delete for enumeration children Arnaldo Carvalho de Melo
` (10 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-22 20:24 UTC (permalink / raw)
To: Alan Maguire; +Cc: Jiri Olsa, Clark Williams, dwarves, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
The return value of function__fprintf was being discarded, causing the
printed byte count to be wrong when enumerations contain DW_TAG_subprogram.
Fixes: 5c0162ee40f06c95 ("dwarf_loader: Initial support for DW_TAG_subprogram in DW_TAG_enumeration")
Assisted-by: Claude:claude-opus-4-6-1m
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
dwarves_fprintf.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dwarves_fprintf.c b/dwarves_fprintf.c
index e0887e14daa16c8c..eeb79d490e02c5c3 100644
--- a/dwarves_fprintf.c
+++ b/dwarves_fprintf.c
@@ -495,7 +495,7 @@ size_t enumeration__fprintf(const struct tag *tag, const struct cu *cu,
switch (pos->tag.tag) {
case DW_TAG_subprogram:
- function__fprintf(&pos->tag, cu, conf, fp);
+ printed += function__fprintf(&pos->tag, cu, conf, fp);
break;
case DW_TAG_enumerator:
printed += fprintf(fp, "%-*s = ", max_entry_name_len, enumerator__name(pos));
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 06/16] dwarves: Use tag__delete for enumeration children
2026-06-22 20:24 [PATCHES v3 0/7] Initial support for some Rust tags, DW_TAG_imported_unit Arnaldo Carvalho de Melo
` (4 preceding siblings ...)
2026-06-22 20:24 ` [PATCH 05/16] dwarves_fprintf: Accumulate function__fprintf return value in enumeration printing Arnaldo Carvalho de Melo
@ 2026-06-22 20:24 ` Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 07/16] btf_encoder: Fix types__match parameter comparison in BTF_KIND_FUNC_PROTO Arnaldo Carvalho de Melo
` (9 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-22 20:24 UTC (permalink / raw)
To: Alan Maguire; +Cc: Jiri Olsa, Clark Williams, dwarves, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Now that enumeration children can be other tag types like
DW_TAG_subprogram, use the generic tag__delete() which handles all tag
types, removing the enumerator-specific wrapper.
Fixes: 5c0162ee40f06c95 ("dwarf_loader: Initial support for DW_TAG_subprogram in DW_TAG_enumeration")
Assisted-by: Claude:claude-opus-4-6-1m
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
dwarves.c | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/dwarves.c b/dwarves.c
index e530627073f7352e..5fae87fa5fe35a28 100644
--- a/dwarves.c
+++ b/dwarves.c
@@ -1334,11 +1334,6 @@ void type__delete(struct type *type, struct cu *cu)
cu__tag_free(cu, type__tag(type));
}
-static void enumerator__delete(struct enumerator *enumerator, struct cu *cu)
-{
- cu__tag_free(cu, &enumerator->tag);
-}
-
void enumeration__delete(struct type *type, struct cu *cu)
{
struct enumerator *pos, *n;
@@ -1348,7 +1343,7 @@ void enumeration__delete(struct type *type, struct cu *cu)
type__for_each_enumerator_safe_reverse(type, pos, n) {
list_del_init(&pos->tag.node);
- enumerator__delete(pos, cu);
+ tag__delete(&pos->tag, cu);
}
if (type->suffix_disambiguation)
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 07/16] btf_encoder: Fix types__match parameter comparison in BTF_KIND_FUNC_PROTO
2026-06-22 20:24 [PATCHES v3 0/7] Initial support for some Rust tags, DW_TAG_imported_unit Arnaldo Carvalho de Melo
` (5 preceding siblings ...)
2026-06-22 20:24 ` [PATCH 06/16] dwarves: Use tag__delete for enumeration children Arnaldo Carvalho de Melo
@ 2026-06-22 20:24 ` Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 08/16] encoders: Handle DW_TAG_subprogram in enumerations during BTF/CTF encoding Arnaldo Carvalho de Melo
` (8 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-22 20:24 UTC (permalink / raw)
To: Alan Maguire; +Cc: Jiri Olsa, Clark Williams, dwarves, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
The BTF_KIND_FUNC_PROTO loop in types__match() compared t1->type vs
t2->type (the return types) on every iteration instead of the individual
parameter types p1->type vs p2->type. This meant all function prototypes
with the same return type and parameter count were considered matching
regardless of their actual parameter types, causing gcc to warn that p1
and p2 were unused:
btf_encoder.c: In function 'types__match':
btf_encoder.c:1123:49: warning: variable 'p2' set but not used
btf_encoder.c:1122:49: warning: variable 'p1' set but not used
Fix the loop to compare each parameter type pair individually.
Reported-by: Sashiko:gemini-3-1-pro-preview # Running on a local machine
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
btf_encoder.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/btf_encoder.c b/btf_encoder.c
index 243064695f6cf171..82dd5f27138c2949 100644
--- a/btf_encoder.c
+++ b/btf_encoder.c
@@ -1129,8 +1129,8 @@ static bool types__match(struct btf_encoder *encoder,
btf2, t2->type))
return false;
for (i = 0; i < vlen; i++, p1++, p2++) {
- if (!types__match(encoder, btf1, t1->type,
- btf2, t2->type))
+ if (!types__match(encoder, btf1, p1->type,
+ btf2, p2->type))
return false;
}
return true;
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 08/16] encoders: Handle DW_TAG_subprogram in enumerations during BTF/CTF encoding
2026-06-22 20:24 [PATCHES v3 0/7] Initial support for some Rust tags, DW_TAG_imported_unit Arnaldo Carvalho de Melo
` (6 preceding siblings ...)
2026-06-22 20:24 ` [PATCH 07/16] btf_encoder: Fix types__match parameter comparison in BTF_KIND_FUNC_PROTO Arnaldo Carvalho de Melo
@ 2026-06-22 20:24 ` Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 09/16] dwarf_loader: Populate DW_TAG_variant children in DW_TAG_variant_part Arnaldo Carvalho de Melo
` (7 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-22 20:24 UTC (permalink / raw)
To: Alan Maguire; +Cc: Jiri Olsa, Clark Williams, dwarves, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Since commit 5c0162ee40f06c95 ("dwarf_loader: Initial support for
DW_TAG_subprogram in DW_TAG_enumeration"), Rust enumerations with
associated functions (impl blocks) are loaded with DW_TAG_subprogram
children in their namespace.
However, the BTF and CTF encoders were not aware of this and treated
these subprogram tags as unexpected, producing noisy warnings:
Unexpected DW_TAG_subprogram <0>, skipping it...
This was problematic for real Rust binaries. For instance, BTF encoding
the sashiko-cli Rust binary produced 653 such warnings from enumerations
like Ordering (137), ChunkedState (106), TlsState (20), and others.
Even the Rust standard library object produced 8 warnings.
Since BTF and CTF have no representation for subprograms inside
enumerations, we should skip them, but inform the user about it rather
than warn about an unexpected tag.
Switch the enumerator iteration in both encoders from an if/continue
pattern to a proper switch statement:
- DW_TAG_enumerator: processed normally
- DW_TAG_subprogram: silently skipped (BTF logs it in verbose mode)
- anything else: still warned about with the enumeration name for
better diagnostics
Before:
$ pahole --btf_encode sashiko-cli 2>&1 | grep -c subprogram
653
After:
$ pahole --btf_encode sashiko-cli 2>&1 | grep -c subprogram
0
$ pahole -V --btf_encode sashiko-cli 2>&1 | grep -c 'subprogram in enumeration'
653
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
btf_encoder.c | 22 +++++++++++++++-------
ctf_encoder.c | 15 ++++++++++-----
2 files changed, 25 insertions(+), 12 deletions(-)
diff --git a/btf_encoder.c b/btf_encoder.c
index 82dd5f27138c2949..dc1e18a986605d04 100644
--- a/btf_encoder.c
+++ b/btf_encoder.c
@@ -1798,14 +1798,22 @@ static int32_t btf_encoder__add_enum_type(struct btf_encoder *encoder, struct ta
return type_id;
type__for_each_enumerator(etype, pos) {
- if (pos->tag.tag != DW_TAG_enumerator) {
- fprintf(stderr, "Unexpected DW_TAG_%s <%llx>, skipping it...\n",
- dwarf_tag_name(pos->tag.tag), tag__orig_id(&pos->tag, cu));
- continue;
+ switch (pos->tag.tag) {
+ case DW_TAG_enumerator:
+ name = enumerator__name(pos);
+ if (btf_encoder__add_enum_val(encoder, name, pos->value, etype, conf_load))
+ return -1;
+ break;
+ case DW_TAG_subprogram:
+ if (encoder->verbose)
+ fprintf(stderr, "BTF: DW_TAG_subprogram in enumeration '%s' not supported, skipping\n",
+ type__name(etype) ?: "(anonymous)");
+ break;
+ default:
+ fprintf(stderr, "BTF: unexpected DW_TAG_%s in enumeration '%s', skipping\n",
+ dwarf_tag_name(pos->tag.tag), type__name(etype) ?: "(anonymous)");
+ break;
}
- name = enumerator__name(pos);
- if (btf_encoder__add_enum_val(encoder, name, pos->value, etype, conf_load))
- return -1;
}
return type_id;
diff --git a/ctf_encoder.c b/ctf_encoder.c
index f2c63c2b039026f8..f9e75a5821ce4e83 100644
--- a/ctf_encoder.c
+++ b/ctf_encoder.c
@@ -155,12 +155,17 @@ static int enumeration_type__encode(struct tag *tag, const struct cu *cu, uint32
struct enumerator *pos;
type__for_each_enumerator(etype, pos) {
- if (pos->tag.tag != DW_TAG_enumerator) {
- fprintf(stderr, "Unexpected DW_TAG_%s <%llx>, skipping it...\n",
- dwarf_tag_name(pos->tag.tag), tag__orig_id(&pos->tag, cu));
- continue;
+ switch (pos->tag.tag) {
+ case DW_TAG_enumerator:
+ ctf__add_enumerator(ctf, pos->name, pos->value, &position);
+ break;
+ case DW_TAG_subprogram:
+ break;
+ default:
+ fprintf(stderr, "CTF: unexpected DW_TAG_%s in enumeration '%s', skipping\n",
+ dwarf_tag_name(pos->tag.tag), type__name(etype) ?: "(anonymous)");
+ break;
}
- ctf__add_enumerator(ctf, pos->name, pos->value, &position);
}
return 0;
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 09/16] dwarf_loader: Populate DW_TAG_variant children in DW_TAG_variant_part
2026-06-22 20:24 [PATCHES v3 0/7] Initial support for some Rust tags, DW_TAG_imported_unit Arnaldo Carvalho de Melo
` (7 preceding siblings ...)
2026-06-22 20:24 ` [PATCH 08/16] encoders: Handle DW_TAG_subprogram in enumerations during BTF/CTF encoding Arnaldo Carvalho de Melo
@ 2026-06-22 20:24 ` Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 10/16] btf_encoder: Encode variant parts as union members in BTF Arnaldo Carvalho de Melo
` (6 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-22 20:24 UTC (permalink / raw)
To: Alan Maguire; +Cc: Jiri Olsa, Clark Williams, dwarves, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Rust discriminated unions (enums like Option<T> and Result<T,E>) are
represented in DWARF as:
DW_TAG_structure_type
DW_TAG_variant_part (DW_AT_discr -> discriminant member)
DW_TAG_variant (DW_AT_discr_value = 0)
DW_TAG_member "None" -> struct None
DW_TAG_variant (DW_AT_discr_value = 1)
DW_TAG_member "Some" -> struct Some
Commit 7b135647cbf22e0c ("dwarf_loader: Initial support for
DW_TAG_variant_part") added the variant_part container to pahole's
internal representation, but did not process its DW_TAG_variant children.
This meant the variant parts were always empty, and any Rust struct
backed by a variant_part appeared as a zero-member struct in the output.
Add a struct variant with name and discriminant value fields, a
variant__new() loader that extracts the DW_AT_discr_value and the child
DW_TAG_member's name and type reference, and wire it into
variant_part__new() so DW_TAG_variant children are populated at load
time.
Also add type recoding for variant type references in
namespace__recode_dwarf_types, so variant member types are resolved from
DWARF offsets to CU-local type indices, following the same pattern used
for regular struct/union members.
The variant_part__delete destructor is made non-static and extended to
clean up variant children, and helper functions and iterator macros are
added for the new types.
Before, with a Rust binary like sashiko-cli:
$ pahole -F btf -C 'Option<u32>' code_with_type.o
struct Option<u32> {
/* size: 8, cachelines: 1, members: 0 */
/* padding: 8 */
/* last cacheline: 8 bytes */
} __attribute__((__aligned__(16)));
After:
$ pahole -F dwarf -C 'Option<u32>' code_with_type.o
struct Option<u32> {
struct None { ... } __attribute__((__aligned__(4)));
struct Some {
u32 __0 __attribute__((__aligned__(4))); /* 4 4 */
} __attribute__((__aligned__(4)));
/* size: 8, cachelines: 1, members: 0 */
} __attribute__((__aligned__(4)));
The variant parts are now populated in pahole's internal representation
and available for downstream consumers (BTF/CTF encoders, pretty
printers).
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
dwarf_loader.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++
dwarves.c | 42 ++++++++++++++++++++++++++++-
dwarves.h | 24 +++++++++++++++++
3 files changed, 137 insertions(+), 1 deletion(-)
diff --git a/dwarf_loader.c b/dwarf_loader.c
index 956532bbc5a35410..fc88221ae339e7cd 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -1209,6 +1209,32 @@ static struct template_parameter_pack *template_parameter_pack__new(Dwarf_Die *d
return pack;
}
+static struct variant *variant__new(Dwarf_Die *die, struct cu *cu, struct conf_load *conf)
+{
+ struct variant *var = tag__alloc(cu, sizeof(*var));
+
+ if (var != NULL) {
+ tag__init(&var->tag, cu, die);
+ var->discr_value = attr_numeric(die, DW_AT_discr_value);
+ var->name = NULL;
+
+ Dwarf_Die child;
+ if (dwarf_child(die, &child) == 0) {
+ do {
+ if (dwarf_tag(&child) == DW_TAG_member) {
+ struct dwarf_tag *dtag = tag__dwarf(&var->tag);
+
+ var->name = attr_string(&child, DW_AT_name, conf);
+ dwarf_tag__set_attr_type(dtag, type, &child, DW_AT_type);
+ break;
+ }
+ } while (dwarf_siblingof(&child, &child) == 0);
+ }
+ }
+
+ return var;
+}
+
static struct variant_part *variant_part__new(Dwarf_Die *die, struct cu *cu, struct conf_load *conf)
{
struct variant_part *vpart = tag__alloc(cu, sizeof(*vpart));
@@ -1216,6 +1242,20 @@ static struct variant_part *variant_part__new(Dwarf_Die *die, struct cu *cu, str
if (vpart != NULL) {
tag__init(&vpart->tag, cu, die);
INIT_LIST_HEAD(&vpart->variants);
+
+ Dwarf_Die child;
+ if (dwarf_child(die, &child) == 0) {
+ do {
+ if (dwarf_tag(&child) == DW_TAG_variant) {
+ struct variant *var = variant__new(&child, cu, conf);
+ if (var == NULL) {
+ variant_part__delete(vpart, cu);
+ return NULL;
+ }
+ variant_part__add_variant(vpart, var);
+ }
+ } while (dwarf_siblingof(&child, &child) == 0);
+ }
}
return vpart;
@@ -2730,6 +2770,38 @@ check_type:
next:
pos->type = dtype->small_id;
}
+
+ if (tag__is_struct(tag) || tag__is_union(tag)) {
+ struct type *type = tag__type(tag);
+ struct variant_part *vpart;
+ struct dwarf_cu *dcu = cu->priv;
+
+ type__for_each_variant_part(type, vpart) {
+ struct variant *variant;
+ struct dwarf_tag *dvpart = tag__dwarf(&vpart->tag);
+
+ if (dvpart->type != 0) {
+ struct dwarf_tag *dtype = dwarf_cu__find_tag_by_ref(dcu, dvpart, type);
+ if (dtype != NULL)
+ vpart->tag.type = dtype->small_id;
+ }
+
+ variant_part__for_each_variant(vpart, variant) {
+ struct dwarf_tag *dvar = tag__dwarf(&variant->tag);
+
+ if (dvar->type == 0)
+ continue;
+
+ struct dwarf_tag *dtype = dwarf_cu__find_type_by_ref(dcu, dvar, type);
+ if (dtype == NULL) {
+ tag__print_type_not_found(&variant->tag);
+ continue;
+ }
+ variant->tag.type = dtype->small_id;
+ }
+ }
+ }
+
return 0;
}
diff --git a/dwarves.c b/dwarves.c
index 5fae87fa5fe35a28..04fe4a5ad62fffde 100644
--- a/dwarves.c
+++ b/dwarves.c
@@ -1289,11 +1289,18 @@ static void type__delete_class_members(struct type *type, struct cu *cu)
}
}
-static void variant_part__delete(struct variant_part *vpart, struct cu *cu)
+void variant_part__delete(struct variant_part *vpart, struct cu *cu)
{
+ struct variant *pos, *next;
+
if (vpart == NULL)
return;
+ list_for_each_entry_safe(pos, next, &vpart->variants, tag.node) {
+ list_del_init(&pos->tag.node);
+ cu__tag_free(cu, &pos->tag);
+ }
+
cu__tag_free(cu, &vpart->tag);
}
@@ -1387,6 +1394,11 @@ void type__add_variant_part(struct type *type, struct variant_part *vpart)
list_add_tail(&vpart->tag.node, &type->variant_parts);
}
+void variant_part__add_variant(struct variant_part *vpart, struct variant *var)
+{
+ list_add_tail(&var->tag.node, &vpart->variants);
+}
+
struct class_member *type__last_member(struct type *type)
{
struct class_member *pos;
@@ -1413,6 +1425,34 @@ static int type__clone_members(struct type *type, const struct type *from, struc
type__add_member(type, clone);
}
+ struct variant_part *vpart;
+
+ type__for_each_variant_part(from, vpart) {
+ struct variant_part *vp_clone = cu__tag_alloc(cu, sizeof(*vp_clone));
+
+ if (vp_clone == NULL)
+ return -1;
+
+ memcpy(vp_clone, vpart, sizeof(*vp_clone));
+ INIT_LIST_HEAD(&vp_clone->variants);
+
+ struct variant *variant;
+
+ variant_part__for_each_variant(vpart, variant) {
+ struct variant *v_clone = cu__tag_alloc(cu, sizeof(*v_clone));
+
+ if (v_clone == NULL) {
+ variant_part__delete(vp_clone, cu);
+ return -1;
+ }
+
+ memcpy(v_clone, variant, sizeof(*v_clone));
+ variant_part__add_variant(vp_clone, v_clone);
+ }
+
+ type__add_variant_part(type, vp_clone);
+ }
+
return 0;
}
diff --git a/dwarves.h b/dwarves.h
index 638a469b11535061..f2dac86b05ed6d2e 100644
--- a/dwarves.h
+++ b/dwarves.h
@@ -1016,6 +1016,12 @@ static inline struct formal_parameter_pack *tag__formal_parameter_pack(const str
void formal_parameter_pack__add(struct formal_parameter_pack *pack, struct parameter *param);
+struct variant {
+ struct tag tag;
+ const char *name;
+ uint64_t discr_value;
+};
+
struct variant_part {
struct tag tag;
struct list_head variants;
@@ -1418,10 +1424,28 @@ static inline struct class_member *class_member__next(struct class_member *membe
#define type__for_each_variant_part_safe_reverse(type, pos, n) \
list_for_each_entry_safe_reverse(pos, n, &(type)->variant_parts, tag.node)
+/**
+ * type__for_each_variant_part - iterate thru all variant_parts in a type
+ * @type: struct type instance to iterate
+ * @pos: struct variant_part iterator
+ */
+#define type__for_each_variant_part(type, pos) \
+ list_for_each_entry(pos, &(type)->variant_parts, tag.node)
+
void type__add_member(struct type *type, struct class_member *member);
void type__add_template_type_param(struct type *type, struct template_type_param *ttparm);
void type__add_template_value_param(struct type *type, struct template_value_param *tvparam);
void type__add_variant_part(struct type *type, struct variant_part *vpart);
+void variant_part__delete(struct variant_part *vpart, struct cu *cu);
+void variant_part__add_variant(struct variant_part *vpart, struct variant *var);
+
+/**
+ * variant_part__for_each_variant - iterate thru all variants in a variant_part
+ * @vpart: struct variant_part instance to iterate
+ * @pos: struct variant iterator
+ */
+#define variant_part__for_each_variant(vpart, pos) \
+ list_for_each_entry(pos, &(vpart)->variants, tag.node)
struct class_member *
type__find_first_biggest_size_base_type_member(struct type *type,
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 10/16] btf_encoder: Encode variant parts as union members in BTF
2026-06-22 20:24 [PATCHES v3 0/7] Initial support for some Rust tags, DW_TAG_imported_unit Arnaldo Carvalho de Melo
` (8 preceding siblings ...)
2026-06-22 20:24 ` [PATCH 09/16] dwarf_loader: Populate DW_TAG_variant children in DW_TAG_variant_part Arnaldo Carvalho de Melo
@ 2026-06-22 20:24 ` Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 11/16] dwarf_loader: Handle DW_FORM_block in attr_numeric for Rust discriminant values Arnaldo Carvalho de Melo
` (5 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-22 20:24 UTC (permalink / raw)
To: Alan Maguire; +Cc: Jiri Olsa, Clark Williams, dwarves, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
With the DWARF loader now populating DW_TAG_variant children (previous
commit), wire them into BTF encoding so Rust discriminated unions
(Option<T>, Result<T,E>, etc.) are no longer emitted as empty structs.
Two changes:
1. Struct-to-union promotion: when a DW_TAG_structure_type has
variant_parts but no regular data members, encode it as BTF_KIND_UNION
instead of BTF_KIND_STRUCT, since the variants overlap at offset 0.
2. Variant member encoding: after encoding regular data members, iterate
the variant_parts and emit each variant as a BTF union field with the
variant's name and resolved type reference.
Testing with the sashiko-cli Rust binary (a real-world async HTTP client
using tokio, hyper, serde, etc.):
Before:
$ bpftool btf dump file sashiko-cli | grep -c UNION
2073
$ bpftool btf dump file sashiko-cli | grep 'STRUCT.*vlen=0' | grep -vc 'size=0'
24335
After:
$ bpftool btf dump file sashiko-cli | grep -c UNION
25750
$ bpftool btf dump file sashiko-cli | grep 'STRUCT.*vlen=0' | grep -vc 'size=0'
2236
22,099 types that were previously encoded as empty structs are now
properly represented as unions with their variant members:
Before:
$ bpftool btf dump file code_with_type.o | grep -A1 'Option<u32>'
[11] STRUCT 'Option<u32>' size=8 vlen=0
After:
$ bpftool btf dump file code_with_type.o | grep -A3 'Option<u32>'
[11] UNION 'Option<u32>' size=8 vlen=2
'None' type_id=9 bits_offset=0
'Some' type_id=10 bits_offset=0
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
btf_encoder.c | 37 +++++++++++++++++++++++++++++++++++--
1 file changed, 35 insertions(+), 2 deletions(-)
diff --git a/btf_encoder.c b/btf_encoder.c
index dc1e18a986605d04..5a510afbde7afa84 100644
--- a/btf_encoder.c
+++ b/btf_encoder.c
@@ -1743,6 +1743,11 @@ static int tag__check_id_drift(struct btf_encoder *encoder, const struct tag *ta
return 0;
}
+static bool type__has_variant_parts(const struct type *type)
+{
+ return !list_empty(&type->variant_parts);
+}
+
static int32_t btf_encoder__add_struct_type(struct btf_encoder *encoder, struct tag *tag)
{
struct type *type = tag__type(tag);
@@ -1751,8 +1756,18 @@ static int32_t btf_encoder__add_struct_type(struct btf_encoder *encoder, struct
int32_t type_id;
uint8_t kind;
- kind = (tag->tag == DW_TAG_union_type) ?
- BTF_KIND_UNION : BTF_KIND_STRUCT;
+ /*
+ * Rust discriminated unions (enums) are represented in DWARF as
+ * DW_TAG_structure_type with DW_TAG_variant_part children.
+ * If the struct has only variant parts and no regular data members,
+ * encode it as a BTF union since the variants overlap at offset 0.
+ */
+ if (tag->tag == DW_TAG_union_type)
+ kind = BTF_KIND_UNION;
+ else if (type__has_variant_parts(type) && type->nr_members == 0)
+ kind = BTF_KIND_UNION;
+ else
+ kind = BTF_KIND_STRUCT;
type_id = btf_encoder__add_struct(encoder, kind, name, type->size);
if (type_id < 0)
@@ -1770,6 +1785,24 @@ static int32_t btf_encoder__add_struct_type(struct btf_encoder *encoder, struct
return -1;
}
+ if (type__has_variant_parts(type) && kind == BTF_KIND_UNION) {
+ struct variant_part *vpart;
+
+ type__for_each_variant_part(type, vpart) {
+ struct variant *variant;
+
+ variant_part__for_each_variant(vpart, variant) {
+ if (variant->tag.type == 0)
+ continue;
+
+ uint32_t ref_type_id = encoder->type_id_off + variant->tag.type;
+
+ if (btf_encoder__add_field(encoder, variant->name, ref_type_id, 0, 0))
+ return -1;
+ }
+ }
+ }
+
return type_id;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 11/16] dwarf_loader: Handle DW_FORM_block in attr_numeric for Rust discriminant values
2026-06-22 20:24 [PATCHES v3 0/7] Initial support for some Rust tags, DW_TAG_imported_unit Arnaldo Carvalho de Melo
` (9 preceding siblings ...)
2026-06-22 20:24 ` [PATCH 10/16] btf_encoder: Encode variant parts as union members in BTF Arnaldo Carvalho de Melo
@ 2026-06-22 20:24 ` Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 12/16] dwarf_loader: Support DW_TAG_imported_unit for same-file partial units Arnaldo Carvalho de Melo
` (4 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-22 20:24 UTC (permalink / raw)
To: Alan Maguire; +Cc: Jiri Olsa, Clark Williams, dwarves, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Rust enums with 128-bit discriminant types (i128/u128) encode their
DW_AT_discr_value as a DW_FORM_block1 containing a 16-byte little-endian
integer, rather than using a standard data form like DW_FORM_data*.
Since attr_numeric did not handle DW_FORM_block*, these discriminant
values were not read and produced 311 warnings on a real Rust binary like
sashiko-cli:
DW_AT_<0x16>=0xa
DW_AT_<0x16>=0xa
...
(0x16 = DW_AT_discr_value, 0xa = DW_FORM_block1)
Add DW_FORM_block1/block2/block4/block to attr_numeric, reading up to 8
bytes from the block into a uint64_t via memcpy and converting from
little-endian with le64toh() so the result is correct on big-endian hosts
as well. This covers all practical discriminant values.
Before (sashiko-cli):
$ pahole --btf_encode sashiko-cli 2>&1 | grep -c 'DW_AT_'
311
After:
$ pahole --btf_encode sashiko-cli 2>&1 | wc -l
0
All warnings from Rust DWARF encoding of sashiko-cli are now resolved.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
dwarf_loader.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/dwarf_loader.c b/dwarf_loader.c
index fc88221ae339e7cd..9f7d2bd23359191b 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -7,6 +7,7 @@
#include <assert.h>
#include <dirent.h>
#include <dwarf.h>
+#include <endian.h>
#include <elfutils/libdwfl.h>
#include <elfutils/version.h>
#include <errno.h>
@@ -353,6 +354,19 @@ static uint64_t attr_numeric(Dwarf_Die *die, uint32_t name)
return value;
}
break;
+ case DW_FORM_block1:
+ case DW_FORM_block2:
+ case DW_FORM_block4:
+ case DW_FORM_block: {
+ Dwarf_Block block;
+ if (dwarf_formblock(&attr, &block) == 0 && block.length > 0) {
+ uint64_t value = 0;
+ size_t n = block.length > sizeof(value) ? sizeof(value) : block.length;
+ memcpy(&value, block.data, n);
+ return le64toh(value);
+ }
+ }
+ break;
default:
fprintf(stderr, "DW_AT_<0x%x>=0x%x\n", name, form);
break;
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 12/16] dwarf_loader: Support DW_TAG_imported_unit for same-file partial units
2026-06-22 20:24 [PATCHES v3 0/7] Initial support for some Rust tags, DW_TAG_imported_unit Arnaldo Carvalho de Melo
` (10 preceding siblings ...)
2026-06-22 20:24 ` [PATCH 11/16] dwarf_loader: Handle DW_FORM_block in attr_numeric for Rust discriminant values Arnaldo Carvalho de Melo
@ 2026-06-22 20:24 ` Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 13/16] dwarf_loader: Fix cus__merging_cu failing to detect DW_FORM_ref_addr Arnaldo Carvalho de Melo
` (3 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-22 20:24 UTC (permalink / raw)
To: Alan Maguire; +Cc: Jiri Olsa, Clark Williams, dwarves, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Binaries processed by the dwz(1) tool have their DWARF type information
deduplicated into DW_TAG_partial_unit entries that are then referenced
via DW_TAG_imported_unit from each DW_TAG_compile_unit that uses those
types. This is the standard DWARF mechanism for cross-CU type sharing.
On Fedora/RHEL, most debuginfo packages are built with dwz, making this
a common pattern. For instance, bash-debuginfo has 10,486
DW_TAG_partial_unit, 384 DW_TAG_compile_unit, and 8,572
DW_TAG_imported_unit entries — all using same-file references (no .dwz
alternate DWARF file involved).
Before this patch, pahole skipped DW_TAG_partial_unit with a warning:
WARNING: DW_TAG_partial_unit used, some types will not be considered!
Probably this was optimized using a tool like 'dwz'
A future version of pahole will support this.
And DW_TAG_imported_unit was silently ignored (returned NULL), causing
pahole to report "file has no dwarf type information" for binaries like
bash and glibc.
The fix adds die__process_imported_unit(), called from die__process_unit()
when encountering DW_TAG_imported_unit. It follows DW_AT_import to the
referenced DW_TAG_partial_unit DIE and processes its children inline into
the importing compile unit's type tables. This works because
dwarf_formref_die() already handles all DWARF reference forms, and each
CU maintains its own independent hash tables — so the same partial unit
can be safely imported by multiple CUs, each getting its own copy of the
types.
Since imported units can themselves contain DW_TAG_imported_unit entries
(nested imports), a depth limit of 64 is enforced to prevent stack
overflow from pathological or corrupted DWARF. A warning is emitted if
the limit is reached.
Some binaries (e.g. chromium-browser on Fedora 44, built with Rust
components) also have DW_TAG_imported_unit entries that reference partial
units in an alternate debug file via DW_FORM_GNU_ref_alt (the
.gnu_debugaltlink mechanism). When elfutils resolves such a reference, it
returns DIEs from the alternate file whose offsets are in a different
address space — processing these into the main CU's hash tables corrupts
type references and causes a crash during type recoding.
The same DW_FORM_GNU_ref_alt form can also appear on regular type
attributes (DW_AT_type, DW_AT_abstract_origin, DW_AT_specification,
etc.), not just on DW_TAG_imported_unit's DW_AT_import. Guard all paths
via attr_form_is_ref_alt(), which skips the reference and warns once, so
users know why some types are missing rather than getting a crash.
The korg/alt_dwarf branch had a previous attempt at this that also
handled the .dwz alternate DWARF file case (DW_FORM_GNU_ref_alt), but it
was never merged and is now 294 commits behind master. This patch takes a
simpler approach focused on the same-file case first, which covers dwz
output on Fedora/RHEL where all partial units are within the same .debug
file.
Before (bash-5.3.9-3.fc44.x86_64 debuginfo):
$ pahole -F dwarf /usr/lib/debug/usr/bin/bash-5.3.9-3.fc44.x86_64.debug
WARNING: DW_TAG_partial_unit used, some types will not be considered!
pahole: /usr/lib/debug/usr/bin/bash-5.3.9-3.fc44.x86_64.debug: file has no dwarf type information
After:
$ pahole -F dwarf /usr/lib/debug/usr/bin/bash-5.3.9-3.fc44.x86_64.debug | wc -l
1605
$ pahole -F dwarf -C variable /usr/lib/debug/usr/bin/bash-5.3.9-3.fc44.x86_64.debug
struct variable {
char * name; /* 0 8 */
char * value; /* 8 8 */
...
/* size: 48, cachelines: 1, members: 7 */
};
Before (chromium-browser debuginfo, Fedora 44):
$ pahole /usr/lib/debug/.../chromium-browser-149.0.7827.155-1.fc44.x86_64.debug
Segmentation fault
After:
$ pahole /usr/lib/debug/.../chromium-browser-149.0.7827.155-1.fc44.x86_64.debug
WARNING: DW_FORM_GNU_ref_alt (dwz alternate debug file) not yet supported,
some types will not be available.
Reported-by: Sashiko:gemini-3-1-pro-preview # Running on a local machine
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
dwarf_loader.c | 153 ++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 131 insertions(+), 22 deletions(-)
diff --git a/dwarf_loader.c b/dwarf_loader.c
index 9f7d2bd23359191b..7091655588cd8b4d 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -136,6 +136,9 @@ struct dwarf_cu {
struct dwarf_tag *last_type_lookup;
struct cu *cu;
struct dwarf_cu *type_unit;
+ Dwarf_Off *imported_units;
+ uint32_t nr_imported_units;
+ uint32_t allocated_imported_units;
};
static int dwarf_cu__init(struct dwarf_cu *dcu, struct cu *cu)
@@ -161,6 +164,9 @@ static int dwarf_cu__init(struct dwarf_cu *dcu, struct cu *cu)
INIT_HLIST_HEAD(&dcu->hash_types[i]);
}
dcu->type_unit = NULL;
+ dcu->imported_units = NULL;
+ dcu->nr_imported_units = 0;
+ dcu->allocated_imported_units = 0;
// To avoid a per-lookup check against NULL in dwarf_cu__find_type_by_ref()
dcu->last_type_lookup = &sentinel_dtag;
return 0;
@@ -185,6 +191,7 @@ static void dwarf_cu__delete(struct cu *cu)
struct dwarf_cu *dcu = cu->priv;
+ free(dcu->imported_units);
// dcu->hash_tags & dcu->hash_types are on cu->obstack
cu__free(cu, dcu);
cu->priv = NULL;
@@ -446,12 +453,32 @@ static const char *attr_string(Dwarf_Die *die, uint32_t name, struct conf_load *
return str;
}
+static bool attr_form_is_ref_alt(Dwarf_Attribute *attr)
+{
+ if (attr->form == DW_FORM_GNU_ref_alt) {
+ static bool warned;
+
+ if (!warned) {
+ fprintf(stderr,
+ "WARNING: DW_FORM_GNU_ref_alt (dwz alternate debug file) not yet supported,\n"
+ " some types will not be available.\n");
+ warned = true;
+ }
+ return true;
+ }
+ return false;
+}
+
static bool attr_type(Dwarf_Die *die, uint32_t attr_name, Dwarf_Off *offset)
{
Dwarf_Attribute attr;
if (dwarf_attr(die, attr_name, &attr) != NULL) {
Dwarf_Die type_die;
+ if (attr_form_is_ref_alt(&attr)) {
+ *offset = 0;
+ return 0;
+ }
if (dwarf_formref_die(&attr, &type_die) != NULL) {
*offset = dwarf_dieoffset(&type_die);
return attr.form == DW_FORM_ref_sig8;
@@ -679,7 +706,8 @@ static void type__init(struct type *type, Dwarf_Die *die, struct cu *cu, struct
Dwarf_Attribute attr;
if (dwarf_attr(die, DW_AT_type, &attr) != NULL) {
Dwarf_Die type_die;
- if (dwarf_formref_die(&attr, &type_die) != NULL) {
+ if (!attr_form_is_ref_alt(&attr) &&
+ dwarf_formref_die(&attr, &type_die) != NULL) {
uint64_t encoding = attr_numeric(&type_die, DW_AT_encoding);
if (encoding == DW_ATE_signed || encoding == DW_ATE_signed_char)
@@ -993,9 +1021,14 @@ static int add_gnu_annotation_chain(Dwarf_Die *die, int component_idx,
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) {
+ while (dwarf_attr(die, DW_AT_GNU_annotation, &attr) != NULL) {
+ if (attr_form_is_ref_alt(&attr))
+ break;
+ if (dwarf_formref_die(&attr, &annot_die) == NULL)
+ break;
+ if (dwarf_tag(&annot_die) != DW_TAG_GNU_annotation)
+ break;
+
int ret = add_tag_annotation(&annot_die, component_idx, conf, head);
if (ret)
return ret;
@@ -1791,9 +1824,13 @@ check_gnu_attr:
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) {
+ while (dwarf_attr(die, DW_AT_GNU_annotation, &attr) != NULL) {
+ if (attr_form_is_ref_alt(&attr))
+ break;
+ if (dwarf_formref_die(&attr, &annot_die) == NULL)
+ break;
+ 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;
@@ -2614,7 +2651,7 @@ static struct tag *__die__process_tag(Dwarf_Die *die, struct cu *cu,
switch (dwarf_tag(die)) {
case DW_TAG_imported_unit:
- return NULL; // We don't support imported units yet, so to avoid segfaults
+ return &unsupported_tag; // Handled in die__process_unit()
case DW_TAG_array_type:
tag = die__create_new_array(die, cu); break;
case DW_TAG_string_type: // FORTRAN stuff, looks like an array
@@ -2682,9 +2719,90 @@ static struct tag *__die__process_tag(Dwarf_Die *die, struct cu *cu,
return tag;
}
-static int die__process_unit(Dwarf_Die *die, struct cu *cu, struct conf_load *conf)
+#define MAX_IMPORTED_UNIT_DEPTH 64
+
+static int die__process_unit(Dwarf_Die *die, struct cu *cu, struct conf_load *conf, int import_depth);
+
+static bool dwarf_cu__imported_unit_visited(struct dwarf_cu *dcu, Dwarf_Off offset)
+{
+ for (uint32_t i = 0; i < dcu->nr_imported_units; i++)
+ if (dcu->imported_units[i] == offset)
+ return true;
+ return false;
+}
+
+static int dwarf_cu__mark_imported_unit(struct dwarf_cu *dcu, struct cu *cu, Dwarf_Off offset)
+{
+ if (dcu->nr_imported_units == dcu->allocated_imported_units) {
+ uint32_t new_size = dcu->allocated_imported_units ? dcu->allocated_imported_units * 2 : 16;
+ Dwarf_Off *new_array = realloc(dcu->imported_units, new_size * sizeof(Dwarf_Off));
+ if (new_array == NULL)
+ return -ENOMEM;
+ dcu->imported_units = new_array;
+ dcu->allocated_imported_units = new_size;
+ }
+ dcu->imported_units[dcu->nr_imported_units++] = offset;
+ return 0;
+}
+
+static int die__process_imported_unit(Dwarf_Die *die, struct cu *cu, struct conf_load *conf, int import_depth)
+{
+ Dwarf_Attribute attr;
+
+ if (dwarf_attr(die, DW_AT_import, &attr) == NULL)
+ return 0;
+
+ if (attr_form_is_ref_alt(&attr))
+ return 0;
+
+ Dwarf_Die imported_die;
+
+ if (dwarf_formref_die(&attr, &imported_die) == NULL)
+ return 0;
+
+ if (dwarf_tag(&imported_die) != DW_TAG_partial_unit)
+ return 0;
+
+ if (import_depth >= MAX_IMPORTED_UNIT_DEPTH) {
+ static bool warned;
+
+ if (!warned) {
+ fprintf(stderr,
+ "WARNING: DW_TAG_imported_unit nesting too deep (>%d), "
+ "some types will not be available.\n",
+ MAX_IMPORTED_UNIT_DEPTH);
+ warned = true;
+ }
+ return 0;
+ }
+
+ Dwarf_Off offset = dwarf_dieoffset(&imported_die);
+ struct dwarf_cu *dcu = cu->priv;
+
+ if (dwarf_cu__imported_unit_visited(dcu, offset))
+ return 0;
+
+ if (dwarf_cu__mark_imported_unit(dcu, cu, offset))
+ return -ENOMEM;
+
+ Dwarf_Die child;
+
+ if (dwarf_child(&imported_die, &child) == 0)
+ return die__process_unit(&child, cu, conf, import_depth + 1);
+
+ return 0;
+}
+
+static int die__process_unit(Dwarf_Die *die, struct cu *cu, struct conf_load *conf, int import_depth)
{
do {
+ if (dwarf_tag(die) == DW_TAG_imported_unit) {
+ int err = die__process_imported_unit(die, cu, conf, import_depth);
+ if (err)
+ return err;
+ continue;
+ }
+
struct tag *tag = die__process_tag(die, cu, 1, conf);
if (tag == NULL)
return -ENOMEM;
@@ -3305,17 +3423,8 @@ static int die__process(Dwarf_Die *die, struct cu *cu, struct conf_load *conf)
return 0; // so that other units can be processed
}
- if (tag == DW_TAG_partial_unit) {
- static bool warned;
-
- if (!warned) {
- fprintf(stderr, "WARNING: DW_TAG_partial_unit used, some types will not be considered!\n"
- " Probably this was optimized using a tool like 'dwz'\n"
- " A future version of pahole will support this.\n");
- warned = true;
- }
- return 0; // so that other units can be processed
- }
+ if (tag == DW_TAG_partial_unit)
+ return 0; // Processed inline when reached via DW_TAG_imported_unit
if (tag != DW_TAG_compile_unit && tag != DW_TAG_type_unit) {
fprintf(stderr, "%s: DW_TAG_compile_unit, DW_TAG_type_unit, DW_TAG_partial_unit or DW_TAG_skeleton_unit expected got %s (0x%x) @ %llx!\n",
@@ -3336,7 +3445,7 @@ static int die__process(Dwarf_Die *die, struct cu *cu, struct conf_load *conf)
return DWARF_CB_OK;
if (dwarf_child(die, &child) == 0) {
- int err = die__process_unit(&child, cu, conf);
+ int err = die__process_unit(&child, cu, conf, 0);
if (err)
return err;
}
@@ -4099,7 +4208,7 @@ static int cus__merge_and_process_cu(struct cus *cus, struct conf_load *conf,
filtered = conf->early_cu_filter(&unmerged_cu) == NULL;
}
- if (!filtered && die__process_unit(&child, cu, conf) != 0)
+ if (!filtered && die__process_unit(&child, cu, conf, 0) != 0)
goto out_abort;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 13/16] dwarf_loader: Fix cus__merging_cu failing to detect DW_FORM_ref_addr
2026-06-22 20:24 [PATCHES v3 0/7] Initial support for some Rust tags, DW_TAG_imported_unit Arnaldo Carvalho de Melo
` (11 preceding siblings ...)
2026-06-22 20:24 ` [PATCH 12/16] dwarf_loader: Support DW_TAG_imported_unit for same-file partial units Arnaldo Carvalho de Melo
@ 2026-06-22 20:24 ` Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 14/16] tests: Add inter-CU type reference comparison test Arnaldo Carvalho de Melo
` (2 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-22 20:24 UTC (permalink / raw)
To: Alan Maguire; +Cc: Jiri Olsa, Clark Williams, dwarves, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
cus__merging_cu() scans abbreviation tables looking for DW_FORM_ref_addr
to detect binaries with inter-CU type references (like Rust CUs in
perf). When found, it triggers the merged CU loading path that can
resolve cross-CU references.
However, dwarf_getabbrevattr() can fail on certain attributes, notably
when DW_FORM_implicit_const is used (DWARF5). The function was treating
this failure as terminal, returning false immediately without scanning
the remaining abbreviations. This prevented detection of
DW_FORM_ref_addr in later CUs, causing the parallel path to be taken
instead — which cannot resolve cross-CU references.
For example, with the perf binary containing 507 CUs where 7 Rust CUs
(CU 209-215) use DW_FORM_ref_addr, the function was failing at CU 0
abbreviation 20 attribute 8 and returning false, never reaching the
Rust CUs.
Before:
$ pahole -F dwarf ~/bin/perf 2>&1 | grep "couldn't find" | wc -l
314
$ diff <(pahole -F dwarf ~/bin/perf 2>/dev/null) \
<(pahole --features=force_cu_merging -F dwarf ~/bin/perf 2>/dev/null) \
| grep '^[<>]' | wc -l
70
After:
$ pahole -F dwarf ~/bin/perf 2>&1 | grep "couldn't find" | wc -l
0
$ diff <(pahole -F dwarf ~/bin/perf 2>/dev/null) \
<(pahole --features=force_cu_merging -F dwarf ~/bin/perf 2>/dev/null) \
| wc -l
0
The fix changes dwarf_getattrcnt() failure to skip the current
abbreviation (goto next_abbrev) and dwarf_getabbrevattr() failure to
skip to the next attribute (continue), both continuing to scan for
DW_FORM_ref_addr instead of aborting the entire detection.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
dwarf_loader.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/dwarf_loader.c b/dwarf_loader.c
index 7091655588cd8b4d..1ab680a4c30634c8 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -3761,7 +3761,7 @@ static bool cus__merging_cu(Dwarf *dw, Elf *elf)
size_t attrcnt;
if (dwarf_getattrcnt (abbrev, &attrcnt) != 0)
- return false;
+ goto next_abbrev;
unsigned int attr_num, attr_form;
Dwarf_Off aboffset;
@@ -3769,10 +3769,11 @@ static bool cus__merging_cu(Dwarf *dw, Elf *elf)
for (j = 0; j < attrcnt; ++j) {
if (dwarf_getabbrevattr (abbrev, j, &attr_num, &attr_form,
&aboffset))
- return false;
+ continue;
if (attr_form == DW_FORM_ref_addr)
return true;
}
+next_abbrev:
offset += length;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 14/16] tests: Add inter-CU type reference comparison test
2026-06-22 20:24 [PATCHES v3 0/7] Initial support for some Rust tags, DW_TAG_imported_unit Arnaldo Carvalho de Melo
` (12 preceding siblings ...)
2026-06-22 20:24 ` [PATCH 13/16] dwarf_loader: Fix cus__merging_cu failing to detect DW_FORM_ref_addr Arnaldo Carvalho de Melo
@ 2026-06-22 20:24 ` Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 15/16] tests: Guard cleanup() against empty outdir to prevent rm /* Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 16/16] tests: Source test_lib.sh via dirname so tests run from any directory Arnaldo Carvalho de Melo
15 siblings, 0 replies; 17+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-22 20:24 UTC (permalink / raw)
To: Alan Maguire; +Cc: Jiri Olsa, Clark Williams, dwarves, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
When the perf binary includes Rust CUs, some DWARF types reference types
in other CUs via DW_FORM_ref_addr. The parallel loading path processes
each CU independently, so cross-CU references fail during
cu__recode_dwarf_types() and the types become unresolved (void).
The force_cu_merging feature worked around this by serializing loading
into a mega-CU, but disables parallel DIE processing. This test
compares both outputs to validate that cross-CU references are
correctly resolved without needing force_cu_merging.
Before the preceding fix, 314 cross-CU references failed, producing 70
lines of output difference across 12 diff hunks:
$ pahole -F dwarf ~/bin/perf 2>/dev/null > parallel.txt
$ pahole --features=force_cu_merging -F dwarf ~/bin/perf 2>/dev/null > merged.txt
$ diff parallel.txt merged.txt | grep '^[<>]' | wc -l
70
$ diff -u parallel.txt merged.txt | head -20
--- parallel.txt
+++ merged.txt
@@ -10821,7 +10821,11 @@
usize __0 __attribute__((__aligned__(8)));
struct ThreadInfo __1 __attribute__((__aligned__(8)));
+ /* XXX last struct has 8 bytes of padding, 1 hole */
+
/* size: 48, cachelines: 1, members: 2 */
+ /* member types with holes: 1, total: 1 */
+ /* paddings: 1, sum paddings: 8 */
/* forced alignments: 2 */
/* last cacheline: 48 bytes */
} __attribute__((__aligned__(8)));
The preceding commit fixed the detection of DW_FORM_ref_addr in
cus__merging_cu(), making this test pass.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tests/inter_cu_refs.sh | 50 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
create mode 100755 tests/inter_cu_refs.sh
diff --git a/tests/inter_cu_refs.sh b/tests/inter_cu_refs.sh
new file mode 100755
index 0000000000000000..6827bf83931eb3ed
--- /dev/null
+++ b/tests/inter_cu_refs.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-only
+# Copyright © 2026 Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com>
+#
+# Compare default CU loading with explicit force_cu_merging for binaries
+# with inter-CU type references (e.g. Rust CUs in perf).
+#
+# When cus__merging_cu() correctly detects DW_FORM_ref_addr usage, it
+# automatically falls back to the merged CU path. This test verifies
+# that the automatic detection produces the same output as explicitly
+# forcing CU merging.
+
+. ./test_lib.sh
+
+outdir=$(make_tmpdir)
+trap cleanup EXIT
+
+title_log "Compare parallel vs merged CU loading for inter-CU type references."
+
+perf=$(which perf 2>/dev/null)
+if [ -z "$perf" ] ; then
+ info_log "skip: No 'perf' binary available"
+ test_skip
+fi
+
+if ! pahole --features=force_cu_merging -F dwarf -C perf_event_header "$perf" 2>/dev/null | grep -q "^struct perf_event_header {" ; then
+ info_log "skip: $perf doesn't have 'struct perf_event_header' type info"
+ test_skip
+fi
+
+parallel_out=$outdir/parallel.txt
+merged_out=$outdir/merged.txt
+
+if ! pahole -F dwarf "$perf" > "$parallel_out" 2>/dev/null ; then
+ error_log "FAIL: pahole failed processing $perf (parallel mode)"
+ test_fail
+fi
+
+if ! pahole --features=force_cu_merging -F dwarf "$perf" > "$merged_out" 2>/dev/null ; then
+ error_log "FAIL: pahole failed processing $perf (merged mode)"
+ test_fail
+fi
+
+if diff -u "$parallel_out" "$merged_out" > /dev/null 2>&1 ; then
+ test_pass
+else
+ nr_diff=$(diff "$parallel_out" "$merged_out" | grep '^[<>]' | wc -l)
+ error_log "FAIL: $nr_diff lines differ between parallel and merged CU loading"
+ test_fail
+fi
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 15/16] tests: Guard cleanup() against empty outdir to prevent rm /*
2026-06-22 20:24 [PATCHES v3 0/7] Initial support for some Rust tags, DW_TAG_imported_unit Arnaldo Carvalho de Melo
` (13 preceding siblings ...)
2026-06-22 20:24 ` [PATCH 14/16] tests: Add inter-CU type reference comparison test Arnaldo Carvalho de Melo
@ 2026-06-22 20:24 ` Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 16/16] tests: Source test_lib.sh via dirname so tests run from any directory Arnaldo Carvalho de Melo
15 siblings, 0 replies; 17+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-22 20:24 UTC (permalink / raw)
To: Alan Maguire; +Cc: Jiri Olsa, Clark Williams, dwarves, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
If make_tmpdir() fails, outdir is empty. The cleanup() function
executes rm ${outdir}/* which expands to rm /* when outdir is unset or
empty — potentially deleting the entire root filesystem.
Add a guard to check that outdir is non-empty and points to an existing
directory before attempting removal.
Fixes: 52dbfb0b39595ed8 ("pahole: Refactor selftests")
Reported-by: Sashiko:gemini-3-1-pro-preview # Running on a local machine
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tests/test_lib.sh | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/tests/test_lib.sh b/tests/test_lib.sh
index fb724329b6079aa2..19c90e897d9bff53 100755
--- a/tests/test_lib.sh
+++ b/tests/test_lib.sh
@@ -176,7 +176,9 @@ test_skip()
cleanup()
{
- rm ${outdir}/*
- rmdir $outdir
+ if [ -n "$outdir" ] && [ -d "$outdir" ]; then
+ rm ${outdir}/*
+ rmdir $outdir
+ fi
return 0
}
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 16/16] tests: Source test_lib.sh via dirname so tests run from any directory
2026-06-22 20:24 [PATCHES v3 0/7] Initial support for some Rust tags, DW_TAG_imported_unit Arnaldo Carvalho de Melo
` (14 preceding siblings ...)
2026-06-22 20:24 ` [PATCH 15/16] tests: Guard cleanup() against empty outdir to prevent rm /* Arnaldo Carvalho de Melo
@ 2026-06-22 20:24 ` Arnaldo Carvalho de Melo
15 siblings, 0 replies; 17+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-22 20:24 UTC (permalink / raw)
To: Alan Maguire; +Cc: Jiri Olsa, Clark Williams, dwarves, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
The test scripts sourced test_lib.sh using a bare filename (source
test_lib.sh or . ./test_lib.sh), which only works when the current
working directory is tests/. Running a test from the repository root
(e.g. tests/inter_cu_refs.sh) would fail with "test_lib.sh: No such
file or directory".
Use $(dirname "$0")/test_lib.sh so each script locates the library
relative to its own path, regardless of the caller's working directory.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tests/btf_functions.sh | 2 +-
tests/btf_type_tag_order.sh | 2 +-
tests/default_vmlinux_btf.sh | 2 +-
tests/flexible_arrays.sh | 2 +-
tests/gcc_true_signatures.sh | 2 +-
tests/inter_cu_refs.sh | 2 +-
tests/pfunct-btf-decl-tags.sh | 2 +-
tests/prettify_perf.data.sh | 2 +-
tests/reproducible_build.sh | 2 +-
9 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/tests/btf_functions.sh b/tests/btf_functions.sh
index 127640e158b70805..351412a4c6a8ccb9 100755
--- a/tests/btf_functions.sh
+++ b/tests/btf_functions.sh
@@ -8,7 +8,7 @@
# also should have been.
#
-source test_lib.sh
+source "$(dirname "$0")/test_lib.sh"
vmlinux=$(get_vmlinux $1)
if [ $? -ne 0 ] ; then
diff --git a/tests/btf_type_tag_order.sh b/tests/btf_type_tag_order.sh
index 09d1ac346000bb6b..0e671ae538603d2b 100755
--- a/tests/btf_type_tag_order.sh
+++ b/tests/btf_type_tag_order.sh
@@ -3,7 +3,7 @@
# Check that pahole preserves btf_type_tag order when emitting BTF from DWARF.
-source test_lib.sh
+source "$(dirname "$0")/test_lib.sh"
outdir=$(make_tmpdir)
diff --git a/tests/default_vmlinux_btf.sh b/tests/default_vmlinux_btf.sh
index 496e840bfbec8f3a..b3d6e9cff8719e5f 100755
--- a/tests/default_vmlinux_btf.sh
+++ b/tests/default_vmlinux_btf.sh
@@ -1,5 +1,5 @@
#!/bin/bash
-source test_lib.sh
+source "$(dirname "$0")/test_lib.sh"
title_log "Default BTF on a system without BTF."
diff --git a/tests/flexible_arrays.sh b/tests/flexible_arrays.sh
index 4e9e995271ce816f..6443449b50e6c9c2 100755
--- a/tests/flexible_arrays.sh
+++ b/tests/flexible_arrays.sh
@@ -5,7 +5,7 @@
#
# Arnaldo Carvalho de Melo <acme@redhat.com> (C) 2024-
-source test_lib.sh
+source "$(dirname "$0")/test_lib.sh"
vmlinux=$(get_vmlinux $1)
if [ $? -ne 0 ] ; then
diff --git a/tests/gcc_true_signatures.sh b/tests/gcc_true_signatures.sh
index 9ebe1fa6c32f7bf7..96b282be5dba721a 100755
--- a/tests/gcc_true_signatures.sh
+++ b/tests/gcc_true_signatures.sh
@@ -1,7 +1,7 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0-only
-source test_lib.sh
+source "$(dirname "$0")/test_lib.sh"
outdir=$(make_tmpdir)
diff --git a/tests/inter_cu_refs.sh b/tests/inter_cu_refs.sh
index 6827bf83931eb3ed..0df0ce13cd948f1f 100755
--- a/tests/inter_cu_refs.sh
+++ b/tests/inter_cu_refs.sh
@@ -10,7 +10,7 @@
# that the automatic detection produces the same output as explicitly
# forcing CU merging.
-. ./test_lib.sh
+. "$(dirname "$0")/test_lib.sh"
outdir=$(make_tmpdir)
trap cleanup EXIT
diff --git a/tests/pfunct-btf-decl-tags.sh b/tests/pfunct-btf-decl-tags.sh
index 520148b2ccef15b2..25d9cd6d4ad8551f 100755
--- a/tests/pfunct-btf-decl-tags.sh
+++ b/tests/pfunct-btf-decl-tags.sh
@@ -3,7 +3,7 @@
# Check that pfunct can print btf_decl_tags read from BTF
-source test_lib.sh
+source "$(dirname "$0")/test_lib.sh"
outdir=$(make_tmpdir)
diff --git a/tests/prettify_perf.data.sh b/tests/prettify_perf.data.sh
index 384c250ff4e01a4c..ae0a7d995da6cad2 100755
--- a/tests/prettify_perf.data.sh
+++ b/tests/prettify_perf.data.sh
@@ -7,7 +7,7 @@
# Check if the perf binary is available, if it is from a distro, normally it
# will get the needed DWARF info using libddebuginfod, we'll check if the
# needed types are available, skipping the test and informing the reason.
-. ./test_lib.sh
+. "$(dirname "$0")/test_lib.sh"
outdir=$(make_tmpdir)
diff --git a/tests/reproducible_build.sh b/tests/reproducible_build.sh
index d8c6507526bdfbbc..7b7a5afd1fe4e50d 100755
--- a/tests/reproducible_build.sh
+++ b/tests/reproducible_build.sh
@@ -4,7 +4,7 @@
# Test if BTF generated serially matches reproducible parallel DWARF loading + serial BTF encoding
# Arnaldo Carvalho de Melo <acme@redhat.com> (C) 2024-
-source test_lib.sh
+source "$(dirname "$0")/test_lib.sh"
vmlinux=$(get_vmlinux $1)
if [ $? -ne 0 ]; then
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
end of thread, other threads:[~2026-06-22 20:25 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-22 20:24 [PATCHES v3 0/7] Initial support for some Rust tags, DW_TAG_imported_unit Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 01/16] dwarf_loader: Initial support for DW_TAG_variant_part Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 02/16] dwarf_loader: Allow forcing the merge of CUs for solving inter CU tag references Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 03/16] dwarf_loader: Initial support for DW_TAG_subprogram in DW_TAG_enumeration Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 04/16] encoders: Fix diagnostic messages for unexpected tags in enumerations Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 05/16] dwarves_fprintf: Accumulate function__fprintf return value in enumeration printing Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 06/16] dwarves: Use tag__delete for enumeration children Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 07/16] btf_encoder: Fix types__match parameter comparison in BTF_KIND_FUNC_PROTO Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 08/16] encoders: Handle DW_TAG_subprogram in enumerations during BTF/CTF encoding Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 09/16] dwarf_loader: Populate DW_TAG_variant children in DW_TAG_variant_part Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 10/16] btf_encoder: Encode variant parts as union members in BTF Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 11/16] dwarf_loader: Handle DW_FORM_block in attr_numeric for Rust discriminant values Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 12/16] dwarf_loader: Support DW_TAG_imported_unit for same-file partial units Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 13/16] dwarf_loader: Fix cus__merging_cu failing to detect DW_FORM_ref_addr Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 14/16] tests: Add inter-CU type reference comparison test Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 15/16] tests: Guard cleanup() against empty outdir to prevent rm /* Arnaldo Carvalho de Melo
2026-06-22 20:24 ` [PATCH 16/16] tests: Source test_lib.sh via dirname so tests run from any directory Arnaldo Carvalho de Melo
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.