mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Namhyung Kim <namhyung@kernel.org>
To: Arnaldo Carvalho de Melo <acme@kernel.org>
Cc: Ingo Molnar <mingo@kernel.org>,
	Thomas Gleixner <tglx@linutronix.de>,
	James Clark <james.clark@linaro.org>,
	Jiri Olsa <jolsa@kernel.org>, Ian Rogers <irogers@google.com>,
	Adrian Hunter <adrian.hunter@intel.com>,
	Clark Williams <williams@redhat.com>,
	linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org,
	Arnaldo Carvalho de Melo <acme@redhat.com>
Subject: Re: [PATCH 10/12] perf annotate-data: Resolve type DIEs in the debug file they came from
Date: Wed, 16 Sep 2026 14:44:54 -0700	[thread overview]
Message-ID: <aqsN1si6jjWrocIQ@google.com> (raw)
In-Reply-To: <20260916114740.48230-11-acme@kernel.org>

On Wed, Sep 16, 2026 at 08:47:37AM -0300, Arnaldo Carvalho de Melo wrote:
> From: Arnaldo Carvalho de Melo <acme@redhat.com>
> 
> A 'perf report -s type' on a 783 MB AMD IBS data type profiling session
> hangs, burning all of a CPU and producing no output:
> 
>   ⬢ [acme@toolbx perf-tools-next]$ perf report --progress -s type -i perf.data.ibs > /dev/null
>   Processing events... [100.0%] 746M / 746M
>   Merging related events... [ 58.0%] 293828 / 506686
>   Merging related events... [ 59.0%] 298894 / 506686
>   Merging related events... [ 60.0%] 303960 / 506686
>   Merging related events... [ 61.0%] 309026 / 506686
>   [ ... nothing else, ever ... ]
> 
> It is a spin and not a slow path: with perf-stuck the CPU time used
> grows by a whole interval on every sample, while the [stack] mapping,
> which would be moving down if this was recursion, stays put:
> 
>   ⬢ [acme@toolbx perf-tools-next]$ tools/perf/scripts/perf-stuck.sh -i 15 -n 4 -l ibs.log $(pgrep -x perf)
>   watching 2297313 (perf report --progress -s type -i perf.data.ibs) every 15s
>   09:39:34 state=R cpu=+0 (0.00s) rss=664295kB stack=7ffc3ae31000-7ffc3ae52000 size=132kB  Merging related events... [ 39.0%] 197574 / 506686
>   09:39:49 state=R cpu=+1496 (14.96s) rss=665383kB stack=7ffc3ae31000-7ffc3ae52000 size=132kB stuck=1 Merging related events... [ 39.0%] 197574 / 506686
>   09:40:04 state=R cpu=+1497 (14.97s) rss=665383kB stack=7ffc3ae31000-7ffc3ae52000 size=132kB stuck=2 Merging related events... [ 39.0%] 197574 / 506686
>   09:40:19 state=R cpu=+1497 (14.97s) rss=502250kB stack=7ffc3ae31000-7ffc3ae52000 size=132kB stuck=0 Merging related events... [ 61.0%] 309026 / 506686
> 
> Attaching gdb and stepping the loop, printing dwarf_dieoffset() and
> dwarf_tag() for the DIE being chased on each trip round it, shows the
> chase never moving, on a typedef that refers to itself:
> 
>   ⬢ [acme@toolbx perf-tools-next]$ gdb -p $(pgrep -x perf) -batch -x tools/perf/scripts/perf-stuck.gdb -ex 'perf-die-chain die_get_pointer_type type_die 8' -ex perf-dso -ex detach
>   stuck in die_get_pointer_type():
>   #3  0x0000000000772421 in die_get_pointer_type (type_die=0x7ffc3ae4b710, type_die@entry=0x7ffc3ae4b6f0, die_mem=die_mem@entry=0x7ffc3ae4b710) at util/dwarf-aux.c:327
>   327			type_die = die_get_type(type_die, die_mem);
>   chain[0] die=0x7ffc3ae4b710 addr=0x7ff6811591ef cu=0x44ef0878 off=0x1f tag=22 name=(null)
>   chain[1] die=0x7ffc3ae4b710 addr=0x7ff6811591ef cu=0x44ef0878 off=0x1f tag=22 name=(null)
>   [ ... the very same DIE, forever ... ]
>   dso=/usr/lib64/libz.so.1.3.1.zlib-ng ip=0xe2e sym=build_tree
> 
> The DIE is at offset 0x1f of the debug info of libz.so.1, which is
> zlib-ng, and is one of the dwz compressed ones: the type DIEs shared by
> more than one CU live in the common file, where 0x1f is a perfectly good
> DW_TAG_base_type:
> 
>   ⬢ [acme@toolbx perf-tools-next]$ readelf --debug-dump=info /usr/lib/debug/.dwz/zlib-ng-2.3.3-3.fc44.x86_64 | sed -n '/Compilation Unit @ offset 0:/,/Compilation Unit @ offset 0x5f:/p'
>     Compilation Unit @ offset 0:
>      Length:        0x5b (32-bit)
>      Version:       5
>      Unit Type:     DW_UT_partial (3)
>    <0><c>: Abbrev Number: 1 (DW_TAG_partial_unit)
>    [...]
>    <1><1f>: Abbrev Number: 62 (DW_TAG_base_type)
>       <20>   DW_AT_byte_size   : 4
>       <21>   DW_AT_encoding    : 5	(signed)
>       <22>   DW_AT_name        : int
> 
> while in the main debug file that same offset is not a DIE at all, it is
> the start of another unit's header:
> 
>   ⬢ [acme@toolbx perf-tools-next]$ readelf --debug-dump=info /usr/lib/debug/usr/lib64/libz.so.1.3.1.zlib-ng-2.3.3-3.fc44.x86_64.debug | grep 'Compilation Unit @' | head -2
>     Compilation Unit @ offset 0:
>     Compilation Unit @ offset 0x1f:
> 
> die_collect_vars() saves the dwarf_dieoffset() of the type DIE, which is
> relative to the file that DIE lives in, and update_var_state() then hands
> that offset to dwarf_offdie() together with the main debug file, where
> dwarf_offdie() parses whatever is there as a DIE, in this case the one
> typedef that refers to itself, which is what makes the "follow the
> typedefs and qualifiers until a pointer or an array type" loop in
> die_get_pointer_type() spin.
> 
> So record, next to the offset, whether the type DIE was in the file the
> variable DIE came from or in the dwz common one, and resolve the offset
> in the file it came from, which is what the new die_get_type_die() does.
> 
> Which of the two that is does not have to be guessed from what is at the
> offset: dwz encodes the references into its common file as
> DW_FORM_GNU_ref_alt, so elfutils resolves them into the alt Dwarf and
> the CU of the resulting type DIE belongs to that other file, so
> comparing the Dwarf each of the two CUs belongs to, die_same_file(),
> settles it exactly, for any number of hops from the variable DIE.
> 
> Then bound the chases themselves: no sane chain of typedefs and
> qualifiers is 32 DIEs long, no sane nesting for the struct and union
> members that __add_member_cb() follows recursively is 8 deep, and the
> same recursion bound covers the type names die_get_typename_from_type()
> builds by following pointers and arrays, so that a debug info file
> broken in some other way makes perf give up on a type, telling about it
> with pr_debug, visible with -v, instead of looking like it hung.  The
> member nesting bound is reported with pr_debug_dtp (visible with -vvv or
> -D type-profile).
> 
> Cycles can't occur in member trees from valid DWARF, embedded members
> can't be recursive in C, so the nesting bound only ever bites legitimate
> depth and the member where the recursion was cut is marked 'truncated',
> which the JSON exporter added in a subsequent series reports to its
> consumers, so that they can tell a truncated tree from one that really
> ends there.
> 
> die_get_type_die() then has no fallback to the other file: resolving an
> alt file offset in the main file is the misparse above, so it resolves
> in the file the offset was recorded as belonging to, and gives up on the
> type when the offset does not resolve there.  The tag the type DIE had is
> kept as a sanity check, a mismatch means the debug info changed under
> perf or is broken in yet another way, and the bounds on the type chasers
> remain the backstop: a debug info file broken in some other way still
> makes perf give up on a type with a pr_debug instead of hanging.
> 
> Testing:
> 
> Before, on the 783 MB AMD IBS session, killed after some 8 minutes stuck
> at 61%:
> 
>   ⬢ [acme@toolbx perf-tools-next]$ perf report --progress -s type -i perf.data.ibs > /dev/null
>   Processing events... [100.0%] 746M / 746M
>   Merging related events... [ 61.0%] 309026 / 506686
>   ⬢ [acme@toolbx perf-tools-next]$
> 
> After, the whole session is processed, and the zlib-ng types, from the
> build_tree() hist entry that used to hang it, show up:
> 
>   ⬢ [acme@toolbx perf-tools-next]$ perf report --progress -s type -i perf.data.ibs > ibs.out
>   Processing events... [100.0%] 746M / 746M
>   Merging related events... [100.0%] 506686 / 506686
>   Sorting events for output... [100.0%] 1428 / 1428
>   ⬢ [acme@toolbx perf-tools-next]$ grep -E "deflate_state|inflate_state|internal_state" ibs.out
>       0.00%  deflate_state
>       0.00%  deflate_state*
>       0.00%  struct inflate_state
>       0.00%  struct inflate_state*
>       0.00%  struct internal_state
>   ⬢ [acme@toolbx perf-tools-next]$ perf test 17 27 30 31 85 88
>   17: Match and link multiple hists                              : Ok
>   27: Filter hist entries                                        : Ok
>   30: Sort output of hist entries                                : Ok
>   31: Cumulate child hist entries                                : Ok
>   85: Test that perf report includes file offsets and event type names in diagnostic messages. : Ok
>   88: Test that perf report handles truncated perf.data gracefully (no crash, no segfault — clean error exit).: Skip
>   ⬢ [acme@toolbx perf-tools-next]$
> 
> Requiring elfutils 0.160 for this: dwarf_cu_getdwarf(), the function
> that tells which Dwarf a CU belongs to, first appeared in 0.160 ("libdw:
> New functions dwarf_cu_getdwarf, dwarf_cu_die", elfutils NEWS), so the
> libdw feature test now probes for it, in tools/build/feature/test-libdw.c,
> and Makefile.config says 0.160 where it said 0.157.  The probe takes the
> address instead of calling it, as that is all that is needed to make a
> 0.157-0.159 elfutils, from 2014 and without the symbol, disable dwarf
> support with the existing message rather than fail to link dwarf-aux.c.
> 
> Fixes: 06b2ce75386df04b ("perf annotate-data: Maintain variable type info")
> Fixes: 55ee3d005d62279d ("perf annotate-data: Add a cache for global variable types")
> Assisted-by: LLM
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
> ---
>  tools/build/feature/test-libdw.c |  15 ++-
>  tools/perf/Makefile.config       |   2 +-
>  tools/perf/util/annotate-data.c  |  59 ++++++++---
>  tools/perf/util/annotate-data.h  |   3 +
>  tools/perf/util/dwarf-aux.c      | 171 +++++++++++++++++++++++++++----
>  tools/perf/util/dwarf-aux.h      |  33 ++++++
>  6 files changed, 247 insertions(+), 36 deletions(-)
> 
> diff --git a/tools/build/feature/test-libdw.c b/tools/build/feature/test-libdw.c
> index aabd63ca76b4d7e6..23e1ba6ff3466b9f 100644
> --- a/tools/build/feature/test-libdw.c
> +++ b/tools/build/feature/test-libdw.c
> @@ -49,8 +49,21 @@ int test_elfutils(void)
>  	return 0;
>  }
>  
> +/*
> + * elfutils 0.160 and later: used to tell which debug file a DIE lives in,
> + * the dwz alt file or the main one, see die_same_file() in
> + * tools/perf/util/dwarf-aux.c.  Only the symbol is needed, so take its
> + * address instead of calling it.
> + */
> +int test_libdw_cu_getdwarf(void)
> +{
> +	void *sym = (void *)dwarf_cu_getdwarf;
> +
> +	return sym == NULL;
> +}
> +
>  int main(void)
>  {
>  	return test_libdw() + test_libdw_unwind() + test_libdw_getlocations() +
> -	       test_libdw_getcfi() + test_elfutils();
> +	       test_libdw_getcfi() + test_libdw_cu_getdwarf() + test_elfutils();
>  }
> diff --git a/tools/perf/Makefile.config b/tools/perf/Makefile.config
> index 4ee7393a39f91d85..0585d71e3182c6dd 100644
> --- a/tools/perf/Makefile.config
> +++ b/tools/perf/Makefile.config
> @@ -470,7 +470,7 @@ else
>    else
>      ifneq ($(feature-libdw), 1)
>        ifndef NO_LIBDW
> -        $(warning No libdw.h found or old libdw.h found or elfutils is older than 0.157, disables dwarf support. Please install new elfutils-devel/libdw-dev)
> +        $(warning No libdw.h found or old libdw.h found or elfutils is older than 0.160, disables dwarf support. Please install new elfutils-devel/libdw-dev)
>          NO_LIBDW := 1
>        endif
>      endif # Dwarf support
> diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
> index aff60a630fd05b01..e67c9a21a7a27af2 100644
> --- a/tools/perf/util/annotate-data.c
> +++ b/tools/perf/util/annotate-data.c
> @@ -221,6 +221,15 @@ static bool data_type_less(struct rb_node *node_a, const struct rb_node *node_b)
>  	return strcmp(a->self.type_name, b->self.type_name) < 0;
>  }
>  
> +/*
> + * Members of struct/union members are added recursively, and the same DIE
> + * that is not what it looks like, the one that makes the type chasers in
> + * util/dwarf-aux.c spin, can make a member's type point back at one of its
> + * own ancestors, recursing until the stack is gone.  Nothing usable comes
> + * out of nesting members this deep anyway.
> + */
> +#define MAX_MEMBER_DEPTH 8
> +
>  /* Recursively add new members for struct/union */
>  static int __add_member_cb(Dwarf_Die *die, void *arg)
>  {
> @@ -235,6 +244,16 @@ static int __add_member_cb(Dwarf_Die *die, void *arg)
>  	if (dwarf_tag(die) != DW_TAG_member)
>  		return DIE_FIND_CB_SIBLING;
>  
> +	if (__die_get_real_type(die, &member_type) == NULL)
> +		return DIE_FIND_CB_SIBLING;
> +
> +	if (dwarf_tag(&member_type) == DW_TAG_typedef) {
> +		if (die_get_real_type(&member_type, &die_mem) == NULL)
> +			return DIE_FIND_CB_SIBLING;
> +	} else {
> +		die_mem = member_type;
> +	}
> +
>  	member = zalloc(sizeof(*member));
>  	if (member == NULL)
>  		return DIE_FIND_CB_END;
> @@ -242,12 +261,6 @@ static int __add_member_cb(Dwarf_Die *die, void *arg)
>  	strbuf_init(&sb, 32);
>  	die_get_typename(die, &sb);
>  
> -	__die_get_real_type(die, &member_type);
> -	if (dwarf_tag(&member_type) == DW_TAG_typedef)
> -		die_get_real_type(&member_type, &die_mem);
> -	else
> -		die_mem = member_type;
> -
>  	if (dwarf_aggregate_size(&die_mem, &size) < 0)
>  		size = 0;

This is gonna conflict with my change. :)

