perf dwarf-aux: Bound the type chases for broken debug info

A DIE parsed at an offset that is not the start of a DIE can have a
DW_AT_type that refers back to itself, making the typedef/qualifier
chases in die_get_real_type(), die_get_pointer_type() and
die_get_typename_from_type() spin forever; 'perf report -s type' did
that on the dwz compressed debug info of zlib-ng (libz.so.1).

No sane typedef/qualifier chain is 32 DIEs long, so give up on the type
with a pr_debug instead of hanging.

Reviewed-by: Ian Rogers <irogers@google.com>
Reviewed-by: Namhyung Kim <namhyung@kernel.org>
Cc: Masami Hiramatsu <mhiramat@kernel.org>
Assisted-by: LLM
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
Arnaldo Carvalho de Melo 2026-09-24 16:27:56 -03:00
parent 906af9e1c9
commit a6ac1e9ca9

View File

@ -267,16 +267,27 @@ Dwarf_Die *die_get_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
return NULL;
}
/*
* A DIE parsed at an offset that is not the start of a DIE can refer
* back to itself via DW_AT_type: bound these chases.
*/
#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 ||
@ -297,8 +308,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;
@ -315,7 +333,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);
@ -325,6 +343,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);
@ -1119,17 +1142,25 @@ 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 follows DW_AT_type, so a self-referring DIE makes this
* recurse forever: bound it like the chases above.
*/
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 = "";
@ -1156,7 +1187,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)
@ -1167,6 +1203,19 @@ 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 a type DIE
* @type_die: a type DIE
* @buf: a strbuf for the result type name
*
* Stores the typedef name if possible, "*(function_type)" for function
* pointers. Returns 0 or -ENOENT.
*/
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
@ -1179,12 +1228,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);
}
/**