>  
> @@ -289,10 +302,23 @@ static int __add_member_cb(Dwarf_Die *die, void *arg)
>  	}
>  	member->size = size;
>  	member->offset = loc + parent->offset;
> +	member->depth = parent->depth + 1;
>  	INIT_LIST_HEAD(&member->children);
>  	list_add_tail(&member->node, &parent->children);
>  
>  	tag = dwarf_tag(&die_mem);
> +	if (member->depth >= MAX_MEMBER_DEPTH) {
> +		/*
> +		 * The JSON exporter added in a later series reports
> +		 * this to its consumers, so that they can tell a
> +		 * truncated tree from one that really ends here.
> +		 */
> +		member->truncated = true;
> +		pr_debug_dtp("member nesting limit reached at %s\n",
> +			     member->type_name ?: "(unknown type)");
> +		return DIE_FIND_CB_SIBLING;
> +	}
> +

I think it's worth to be a separate change.


>  	switch (tag) {
>  	case DW_TAG_structure_type:
>  	case DW_TAG_union_type:
> @@ -645,6 +671,8 @@ struct global_var_entry {
>  	u64 start;
>  	u64 end;
>  	u64 die_offset;
> +	int die_tag;
> +	bool from_alt;	/* die_offset is relative to the alt (dwz) file */
>  };
>  
>  static int global_var_cmp(const void *_key, const struct rb_node *node)
> @@ -682,7 +710,7 @@ static struct global_var_entry *global_var__find(struct data_loc_info *dloc, u64
>  }
>  
>  static bool global_var__add(struct data_loc_info *dloc, u64 addr,
> -			    const char *name, Dwarf_Die *type_die)
> +			    const char *name, Dwarf_Die *type_die, bool from_alt)
>  {
>  	struct dso *dso = map__dso(dloc->ms->map);
>  	struct global_var_entry *gvar;
> @@ -704,6 +732,8 @@ static bool global_var__add(struct data_loc_info *dloc, u64 addr,
>  	gvar->start = addr;
>  	gvar->end = addr + size;
>  	gvar->die_offset = dwarf_dieoffset(type_die);
> +	gvar->die_tag = dwarf_tag(type_die);
> +	gvar->from_alt = from_alt;
>  
>  	rb_add(&gvar->node, dso__global_vars(dso), global_var_less);
>  	return true;
> @@ -778,12 +808,14 @@ static void global_var__collect(struct data_loc_info *dloc)
>  			if (pos->reg != -1)
>  				continue;
>  
> -			if (!dwarf_offdie(dwarf, pos->die_off, &type_die))
> +			if (!die_get_type_die(dwarf, pos->die_off, pos->die_tag,
> +					      pos->from_alt, &type_die))
>  				continue;
>  
>  			get_global_var_info(dloc, pos->addr, &var_name, &var_offset);
>  
> -			global_var__add(dloc, pos->addr, var_name, &type_die);
> +			global_var__add(dloc, pos->addr, var_name, &type_die,
> +					pos->from_alt);
>  		}
>  
>  		delete_var_types(var_types);
> @@ -808,7 +840,8 @@ bool get_global_var_type(Dwarf_Die *cu_die, struct data_loc_info *dloc,
>  
>  	gvar = global_var__find(dloc, var_addr);
>  	if (gvar) {
> -		if (!dwarf_offdie(dloc->di->dbg, gvar->die_offset, type_die))
> +		if (!die_get_type_die(dloc->di->dbg, gvar->die_offset,
> +				      gvar->die_tag, gvar->from_alt, type_die))
>  			return false;
>  
>  		*var_offset = var_addr - gvar->start;
> @@ -838,7 +871,8 @@ bool get_global_var_type(Dwarf_Die *cu_die, struct data_loc_info *dloc,
>  
>  ok:
>  	/* The address should point to the start of the variable */
> -	global_var__add(dloc, var_addr - *var_offset, var_name, type_die);
> +	global_var__add(dloc, var_addr - *var_offset, var_name, type_die,
> +			!die_same_file(cu_die, type_die));
>  	return true;
>  }
>  
> @@ -893,7 +927,8 @@ static void update_var_state(struct type_state *state, struct data_loc_info *dlo
>  				continue;
>  		}
>  		/* Get the type DIE using the offset */
> -		if (!dwarf_offdie(dloc->di->dbg, var->die_off, &mem_die))
> +		if (!die_get_type_die(dloc->di->dbg, var->die_off,
> +				      var->die_tag, var->from_alt, &mem_die))
>  			continue;
>  
>  		if (var->reg == DWARF_REG_FB || var->reg == fbreg || var->reg == state->stack_reg) {
> diff --git a/tools/perf/util/annotate-data.h b/tools/perf/util/annotate-data.h
> index ca2096a9ee62cbfe..cc576232f55b5fb0 100644
> --- a/tools/perf/util/annotate-data.h
> +++ b/tools/perf/util/annotate-data.h
> @@ -57,6 +57,9 @@ struct annotated_member {
>  	char *var_name;
>  	int offset;
>  	int size;
> +	unsigned int depth;
> +	/* Children not expanded because the nesting limit was reached */
> +	bool truncated;
>  };
>  
>  /**
> diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
> index d7160f87ac7d7ab3..7acb431fd34a8ecb 100644
> --- a/tools/perf/util/dwarf-aux.c
> +++ b/tools/perf/util/dwarf-aux.c

I tend to split changes in the dwarf-aux.[ch] as they are primitive
helpers and need to work in generic way.

Thanks,
Namhyung


> @@ -266,16 +266,35 @@ Dwarf_Die *die_get_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
>  		return NULL;
>  }
>  
> +/*
> + * The chases below cross typedefs and qualifiers to get to the type that
> + * is actually meant, and a DIE that is not what it looks like, e.g. one
> + * parsed at an offset that is not the start of a DIE in the file it was
> + * resolved in, can have a DW_AT_type that refers back to itself, which
> + * makes them spin forever: 'perf report -s type' did exactly that on the
> + * dwz compressed debug info of zlib-ng (libz.so.1), burning all of a CPU
> + * with no output while resolving a hist entry in build_tree().
> + *
> + * No sane chain is this long, so give up instead of hanging, telling about
> + * it so that the broken debug info can be looked at.
> + */
> +#define MAX_TYPE_CHASE 32
> +
>  /* Get a type die, but skip qualifiers */
>  Dwarf_Die *__die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
>  {
> -	int tag;
> +	int tag, chase = 0;
>  
>  	do {
>  		vr_die = die_get_type(vr_die, die_mem);
>  		if (!vr_die)
> -			break;
> +			return NULL;
>  		tag = dwarf_tag(vr_die);
> +		if (++chase > MAX_TYPE_CHASE) {
> +			pr_debug("DWARF: qualifier chase limit reached at DIE 0x%lx\n",
> +				 (unsigned long)dwarf_dieoffset(vr_die));
> +			return NULL;
> +		}
>  	} while (tag == DW_TAG_const_type ||
>  		 tag == DW_TAG_restrict_type ||
>  		 tag == DW_TAG_volatile_type ||
> @@ -296,8 +315,15 @@ Dwarf_Die *__die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
>   */
>  Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
>  {
> +	int chase = 0;
> +
>  	do {
>  		vr_die = __die_get_real_type(vr_die, die_mem);
> +		if (++chase > MAX_TYPE_CHASE) {
> +			pr_debug("DWARF: typedef chase limit reached at DIE 0x%lx\n",
> +				 vr_die ? (unsigned long)dwarf_dieoffset(vr_die) : 0);
> +			return NULL;
> +		}
>  	} while (vr_die && dwarf_tag(vr_die) == DW_TAG_typedef);
>  
>  	return vr_die;
> @@ -314,7 +340,7 @@ Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
>   */
>  Dwarf_Die *die_get_pointer_type(Dwarf_Die *type_die, Dwarf_Die *die_mem)
>  {
> -	int tag;
> +	int tag, chase = 0;
>  
>  	do {
>  		tag = dwarf_tag(type_die);
> @@ -324,6 +350,11 @@ Dwarf_Die *die_get_pointer_type(Dwarf_Die *type_die, Dwarf_Die *die_mem)
>  		    tag != DW_TAG_restrict_type && tag != DW_TAG_volatile_type &&
>  		    tag != DW_TAG_shared_type)
>  			return NULL;
> +		if (++chase > MAX_TYPE_CHASE) {
> +			pr_debug("DWARF: pointer type chase limit reached at DIE 0x%lx\n",
> +				 (unsigned long)dwarf_dieoffset(type_die));
> +			return NULL;
> +		}
>  		type_die = die_get_type(type_die, die_mem);
>  	} while (type_die);
>  
> @@ -1118,17 +1149,27 @@ Dwarf_Die *die_find_member(Dwarf_Die *st_die, const char *name,
>  			      die_mem);
>  }
>  
> -/**
> - * die_get_typename_from_type - Get the name of given type DIE
> - * @type_die: a type DIE
> - * @buf: a strbuf for result type name
> - *
> - * Get the name of @type_die and stores it to @buf. Return 0 if succeeded.
> - * and Return -ENOENT if failed to find type name.
> - * Note that the result will stores typedef name if possible, and stores
> - * "*(function_type)" if the type is a function pointer.
> +/*
> + * The name of a pointer or array type is built from the name of the type
> + * it points to or holds, so the recursion below follows DW_AT_type; a
> + * garbage DIE whose DW_AT_type refers back to itself makes it recurse
> + * forever, just like the chases above, so it gets the same bound.
>   */
> -int die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf)
> +static int __die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf,
> +					int depth);
> +
> +static int __die_get_typename(Dwarf_Die *vr_die, struct strbuf *buf, int depth)
> +{
> +	Dwarf_Die type;
> +
> +	if (__die_get_real_type(vr_die, &type) == NULL)
> +		return -ENOENT;
> +
> +	return __die_get_typename_from_type(&type, buf, depth);
> +}
> +
> +static int __die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf,
> +					int depth)
>  {
>  	int tag, ret;
>  	const char *tmp = "";
> @@ -1155,7 +1196,12 @@ int die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf)
>  		/* Write a base name */
>  		return strbuf_addf(buf, "%s%s", tmp, name ?: "");
>  	}
> -	ret = die_get_typename(type_die, buf);
> +	if (depth >= MAX_TYPE_CHASE) {
> +		pr_debug("DWARF: type name recursion limit reached at DIE 0x%lx\n",
> +			 (unsigned long)dwarf_dieoffset(type_die));
> +		return -ENOENT;
> +	}
> +	ret = __die_get_typename(type_die, buf, depth + 1);
>  	if (ret < 0) {
>  		/* void pointer has no type attribute */
>  		if (tag == DW_TAG_pointer_type && ret == -ENOENT)
> @@ -1166,6 +1212,21 @@ int die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf)
>  	return strbuf_addstr(buf, tmp);
>  }
>  
> +/**
> + * die_get_typename_from_type - Get the name of given type DIE
> + * @type_die: a type DIE
> + * @buf: a strbuf for result type name
> + *
> + * Get the name of @type_die and stores it to @buf. Return 0 if succeeded.
> + * and Return -ENOENT if failed to find type name.
> + * Note that the result will stores typedef name if possible, and stores
> + * "*(function_type)" if the type is a function pointer.
> + */
> +int die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf)
> +{
> +	return __die_get_typename_from_type(type_die, buf, 0);
> +}
> +
>  /**
>   * die_get_typename - Get the name of given variable DIE
>   * @vr_die: a variable DIE
> @@ -1178,12 +1239,7 @@ int die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf)
>   */
>  int die_get_typename(Dwarf_Die *vr_die, struct strbuf *buf)
>  {
> -	Dwarf_Die type;
> -
> -	if (__die_get_real_type(vr_die, &type) == NULL)
> -		return -ENOENT;
> -
> -	return die_get_typename_from_type(&type, buf);
> +	return __die_get_typename(vr_die, buf, 0);
>  }
>  
>  /**
> @@ -1632,6 +1688,22 @@ Dwarf_Die *die_find_variable_by_addr(Dwarf_Die *sc_die, Dwarf_Addr addr,
>  	return result;
>  }
>  
> +/*
> + * Whether two DIEs live in the same debug file.
> + *
> + * dwarf_dieoffset() is relative to the file the DIE is in, so this is what
> + * tells an offset that has to be resolved in the dwz alt file, where dwz
> + * moved the type, from one that belongs to the main file: dwz encodes the
> + * references into its common file as DW_FORM_GNU_ref_alt, elfutils resolves
> + * them into the alt Dwarf and the CU of the resulting DIE belongs to that
> + * other file, so comparing the Dwarf each CU belongs to settles it exactly,
> + * rather than inferring it from what happens to be at the offset.
> + */
> +bool die_same_file(Dwarf_Die *die_a, Dwarf_Die *die_b)
> +{
> +	return dwarf_cu_getdwarf(die_a->cu) == dwarf_cu_getdwarf(die_b->cu);
> +}
> +
>  static int __die_collect_vars_cb(Dwarf_Die *die_mem, void *arg)
>  {
>  	struct die_var_type **var_types = arg;
> @@ -1676,6 +1748,8 @@ static int __die_collect_vars_cb(Dwarf_Die *die_mem, void *arg)
>  			vt->is_reg_var_addr = true;
>  
>  		vt->die_off = dwarf_dieoffset(&type_die);
> +		vt->die_tag = dwarf_tag(&type_die);
> +		vt->from_alt = !die_same_file(die_mem, &type_die);
>  		vt->addr = start;
>  		vt->end = end;
>  		vt->has_range = (end != 0 || start != 0);
> @@ -1695,7 +1769,8 @@ static int __die_collect_vars_cb(Dwarf_Die *die_mem, void *arg)
>   *
>   * Save all variables and parameters in the @sc_die and save them to @var_types.
>   * The @var_types is a singly-linked list containing type and location info.
> - * Actual type can be retrieved using dwarf_offdie() with 'die_off' later.
> + * Actual type can be retrieved using die_get_type_die() with 'die_off',
> + * 'die_tag' and 'from_alt' later.
>   *
>   * Callers should free @var_types.
>   */
> @@ -1741,6 +1816,8 @@ static int __die_collect_global_vars_cb(Dwarf_Die *die_mem, void *arg)
>  		return DIE_FIND_CB_END;
>  
>  	vt->die_off = dwarf_dieoffset(&type_die);
> +	vt->die_tag = dwarf_tag(&type_die);
> +	vt->from_alt = !die_same_file(die_mem, &type_die);
>  	vt->addr = ops->number;
>  	vt->end = 0;
>  	vt->has_range = false;
> @@ -1752,6 +1829,55 @@ static int __die_collect_global_vars_cb(Dwarf_Die *die_mem, void *arg)
>  	return DIE_FIND_CB_SIBLING;
>  }
>  
> +/**
> + * die_get_type_die - Get a type DIE saved by die_collect_vars()
> + * @dbg: the main debug info
> + * @die_off: offset of the type DIE, from dwarf_dieoffset()
> + * @die_tag: tag that DIE had when the offset was saved
> + * @from_alt: whether the type DIE is in the dwz alt file
> + * @die_mem: where to store the resulting DIE
> + *
> + * See the comment in util/dwarf-aux.h: the offset is only meaningful in the
> + * file the DIE was in, which can be the dwz common file, so resolve it in
> + * the file @from_alt says it was in, the main file or its alt file, and use
> + * the DIE only when it has the @die_tag it had when the offset was saved.
> + * There is deliberately no fallback to the other file: resolving an alt
> + * file offset in the main file does not fail, it parses whatever is there
> + * as a DIE, and that is what hung 'perf report -s type'.
> + */
> +Dwarf_Die *die_get_type_die(Dwarf *dbg, u64 die_off, int die_tag, bool from_alt,
> +			    Dwarf_Die *die_mem)
> +{
> +	Dwarf *target = dbg;
> +	Dwarf_Die die;
> +
> +	if (from_alt) {
> +		/*
> +		 * Deliberately no fallback to the main file when there is
> +		 * no alt file, or when the offset does not resolve in it:
> +		 * resolving an alt file offset in the main file does not
> +		 * fail, it parses whatever is there as a DIE, and that is
> +		 * what hung 'perf report -s type', see the comment in
> +		 * util/dwarf-aux.h.
> +		 */
> +		target = dwarf_getalt(dbg);
> +		if (target == NULL) {
> +			pr_debug("DWARF: no alt (dwz) debug file to resolve the type DIE at offset 0x%lx in\n",
> +				 (unsigned long)die_off);
> +			return NULL;
> +		}
> +	}
> +
> +	if (dwarf_offdie(target, die_off, &die) && dwarf_tag(&die) == die_tag) {
> +		*die_mem = die;
> +		return die_mem;
> +	}
> +
> +	pr_debug("DWARF: no DIE with tag %d at offset 0x%lx in the %s debug file\n",
> +		 die_tag, (unsigned long)die_off, from_alt ? "alt" : "main");
> +	return NULL;
> +}
> +
>  /**
>   * die_collect_global_vars - Save all global variables
>   * @cu_die: a CU DIE
> @@ -1759,7 +1885,8 @@ static int __die_collect_global_vars_cb(Dwarf_Die *die_mem, void *arg)
>   *
>   * Save all global variables in the @cu_die and save them to @var_types.
>   * The @var_types is a singly-linked list containing type and location info.
> - * Actual type can be retrieved using dwarf_offdie() with 'die_off' later.
> + * Actual type can be retrieved using die_get_type_die() with 'die_off',
> + * 'die_tag' and 'from_alt' later.
>   *
>   * Callers should free @var_types.
>   */
> diff --git a/tools/perf/util/dwarf-aux.h b/tools/perf/util/dwarf-aux.h
> index 161f0bf980b6ee6a..149e0cbf63ccc82a 100644
> --- a/tools/perf/util/dwarf-aux.h
> +++ b/tools/perf/util/dwarf-aux.h
> @@ -152,6 +152,8 @@ int die_get_scopes(Dwarf_Die *cu_die, Dwarf_Addr pc, Dwarf_Die **scopes);
>  struct die_var_type {
>  	struct die_var_type *next;
>  	u64 die_off;
> +	int die_tag;
> +	bool from_alt;	/* die_off is relative to the alt (dwz) file */
>  	u64 addr;
>  	u64 end;        /* end address of location range */
>  	int reg;
> @@ -183,6 +185,37 @@ Dwarf_Die *die_find_variable_by_addr(Dwarf_Die *sc_die, Dwarf_Addr addr,
>  /* Save all variables and parameters in this scope */
>  void die_collect_vars(Dwarf_Die *sc_die, struct die_var_type **var_types);
>  
> +/*
> + * Get the type DIE saved by die_collect_vars()/die_collect_global_vars().
> + *
> + * The offsets those save are the dwarf_dieoffset() of the type DIE, which is
> + * relative to the debug file that DIE lives in: the dwz common file, the alt
> + * file in libdw terms, for the types shared by more than one CU, the main
> + * file for the rest.  Resolving an alt file offset in the main file does not
> + * fail: dwarf_offdie() parses whatever is at that offset there, and an offset
> + * that is a CU header in the main file reads back as a typedef whose
> + * DW_AT_type refers to itself, which is what hung 'perf report -s type' on
> + * the dwz compressed debug info of zlib-ng (libz.so.1).
> + *
> + * So @from_alt, recorded when the offset was saved, says which of the two
> + * files to resolve it in.  It is not inferred from the DIE contents: dwz
> + * encodes references into the common file as DW_FORM_GNU_ref_alt, so
> + * elfutils resolves them into the alt Dwarf and the CU of the type DIE then
> + * belongs to that other file, which die_same_file() compares exactly.
> + *
> + * There is deliberately no fallback to the other file when the offset does
> + * not resolve: that fallback is the misparse above.
> + *
> + * @die_tag is then only a sanity check: the offset is of a DIE that had this
> + * tag when it was saved, so a mismatch means the debug info changed under us,
> + * or is broken, and giving up on the type is the right answer.
> + */
> +Dwarf_Die *die_get_type_die(Dwarf *dbg, u64 die_off, int die_tag, bool from_alt,
> +			    Dwarf_Die *die_mem);
> +
> +/* Whether two DIEs live in the same debug file */
> +bool die_same_file(Dwarf_Die *die_a, Dwarf_Die *die_b);
> +
>  /* Save all global variables in this CU */
>  void die_collect_global_vars(Dwarf_Die *cu_die, struct die_var_type **var_types);
>  
> -- 
> 2.55.0
> 

  reply	other threads:[~2026-09-16 21:44 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-16 11:47 [PATCH v6 0/12] perf tools: Annotate fixes, stdio progress indication, debuginfo-client in more places Arnaldo Carvalho de Melo
2026-09-16 11:47 ` [PATCH 01/12] perf test: Skip data_type_profiling when the PMU cannot record memory events Arnaldo Carvalho de Melo
2026-09-16 11:47 ` [PATCH 02/12] perf debuginfo: Fetch debuginfo keyed by build ID using debuginfod Arnaldo Carvalho de Melo
2026-09-16 17:59   ` Ian Rogers
2026-09-16 19:02     ` Arnaldo Carvalho de Melo
2026-09-16 21:28       ` Ian Rogers
2026-09-16 18:42   ` Namhyung Kim
2026-09-16 11:47 ` [PATCH 03/12] perf config: Move perf_config__set_variable() to util/config.c Arnaldo Carvalho de Melo
2026-09-16 11:47 ` [PATCH 04/12] perf debuginfo: Let the user skip and disable debuginfod fetches Arnaldo Carvalho de Melo
2026-09-16 18:53   ` Namhyung Kim
2026-09-16 21:27     ` Arnaldo Carvalho de Melo
2026-09-16 11:47 ` [PATCH 05/12] perf debuginfo: Show the debuginfod fetch progress and keys in the TUI Arnaldo Carvalho de Melo
2026-09-16 11:47 ` [PATCH 06/12] perf symbol: Fall back to fetching the vmlinux by build ID Arnaldo Carvalho de Melo
2026-09-16 11:47 ` [PATCH 07/12] perf annotate-data: Show the sample count in the data-type browser Arnaldo Carvalho de Melo
2026-09-16 21:28   ` Namhyung Kim
2026-09-16 11:47 ` [PATCH 08/12] perf report: Add --progress option Arnaldo Carvalho de Melo
2026-09-16 11:47 ` [PATCH 09/12] perf scripts: Add perf-stuck, to tell where a running perf is stuck Arnaldo Carvalho de Melo
2026-09-16 11:47 ` [PATCH 10/12] perf annotate-data: Resolve type DIEs in the debug file they came from Arnaldo Carvalho de Melo
2026-09-16 21:44   ` Namhyung Kim [this message]
2026-09-16 11:47 ` [PATCH 11/12] perf mem record: Request PERF_SAMPLE_CPU by default Arnaldo Carvalho de Melo
2026-09-16 21:50   ` Namhyung Kim
2026-09-16 11:47 ` [PATCH 12/12] perf mem record: Use the IBS swfilt filter when available Arnaldo Carvalho de Melo
2026-09-16 21:59   ` Namhyung Kim
2026-09-16 22:27 ` [PATCH v6 0/12] perf tools: Annotate fixes, stdio progress indication, debuginfo-client in more places Namhyung Kim
2026-09-16 18:32 Arnaldo Carvalho de Melo
2026-09-16 18:32 ` [PATCH 10/12] perf annotate-data: Resolve type DIEs in the debug file they came from Arnaldo Carvalho de Melo

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=aqsN1si6jjWrocIQ@google.com \
    --to=namhyung@kernel.org \
    --cc=acme@kernel.org \
    --cc=acme@redhat.com \
    --cc=adrian.hunter@intel.com \
    --cc=irogers@google.com \
    --cc=james.clark@linaro.org \
    --cc=jolsa@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-perf-users@vger.kernel.org \
    --cc=mingo@kernel.org \
    --cc=tglx@linutronix.de \
    --cc=williams@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®