public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 0/6] perf DWARF: Fix libdw API contract violations and crashes
@ 2026-05-03  0:35 Ian Rogers
  2026-05-03  0:35 ` [PATCH v3 1/6] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at Ian Rogers
                   ` (6 more replies)
  0 siblings, 7 replies; 31+ messages in thread
From: Ian Rogers @ 2026-05-03  0:35 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

This patch series addresses a number of issues related to improper or
incomplete error handling when interacting with the `libdw` and
`libdwfl` APIs in the `perf` DWARF processing code.
                                                                                                 
The first patch fixes a real segmentation fault observed in `perf
kmem` (via `dwarf_child` and `dwarf_diename`) caused by
`dwarf_getfuncs` returning `-1` on error, which was not caught by the
previous `if (!dwarf_getfuncs(...))` check. This allowed uninitialized
stack memory to be returned and later dereferenced.
  
Following this discovery, a comprehensive audit of `libdw` API usage
was performed across `tools/perf/`. The remaining patches proactively
fix 28 additional instances of ignored return values or unchecked
pointers that could lead to similar uninitialized memory propagation,
crashes (e.g., via `strdup(NULL)` or `strcmp(NULL)`), or silent logic
failures.

v3:
Minor formatting fixes.

v2:
https://lore.kernel.org/lkml/20260502155656.478642-1-irogers@google.com/

v1:
https://lore.kernel.org/linux-perf-users/20260502064839.282422-1-irogers@google.com/

Ian Rogers (6):
  perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at
  perf dwarf-aux: Fix libdw API contract violations
  perf libdw: Fix libdw API contract violations
  perf probe-finder: Fix libdw API contract violations
  perf annotate-data: Fix libdw API contract violations
  perf debuginfo: Fix libdw API contract violations

 tools/perf/util/annotate-data.c | 17 ++++---
 tools/perf/util/debuginfo.c     |  5 +-
 tools/perf/util/dwarf-aux.c     | 37 ++++++++------
 tools/perf/util/libdw.c         | 24 +++++++---
 tools/perf/util/probe-finder.c  | 85 ++++++++++++++++++++++-----------
 5 files changed, 109 insertions(+), 59 deletions(-)

-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [PATCH v3 1/6] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at
  2026-05-03  0:35 [PATCH v3 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
@ 2026-05-03  0:35 ` Ian Rogers
  2026-05-03  0:35 ` [PATCH v3 2/6] perf dwarf-aux: Fix libdw API contract violations Ian Rogers
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 31+ messages in thread
From: Ian Rogers @ 2026-05-03  0:35 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

A segmentation fault was observed in `libdw` when running `perf kmem`
with `--page stat` on some workloads. The crash occurred deep inside
`libdw` (specifically in `dwarf_child` and `dwarf_diename`) when
processing DWARF information.

The root cause was improper error handling of `dwarf_getfuncs` in
`die_find_realfunc` and `die_find_tailfunc`.

`dwarf_getfuncs` returns:
 - `0` on success (when all functions have been processed).
 - A positive offset if the callback aborts early (e.g., via
   `DWARF_CB_ABORT` when a match is found).
 - `-1` on error.

The original code used `if (!dwarf_getfuncs(...)) return NULL;`. On
error (`-1`), `!-1` evaluates to `0` (false), bypassing the error
check. Execution then proceeded as if a match was found, returning
uninitialized stack memory (`die_mem`) to the caller
(`cu_walk_functions_at`). When `cu_walk_functions_at` passed this
uninitialized memory to `libdw` via `dwarf_diename`, it caused a
segmentation fault.

Fix this by correcting the error check to `if (dwarf_getfuncs(...) <= 0)`.

Fixes: e0d153c69040 ("perf-probe: Move dwarf library routines to dwarf-aux.{c, h}")
Fixes: d4c537e6bf86 ("perf probe: Ignore tail calls to probed functions")
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/dwarf-aux.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index 92db2fccc788..8e83cb2d565e 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -171,7 +171,6 @@ int cu_walk_functions_at(Dwarf_Die *cu_die, Dwarf_Addr addr,
 	}
 
 	return ret;
-
 }
 
 /**
@@ -620,7 +619,7 @@ Dwarf_Die *die_find_tailfunc(Dwarf_Die *cu_die, Dwarf_Addr addr,
 	ad.addr = addr;
 	ad.die_mem = die_mem;
 	/* dwarf_getscopes can't find subprogram. */
-	if (!dwarf_getfuncs(cu_die, __die_search_func_tail_cb, &ad, 0))
+	if (dwarf_getfuncs(cu_die, __die_search_func_tail_cb, &ad, 0) <= 0)
 		return NULL;
 	else
 		return die_mem;
@@ -647,6 +646,7 @@ static int __die_search_func_cb(Dwarf_Die *fn_die, void *data)
  * die_find_realfunc - Search a non-inlined function at given address
  * @cu_die: a CU DIE which including @addr
  * @addr: target address
+ * @dbg: Dwarf session
  * @die_mem: a buffer for result DIE
  *
  * Search a non-inlined function DIE which includes @addr. Stores the
@@ -659,7 +659,7 @@ Dwarf_Die *die_find_realfunc(Dwarf_Die *cu_die, Dwarf_Addr addr,
 	ad.addr = addr;
 	ad.die_mem = die_mem;
 	/* dwarf_getscopes can't find subprogram. */
-	if (!dwarf_getfuncs(cu_die, __die_search_func_cb, &ad, 0))
+	if (dwarf_getfuncs(cu_die, __die_search_func_cb, &ad, 0) <= 0)
 		return NULL;
 	else
 		return die_mem;
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v3 2/6] perf dwarf-aux: Fix libdw API contract violations
  2026-05-03  0:35 [PATCH v3 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
  2026-05-03  0:35 ` [PATCH v3 1/6] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at Ian Rogers
@ 2026-05-03  0:35 ` Ian Rogers
  2026-05-03  0:35 ` [PATCH v3 3/6] perf libdw: " Ian Rogers
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 31+ messages in thread
From: Ian Rogers @ 2026-05-03  0:35 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Check return values of `dwarf_decl_line`, `dwarf_getfuncs`, and
`dwarf_lineaddr` to prevent using uninitialized stack variables or
incorrectly reporting success on failure.

Fixes: 57f95bf5f882 ("perf probe: Show correct statement line number by perf probe -l")
Fixes: 3f4460a28fb2 ("perf probe: Filter out redundant inline-instances")
Fixes: 75186a9b09e4 ("perf probe: Fix to show lines of sys_ functions correctly")
Fixes: e0d153c69040 ("perf-probe: Move dwarf library routines to dwarf-aux.{c, h}")
Fixes: 6243b9dc4c99 ("perf probe: Move dwarf specific functions to dwarf-aux.c")
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/dwarf-aux.c | 31 +++++++++++++++++++------------
 1 file changed, 19 insertions(+), 12 deletions(-)

diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index 8e83cb2d565e..1168e69a9292 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -125,7 +125,8 @@ int cu_find_lineinfo(Dwarf_Die *cu_die, Dwarf_Addr addr,
 	    && die_entrypc(&die_mem, &faddr) == 0 &&
 	    faddr == addr) {
 		*fname = die_get_decl_file(&die_mem);
-		dwarf_decl_line(&die_mem, lineno);
+		if (dwarf_decl_line(&die_mem, lineno) != 0)
+			return -ENOENT;
 		goto out;
 	}
 
@@ -796,8 +797,7 @@ static int __die_walk_instances_cb(Dwarf_Die *inst, void *data)
 
 	/* Ignore redundant instances */
 	if (dwarf_tag(inst) == DW_TAG_inlined_subroutine) {
-		dwarf_decl_line(origin, &tmp);
-		if (die_get_call_lineno(inst) == tmp) {
+		if (dwarf_decl_line(origin, &tmp) == 0 && die_get_call_lineno(inst) == tmp) {
 			tmp = die_get_decl_fileno(origin);
 			if (die_get_call_fileno(inst) == tmp)
 				return DIE_FIND_CB_CONTINUE;
@@ -949,7 +949,11 @@ int die_walk_lines(Dwarf_Die *rt_die, line_walk_callback_t callback, void *data)
 	/* Get the CU die */
 	if (dwarf_tag(rt_die) != DW_TAG_compile_unit) {
 		cu_die = dwarf_diecu(rt_die, &die_mem, NULL, NULL);
-		dwarf_decl_line(rt_die, &decl);
+		if (dwarf_decl_line(rt_die, &decl) != 0) {
+			pr_debug2("Failed to get the declared line number of %s\n",
+				  dwarf_diename(rt_die));
+			return -EINVAL;
+		}
 		decf = die_get_decl_file(rt_die);
 		if (!decf) {
 			pr_debug2("Failed to get the declared file name of %s\n",
@@ -1003,8 +1007,7 @@ int die_walk_lines(Dwarf_Die *rt_die, line_walk_callback_t callback, void *data)
 				    die_get_call_lineno(&die_mem) == lineno)
 					goto found;
 
-				dwarf_decl_line(&die_mem, &inl);
-				if (inl != decl ||
+				if (dwarf_decl_line(&die_mem, &inl) != 0 || inl != decl ||
 				    decf != die_get_decl_file(&die_mem))
 					continue;
 			}
@@ -1035,8 +1038,10 @@ int die_walk_lines(Dwarf_Die *rt_die, line_walk_callback_t callback, void *data)
 			.data = data,
 			.retval = 0,
 		};
-		dwarf_getfuncs(cu_die, __die_walk_culines_cb, &param, 0);
-		ret = param.retval;
+		if (dwarf_getfuncs(cu_die, __die_walk_culines_cb, &param, 0) < 0)
+			ret = -EINVAL;
+		else
+			ret = param.retval;
 	}
 
 	return ret;
@@ -1940,10 +1945,12 @@ static bool die_get_postprologue_addr(unsigned long entrypc_idx,
 			break;
 	}
 
-	dwarf_lineaddr(line, postprologue_addr);
-	if (*postprologue_addr >= highpc)
-		dwarf_lineaddr(dwarf_onesrcline(lines, i - 1),
-			       postprologue_addr);
+	if (dwarf_lineaddr(line, postprologue_addr) != 0)
+		return false;
+	if (*postprologue_addr >= highpc) {
+		if (dwarf_lineaddr(dwarf_onesrcline(lines, i - 1), postprologue_addr) != 0)
+			return false;
+	}
 
 	return true;
 }
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v3 3/6] perf libdw: Fix libdw API contract violations
  2026-05-03  0:35 [PATCH v3 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
  2026-05-03  0:35 ` [PATCH v3 1/6] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at Ian Rogers
  2026-05-03  0:35 ` [PATCH v3 2/6] perf dwarf-aux: Fix libdw API contract violations Ian Rogers
@ 2026-05-03  0:35 ` Ian Rogers
  2026-05-03  0:35 ` [PATCH v3 4/6] perf probe-finder: " Ian Rogers
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 31+ messages in thread
From: Ian Rogers @ 2026-05-03  0:35 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Check return values of `dwfl_report_end` and `dwfl_module_addrdie`.
Validate `dwarf_diename` result before passing to `new_inline_sym` (avoid
potential `strdup(NULL)` crash) and check `die_get_call_lineno` for
errors.

Fixes: b7a2b011e962 ("perf powerpc: Unify the skip-callchain-idx libdw with that for addr2line")
Fixes: 88c51002d06f ("perf addr2line: Add a libdw implementation")
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/libdw.c | 24 +++++++++++++++++-------
 1 file changed, 17 insertions(+), 7 deletions(-)

diff --git a/tools/perf/util/libdw.c b/tools/perf/util/libdw.c
index 216977884103..e37f3b22699d 100644
--- a/tools/perf/util/libdw.c
+++ b/tools/perf/util/libdw.c
@@ -60,7 +60,11 @@ struct Dwfl *dso__libdw_dwfl(struct dso *dso)
 		return NULL;
 	}
 
-	dwfl_report_end(dwfl, /*removed=*/NULL, /*arg=*/NULL);
+	if (dwfl_report_end(dwfl, NULL, NULL) != 0) {
+		dwfl_end(dwfl);
+		return NULL;
+	}
+
 	dso__set_libdw(dso, dwfl);
 
 	return dwfl;
@@ -72,22 +76,27 @@ struct libdw_a2l_cb_args {
 	struct inline_node *node;
 	char *leaf_srcline;
 	bool leaf_srcline_used;
+	int err;
 };
 
 static int libdw_a2l_cb(Dwarf_Die *die, void *_args)
 {
 	struct libdw_a2l_cb_args *args  = _args;
-	struct symbol *inline_sym = new_inline_sym(args->dso, args->sym, dwarf_diename(die));
+	const char *name = dwarf_diename(die);
+	struct symbol *inline_sym = new_inline_sym(args->dso, args->sym, name ?: "unknown");
 	const char *call_fname = die_get_call_file(die);
+	int call_lineno = die_get_call_lineno(die);
 	char *call_srcline = srcline__unknown;
 	struct inline_list *ilist;
 
-	if (!inline_sym)
-		return -ENOMEM;
+	if (!inline_sym) {
+		args->err = -ENOMEM;
+		return DWARF_CB_ABORT;
+	}
 
 	/* Assign caller information to the parent. */
-	if (call_fname)
-		call_srcline = srcline_from_fileline(call_fname, die_get_call_lineno(die));
+	if (call_fname && call_lineno > 0)
+		call_srcline = srcline_from_fileline(call_fname, call_lineno);
 
 	list_for_each_entry(ilist, &args->node->val, list) {
 		if (args->leaf_srcline == ilist->srcline)
@@ -163,7 +172,8 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr,
 		};
 
 		/* Walk from the parent down to the leaf. */
-		cu_walk_functions_at(cudie, addr, libdw_a2l_cb, &args);
+		if (cudie)
+			cu_walk_functions_at(cudie, addr, libdw_a2l_cb, &args);
 
 		if (!args.leaf_srcline_used)
 			free(args.leaf_srcline);
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v3 4/6] perf probe-finder: Fix libdw API contract violations
  2026-05-03  0:35 [PATCH v3 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
                   ` (2 preceding siblings ...)
  2026-05-03  0:35 ` [PATCH v3 3/6] perf libdw: " Ian Rogers
@ 2026-05-03  0:35 ` Ian Rogers
  2026-05-03  0:35 ` [PATCH v3 5/6] perf annotate-data: " Ian Rogers
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 31+ messages in thread
From: Ian Rogers @ 2026-05-03  0:35 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Check return values of `dwarf_formsdata`, `dwarf_entrypc`,
`dwarf_highpc`, `dwarf_bytesize`, `dwarf_attr`, `dwarf_decl_line`,
`dwarf_getfuncs`, and `dwarf_formref_die`. Validate `dwarf_diename` and
`dwarf_diecu` results to prevent potential crashes. Fix C90 mixed
declarations.

Fixes: 66f69b219716 ("perf probe: Support DW_AT_const_value constant value")
Fixes: 3d918a12a1b3 ("perf probe: Find fentry mcount fuzzed parameter location")
Fixes: bcfc082150c6 ("perf probe: Remove redundant dwarf functions")
Fixes: 221d061182b8 ("perf probe: Fix to search local variables in appropriate scope")
Fixes: b55a87ade383 ("perf probe: Remove die() from probe-finder code")
Fixes: 4c859351226c ("perf probe: Support glob wildcards for function name")
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/probe-finder.c | 85 ++++++++++++++++++++++------------
 1 file changed, 56 insertions(+), 29 deletions(-)

diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c
index 64328abeef8b..bdef340dfd55 100644
--- a/tools/perf/util/probe-finder.c
+++ b/tools/perf/util/probe-finder.c
@@ -93,7 +93,8 @@ static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr,
 		if (!tvar)
 			return 0;
 
-		dwarf_formsdata(&attr, &snum);
+		if (dwarf_formsdata(&attr, &snum) != 0)
+			return -ENOENT;
 		ret = asprintf(&tvar->value, "\\%ld", (long)snum);
 
 		return ret < 0 ? -ENOMEM : 0;
@@ -103,8 +104,7 @@ static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr,
 	if (dwarf_attr(vr_die, DW_AT_location, &attr) == NULL)
 		return -EINVAL;	/* Broken DIE ? */
 	if (dwarf_getlocation_addr(&attr, addr, &op, &nops, 1) <= 0) {
-		ret = dwarf_entrypc(sp_die, &tmp);
-		if (ret)
+		if (dwarf_entrypc(sp_die, &tmp) != 0)
 			return -ENOENT;
 
 		if (probe_conf.show_location_range &&
@@ -115,8 +115,7 @@ static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr,
 			return -ENOENT;
 		}
 
-		ret = dwarf_highpc(sp_die, &tmp);
-		if (ret)
+		if (dwarf_highpc(sp_die, &tmp) != 0)
 			return -ENOENT;
 		/*
 		 * This is fuzzed by fentry mcount. We try to find the
@@ -138,15 +137,21 @@ static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr,
 static_var:
 		if (!tvar)
 			return ret2;
-		/* Static variables on memory (not stack), make @varname */
-		ret = strlen(dwarf_diename(vr_die));
-		tvar->value = zalloc(ret + 2);
-		if (tvar->value == NULL)
-			return -ENOMEM;
-		snprintf(tvar->value, ret + 2, "@%s", dwarf_diename(vr_die));
-		tvar->ref = alloc_trace_arg_ref((long)offs);
-		if (tvar->ref == NULL)
-			return -ENOMEM;
+		{
+			/* Static variables on memory (not stack), make @varname */
+			const char *name = dwarf_diename(vr_die);
+
+			if (!name)
+				return -ENOENT;
+			ret = strlen(name);
+			tvar->value = zalloc(ret + 2);
+			if (tvar->value == NULL)
+				return -ENOMEM;
+			snprintf(tvar->value, ret + 2, "@%s", name);
+			tvar->ref = alloc_trace_arg_ref((long)offs);
+			if (tvar->ref == NULL)
+				return -ENOMEM;
+		}
 		return ret2;
 	}
 
@@ -234,8 +239,8 @@ static int convert_variable_type(Dwarf_Die *vr_die,
 	}
 
 	if (die_get_real_type(vr_die, &type) == NULL) {
-		pr_warning("Failed to get a type information of %s.\n",
-			   dwarf_diename(vr_die));
+		const char *name = dwarf_diename(vr_die);
+		pr_warning("Failed to get a type information of %s.\n", name ? name : "<unknown>");
 		return -ENOENT;
 	}
 
@@ -291,7 +296,7 @@ static int convert_variable_type(Dwarf_Die *vr_die,
 			 probe_type_is_available(PROBE_TYPE_X) ? 'x' : 'u';
 
 	ret = dwarf_bytesize(&type);
-	if (ret <= 0)
+	if (ret < 0)
 		/* No size ... try to use default type */
 		return 0;
 	ret = BYTES_TO_BITS(ret);
@@ -357,7 +362,13 @@ static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname,
 			else
 				*ref_ptr = ref;
 		}
-		ref->offset += dwarf_bytesize(&type) * field->index;
+		{
+			int bsize = dwarf_bytesize(&type);
+
+			if (bsize < 0)
+				return -EINVAL;
+			ref->offset += bsize * field->index;
+		}
 		ref->user_access = user_access;
 		goto next;
 	} else if (tag == DW_TAG_pointer_type) {
@@ -611,10 +622,16 @@ static int call_probe_finder(Dwarf_Die *sc_die, struct probe_finder *pf)
 		memcpy(&pf->sp_die, sc_die, sizeof(Dwarf_Die));
 
 	/* Get the frame base attribute/ops from subprogram */
-	dwarf_attr(&pf->sp_die, DW_AT_frame_base, &fb_attr);
-	ret = dwarf_getlocation_addr(&fb_attr, pf->addr, &pf->fb_ops, &nops, 1);
-	if (ret <= 0 || nops == 0) {
+	if (dwarf_attr(&pf->sp_die, DW_AT_frame_base, &fb_attr) == NULL) {
 		pf->fb_ops = NULL;
+	} else {
+		ret = dwarf_getlocation_addr(&fb_attr, pf->addr, &pf->fb_ops, &nops, 1);
+		if (ret <= 0 || nops == 0)
+			pf->fb_ops = NULL;
+	}
+
+	if (pf->fb_ops == NULL) {
+		/* Not supported */
 	} else if (nops == 1 && pf->fb_ops[0].atom == DW_OP_call_frame_cfa &&
 		   (pf->cfi_eh != NULL || pf->cfi_dbg != NULL)) {
 		if ((dwarf_cfi_addrframe(pf->cfi_eh, pf->addr, &frame) != 0 &&
@@ -667,8 +684,8 @@ static int find_best_scope_cb(Dwarf_Die *fn_die, void *data)
 		}
 	} else {
 		/* With the line number, find the nearest declared DIE */
-		dwarf_decl_line(fn_die, &lno);
-		if (lno < fsp->line && fsp->diff > fsp->line - lno) {
+		if (dwarf_decl_line(fn_die, &lno) == 0 && lno < fsp->line &&
+		    fsp->diff > fsp->line - lno) {
 			/* Keep a candidate and continue */
 			fsp->diff = fsp->line - lno;
 			memcpy(fsp->die_mem, fn_die, sizeof(Dwarf_Die));
@@ -1018,7 +1035,8 @@ static int find_probe_point_by_func(struct probe_finder *pf)
 {
 	struct dwarf_callback_param _param = {.data = (void *)pf,
 					      .retval = 0};
-	dwarf_getfuncs(&pf->cu_die, probe_point_search_cb, &_param, 0);
+	if (dwarf_getfuncs(&pf->cu_die, probe_point_search_cb, &_param, 0) < 0)
+		return -ENOENT;
 	return _param.retval;
 }
 
@@ -1207,7 +1225,8 @@ static int copy_variables_cb(Dwarf_Die *die_mem, void *data)
 		 * points to correct die.
 		 */
 		if (dwarf_attr(die_mem, DW_AT_abstract_origin, &attr)) {
-			dwarf_formref_die(&attr, &var_die);
+			if (dwarf_formref_die(&attr, &var_die) == NULL)
+				goto out;
 			if (pf->abstrace_dieoffset != dwarf_dieoffset(&var_die))
 				goto out;
 		}
@@ -1270,6 +1289,8 @@ static int add_probe_trace_event(Dwarf_Die *sc_die, struct probe_finder *pf)
 	struct probe_trace_event *tev;
 	struct perf_probe_arg *args = NULL;
 	int ret, i;
+	const char *realname;
+	Dwarf_Die cu_die_mem;
 
 	/*
 	 * For some reason (e.g. different column assigned to same address)
@@ -1293,13 +1314,17 @@ static int add_probe_trace_event(Dwarf_Die *sc_die, struct probe_finder *pf)
 	if (ret < 0)
 		goto end;
 
-	tev->point.realname = strdup(dwarf_diename(sc_die));
+	realname = dwarf_diename(sc_die);
+	tev->point.realname = strdup(realname ?: "unknown");
 	if (!tev->point.realname) {
 		ret = -ENOMEM;
 		goto end;
 	}
 
-	tev->lang = dwarf_srclang(dwarf_diecu(sc_die, &pf->cu_die, NULL, NULL));
+	if (dwarf_diecu(sc_die, &cu_die_mem, NULL, NULL) != NULL)
+		tev->lang = dwarf_srclang(&cu_die_mem);
+	else
+		tev->lang = DW_LANG_C; // Fallback
 
 	pr_debug("Probe point found: %s+%lu\n", tev->point.symbol,
 		 tev->point.offset);
@@ -1794,7 +1819,8 @@ static int line_range_search_cb(Dwarf_Die *sp_die, void *data)
 
 	if (die_match_name(sp_die, lr->function) && die_is_func_def(sp_die)) {
 		lf->fname = die_get_decl_file(sp_die);
-		dwarf_decl_line(sp_die, &lr->offset);
+		if (dwarf_decl_line(sp_die, &lr->offset) != 0)
+			return DWARF_CB_OK; // Skip if no line info
 		pr_debug("fname: %s, lineno:%d\n", lf->fname, lr->offset);
 		lf->lno_s = lr->offset + lr->start;
 		if (lf->lno_s < 0)	/* Overflow */
@@ -1818,7 +1844,8 @@ static int line_range_search_cb(Dwarf_Die *sp_die, void *data)
 static int find_line_range_by_func(struct line_finder *lf)
 {
 	struct dwarf_callback_param param = {.data = (void *)lf, .retval = 0};
-	dwarf_getfuncs(&lf->cu_die, line_range_search_cb, &param, 0);
+	if (dwarf_getfuncs(&lf->cu_die, line_range_search_cb, &param, 0) < 0)
+		return -ENOENT;
 	return param.retval;
 }
 
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v3 5/6] perf annotate-data: Fix libdw API contract violations
  2026-05-03  0:35 [PATCH v3 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
                   ` (3 preceding siblings ...)
  2026-05-03  0:35 ` [PATCH v3 4/6] perf probe-finder: " Ian Rogers
@ 2026-05-03  0:35 ` Ian Rogers
  2026-05-03  0:35 ` [PATCH v3 6/6] perf debuginfo: " Ian Rogers
  2026-05-03 17:10 ` [PATCH v4 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
  6 siblings, 0 replies; 31+ messages in thread
From: Ian Rogers @ 2026-05-03  0:35 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Check return values of `dwarf_aggregate_size` and `dwarf_formudata`.
Validate `dwarf_diename` before `strdup` to prevent potential crashes.

Fixes: 2bc3cf575a16 ("perf annotate-data: Improve debug message with location info")
Fixes: 4a111cadac85 ("perf annotate-data: Add member field in the data type")
Fixes: 8b1042c425f6 ("perf annotate-data: Set bitfield member offset and size properly")
Fixes: fc044c53b99f ("perf annotate-data: Add dso->data_types tree")
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/annotate-data.c | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index 1eff0a27237d..e881a40a4885 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -74,7 +74,8 @@ void pr_debug_type_name(Dwarf_Die *die, enum type_state_kind kind)
 		break;
 	}
 
-	dwarf_aggregate_size(die, &size);
+	if (dwarf_aggregate_size(die, &size) != 0)
+		size = 0;
 
 	strbuf_init(&sb, 32);
 	die_get_typename_from_type(die, &sb);
@@ -250,9 +251,10 @@ static int __add_member_cb(Dwarf_Die *die, void *arg)
 	if (dwarf_aggregate_size(&die_mem, &size) < 0)
 		size = 0;
 
-	if (dwarf_attr_integrate(die, DW_AT_data_member_location, &attr))
-		dwarf_formudata(&attr, &loc);
-	else {
+	if (dwarf_attr_integrate(die, DW_AT_data_member_location, &attr)) {
+		if (dwarf_formudata(&attr, &loc) != 0)
+			loc = 0;
+	} else {
 		/* bitfield member */
 		if (dwarf_attr_integrate(die, DW_AT_data_bit_offset, &attr) &&
 		    dwarf_formudata(&attr, &loc) == 0)
@@ -273,7 +275,9 @@ static int __add_member_cb(Dwarf_Die *die, void *arg)
 				     dwarf_diename(die), (long)bit_size) < 0)
 				member->var_name = NULL;
 		} else {
-			member->var_name = strdup(dwarf_diename(die));
+			const char *name = dwarf_diename(die);
+
+			member->var_name = strdup(name ?: "unknown");
 		}
 
 		if (member->var_name == NULL) {
@@ -370,7 +374,8 @@ static struct annotated_data_type *dso__findnew_data_type(struct dso *dso,
 	if (dwarf_tag(type_die) == DW_TAG_typedef)
 		die_get_real_type(type_die, type_die);
 
-	dwarf_aggregate_size(type_die, &size);
+	if (dwarf_aggregate_size(type_die, &size) != 0)
+		size = 0;
 
 	/* Check existing nodes in dso->data_types tree */
 	key.self.type_name = type_name;
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v3 6/6] perf debuginfo: Fix libdw API contract violations
  2026-05-03  0:35 [PATCH v3 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
                   ` (4 preceding siblings ...)
  2026-05-03  0:35 ` [PATCH v3 5/6] perf annotate-data: " Ian Rogers
@ 2026-05-03  0:35 ` Ian Rogers
  2026-05-03 17:10 ` [PATCH v4 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
  6 siblings, 0 replies; 31+ messages in thread
From: Ian Rogers @ 2026-05-03  0:35 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Check return value of `dwfl_report_end` during offline initialization.
Validate `dwfl_module_relocation_info` result before passing to `strcmp`
to avoid potential segmentation faults.

Fixes: 6f1b6291cf73 ("perf tools: Add util/debuginfo.[ch] files")
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/debuginfo.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/tools/perf/util/debuginfo.c b/tools/perf/util/debuginfo.c
index 0e35c13abd04..49cb7f9b715d 100644
--- a/tools/perf/util/debuginfo.c
+++ b/tools/perf/util/debuginfo.c
@@ -62,7 +62,8 @@ static int debuginfo__init_offline_dwarf(struct debuginfo *dbg,
 
 	dwfl_module_build_id(dbg->mod, &dbg->build_id, &dummy);
 
-	dwfl_report_end(dbg->dwfl, NULL, NULL);
+	if (dwfl_report_end(dbg->dwfl, NULL, NULL) != 0)
+		goto error;
 
 	return 0;
 error:
@@ -167,7 +168,7 @@ int debuginfo__get_text_offset(struct debuginfo *dbg, Dwarf_Addr *offs,
 	/* Search the relocation related .text section */
 	for (i = 0; i < n; i++) {
 		p = dwfl_module_relocation_info(dbg->mod, i, &shndx);
-		if (strcmp(p, ".text") == 0) {
+		if (p && strcmp(p, ".text") == 0) {
 			/* OK, get the section header */
 			scn = elf_getscn(elf, shndx);
 			if (!scn)
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v4 0/6] perf DWARF: Fix libdw API contract violations and crashes
  2026-05-03  0:35 [PATCH v3 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
                   ` (5 preceding siblings ...)
  2026-05-03  0:35 ` [PATCH v3 6/6] perf debuginfo: " Ian Rogers
@ 2026-05-03 17:10 ` Ian Rogers
  2026-05-03 17:10   ` [PATCH v4 1/6] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at Ian Rogers
                     ` (6 more replies)
  6 siblings, 7 replies; 31+ messages in thread
From: Ian Rogers @ 2026-05-03 17:10 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

This patch series addresses a number of DWARF/libdw error-handling
bugs and contract violations, preventing several real Userspace
segmentation faults and memory/FD leaks.

In v4, the series has been thoroughly hardened based on review
feedback for v3, fixing latent bugs and improving unwinding/search
robustness:

 - **Memory/FD Leak Fixes**: Fixed file descriptor leaks in
     `debuginfo` offline init paths, and memory leaks when `strdup` or
     inlined list appends fail during callback walks.
 
 - **Callchain Robustness**: Fixed `ORDER_CALLER` update bugs to
     prevent inline callchain corruption when nest depth >
     2. Re-implemented `die_get_data_member_location` helper with
     `dwarf_attr_integrate` to safely parse location expression
     offsets for inherited properties (specifications/origins).
 
 - **Search Robustness**: Relaxed strict `dwarf_getfuncs` error aborts
     in `probe-finder` and `line-range` loops to allow skipping
     individual corrupted CUs instead of prematurely stopping searches
     entirely. Added DWARF line 0 reference support.
 
 - **Safe DWARF Printing**: Switched to the `die_name()` safe printing
     helper globally to prevent `vfprintf(NULL)` undefined
     behavior/crashes when DWARF entities (like anonymous structs or
     tail calls) lack names.

v4:
 - Localized and squashed robust error handling, memory/FD cleanup
   paths, and safe DWARF printing.

v3:
 - Minor formatting fixes.
https://lore.kernel.org/linux-perf-users/20260503003552.1063540-1-irogers@google.com/

v2:
https://lore.kernel.org/lkml/20260502155656.478642-1-irogers@google.com/

v1:
https://lore.kernel.org/linux-perf-users/20260502064839.282422-1-irogers@google.com/

Ian Rogers (6):
  perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at
  perf dwarf-aux: Fix libdw API contract violations
  perf libdw: Fix libdw API contract violations
  perf probe-finder: Fix libdw API contract violations
  perf annotate-data: Fix libdw API contract violations
  perf debuginfo: Fix libdw API contract violations

 tools/perf/util/annotate-data.c |  27 +++++---
 tools/perf/util/debuginfo.c     |   9 ++-
 tools/perf/util/dwarf-aux.c     |  39 ++++++------
 tools/perf/util/dwarf-aux.h     |   5 ++
 tools/perf/util/libdw.c         |  84 ++++++++++++++++++++-----
 tools/perf/util/probe-finder.c  | 105 +++++++++++++++++++++-----------
 tools/perf/util/srcline.c       |   9 ++-
 tools/perf/util/srcline.h       |   1 +
 8 files changed, 191 insertions(+), 88 deletions(-)

-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [PATCH v4 1/6] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at
  2026-05-03 17:10 ` [PATCH v4 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
@ 2026-05-03 17:10   ` Ian Rogers
  2026-05-03 23:33     ` Namhyung Kim
  2026-05-03 17:10   ` [PATCH v4 2/6] perf dwarf-aux: Fix libdw API contract violations Ian Rogers
                     ` (5 subsequent siblings)
  6 siblings, 1 reply; 31+ messages in thread
From: Ian Rogers @ 2026-05-03 17:10 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

A segmentation fault was observed in `libdw` when running `perf kmem`
with `--page stat` on some workloads. The crash occurred deep inside
`libdw` (specifically in `dwarf_child` and `dwarf_diename`) when
processing DWARF information.

The root cause was improper error handling of `dwarf_getfuncs` in
`die_find_realfunc` and `die_find_tailfunc`.

`dwarf_getfuncs` returns:
 - `0` on success (when all functions have been processed).
 - A positive offset if the callback aborts early (e.g., via
   `DWARF_CB_ABORT` when a match is found).
 - `-1` on error.

The original code used `if (!dwarf_getfuncs(...)) return NULL;`. On
error (`-1`), `!-1` evaluates to `0` (false), bypassing the error
check. Execution then proceeded as if a match was found, returning
uninitialized stack memory (`die_mem`) to the caller
(`cu_walk_functions_at`). When `cu_walk_functions_at` passed this
uninitialized memory to `libdw` via `dwarf_diename`, it caused a
segmentation fault.

Fix this by correcting the error check to `if (dwarf_getfuncs(...) <= 0)`.

Fixes: e0d153c69040 ("perf-probe: Move dwarf library routines to dwarf-aux.{c, h}")
Fixes: d4c537e6bf86 ("perf probe: Ignore tail calls to probed functions")
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/dwarf-aux.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index 92db2fccc788..109a166a6d19 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -171,7 +171,6 @@ int cu_walk_functions_at(Dwarf_Die *cu_die, Dwarf_Addr addr,
 	}
 
 	return ret;
-
 }
 
 /**
@@ -620,7 +619,7 @@ Dwarf_Die *die_find_tailfunc(Dwarf_Die *cu_die, Dwarf_Addr addr,
 	ad.addr = addr;
 	ad.die_mem = die_mem;
 	/* dwarf_getscopes can't find subprogram. */
-	if (!dwarf_getfuncs(cu_die, __die_search_func_tail_cb, &ad, 0))
+	if (dwarf_getfuncs(cu_die, __die_search_func_tail_cb, &ad, 0) <= 0)
 		return NULL;
 	else
 		return die_mem;
@@ -659,7 +658,7 @@ Dwarf_Die *die_find_realfunc(Dwarf_Die *cu_die, Dwarf_Addr addr,
 	ad.addr = addr;
 	ad.die_mem = die_mem;
 	/* dwarf_getscopes can't find subprogram. */
-	if (!dwarf_getfuncs(cu_die, __die_search_func_cb, &ad, 0))
+	if (dwarf_getfuncs(cu_die, __die_search_func_cb, &ad, 0) <= 0)
 		return NULL;
 	else
 		return die_mem;
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v4 2/6] perf dwarf-aux: Fix libdw API contract violations
  2026-05-03 17:10 ` [PATCH v4 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
  2026-05-03 17:10   ` [PATCH v4 1/6] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at Ian Rogers
@ 2026-05-03 17:10   ` Ian Rogers
  2026-05-03 23:36     ` Namhyung Kim
  2026-05-03 17:10   ` [PATCH v4 3/6] perf libdw: " Ian Rogers
                     ` (4 subsequent siblings)
  6 siblings, 1 reply; 31+ messages in thread
From: Ian Rogers @ 2026-05-03 17:10 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Check return values of `dwarf_decl_line` (where non-optional),
`dwarf_getfuncs`, and `dwarf_lineaddr` to prevent using uninitialized
stack variables or incorrectly reporting success on failure.

For the root DIE in `die_walk_lines()`, `dwarf_decl_line` and
`die_get_decl_file` are optional and their failures are handled
gracefully to avoid breaking line walking on valid functions.

Additionally:
 - Add NULL pointer protection for `strcmp()` in `die_walk_lines()`
   when `inf` or `decf` are NULL to prevent crashes on generated
   code.
 - Use `dwarf_attr_integrate` in `die_get_data_member_location` to
   correctly resolve inherited member locations (e.g. via abstract
   origin or specification).

Fixes: 57f95bf5f882 ("perf probe: Show correct statement line number by perf probe -l")
Fixes: 3f4460a28fb2 ("perf probe: Filter out redundant inline-instances")
Fixes: 75186a9b09e4 ("perf probe: Fix to show lines of sys_ functions correctly")
Fixes: e0d153c69040 ("perf-probe: Move dwarf library routines to dwarf-aux.{c, h}")
Fixes: 6243b9dc4c99 ("perf probe: Move dwarf specific functions to dwarf-aux.c")
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
v4:
 - Fix strcmp(NULL) risk and inherited member location fallbacks in dwarf-aux.c.
---
 tools/perf/util/dwarf-aux.c | 34 ++++++++++++++++------------------
 tools/perf/util/dwarf-aux.h |  5 +++++
 2 files changed, 21 insertions(+), 18 deletions(-)

diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index 109a166a6d19..a8ab1c30d0ac 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -125,7 +125,8 @@ int cu_find_lineinfo(Dwarf_Die *cu_die, Dwarf_Addr addr,
 	    && die_entrypc(&die_mem, &faddr) == 0 &&
 	    faddr == addr) {
 		*fname = die_get_decl_file(&die_mem);
-		dwarf_decl_line(&die_mem, lineno);
+		if (dwarf_decl_line(&die_mem, lineno) != 0)
+			return -ENOENT;
 		goto out;
 	}
 
@@ -459,7 +460,7 @@ int die_get_data_member_location(Dwarf_Die *mb_die, Dwarf_Word *offs)
 	size_t nexpr;
 	int ret;
 
-	if (dwarf_attr(mb_die, DW_AT_data_member_location, &attr) == NULL)
+	if (dwarf_attr_integrate(mb_die, DW_AT_data_member_location, &attr) == NULL)
 		return -ENOENT;
 
 	if (dwarf_formudata(&attr, offs) != 0) {
@@ -795,8 +796,7 @@ static int __die_walk_instances_cb(Dwarf_Die *inst, void *data)
 
 	/* Ignore redundant instances */
 	if (dwarf_tag(inst) == DW_TAG_inlined_subroutine) {
-		dwarf_decl_line(origin, &tmp);
-		if (die_get_call_lineno(inst) == tmp) {
+		if (dwarf_decl_line(origin, &tmp) == 0 && die_get_call_lineno(inst) == tmp) {
 			tmp = die_get_decl_fileno(origin);
 			if (die_get_call_fileno(inst) == tmp)
 				return DIE_FIND_CB_CONTINUE;
@@ -950,11 +950,6 @@ int die_walk_lines(Dwarf_Die *rt_die, line_walk_callback_t callback, void *data)
 		cu_die = dwarf_diecu(rt_die, &die_mem, NULL, NULL);
 		dwarf_decl_line(rt_die, &decl);
 		decf = die_get_decl_file(rt_die);
-		if (!decf) {
-			pr_debug2("Failed to get the declared file name of %s\n",
-				  dwarf_diename(rt_die));
-			return -EINVAL;
-		}
 	} else
 		cu_die = rt_die;
 	if (!cu_die) {
@@ -998,12 +993,11 @@ int die_walk_lines(Dwarf_Die *rt_die, line_walk_callback_t callback, void *data)
 			if (die_find_inlinefunc(rt_die, addr, &die_mem)) {
 				/* Call-site check */
 				inf = die_get_call_file(&die_mem);
-				if ((inf && !strcmp(inf, decf)) &&
+				if ((inf == decf || (inf && decf && !strcmp(inf, decf))) &&
 				    die_get_call_lineno(&die_mem) == lineno)
 					goto found;
 
-				dwarf_decl_line(&die_mem, &inl);
-				if (inl != decl ||
+				if (dwarf_decl_line(&die_mem, &inl) != 0 || inl != decl ||
 				    decf != die_get_decl_file(&die_mem))
 					continue;
 			}
@@ -1034,8 +1028,10 @@ int die_walk_lines(Dwarf_Die *rt_die, line_walk_callback_t callback, void *data)
 			.data = data,
 			.retval = 0,
 		};
-		dwarf_getfuncs(cu_die, __die_walk_culines_cb, &param, 0);
-		ret = param.retval;
+		if (dwarf_getfuncs(cu_die, __die_walk_culines_cb, &param, 0) < 0)
+			ret = -EINVAL;
+		else
+			ret = param.retval;
 	}
 
 	return ret;
@@ -1939,10 +1935,12 @@ static bool die_get_postprologue_addr(unsigned long entrypc_idx,
 			break;
 	}
 
-	dwarf_lineaddr(line, postprologue_addr);
-	if (*postprologue_addr >= highpc)
-		dwarf_lineaddr(dwarf_onesrcline(lines, i - 1),
-			       postprologue_addr);
+	if (dwarf_lineaddr(line, postprologue_addr) != 0)
+		return false;
+	if (*postprologue_addr >= highpc) {
+		if (dwarf_lineaddr(dwarf_onesrcline(lines, i - 1), postprologue_addr) != 0)
+			return false;
+	}
 
 	return true;
 }
diff --git a/tools/perf/util/dwarf-aux.h b/tools/perf/util/dwarf-aux.h
index a79968a2e573..161f0bf980b6 100644
--- a/tools/perf/util/dwarf-aux.h
+++ b/tools/perf/util/dwarf-aux.h
@@ -10,6 +10,11 @@
 #include <elfutils/libdwfl.h>
 #include <elfutils/version.h>
 
+static inline const char *die_name(Dwarf_Die *die)
+{
+	return dwarf_diename(die) ?: "<unknown>";
+}
+
 struct strbuf;
 
 /* Find the realpath of the target file */
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v4 3/6] perf libdw: Fix libdw API contract violations
  2026-05-03 17:10 ` [PATCH v4 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
  2026-05-03 17:10   ` [PATCH v4 1/6] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at Ian Rogers
  2026-05-03 17:10   ` [PATCH v4 2/6] perf dwarf-aux: Fix libdw API contract violations Ian Rogers
@ 2026-05-03 17:10   ` Ian Rogers
  2026-05-03 23:44     ` Namhyung Kim
  2026-05-03 17:10   ` [PATCH v4 4/6] perf probe-finder: " Ian Rogers
                     ` (3 subsequent siblings)
  6 siblings, 1 reply; 31+ messages in thread
From: Ian Rogers @ 2026-05-03 17:10 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Check return values of `dwfl_report_end` and `dwfl_module_addrdie`.
Check `die_get_call_lineno` for errors.

Additionally:
 - Introduce `inline_node__clear_frames()` to clean up partial
   allocations.
 - Ensure `*file` is freed and inline frames are cleared on error in
   `libdw__addr2line()` to prevent memory leaks and duplicated
   callchains when falling back to other unwinders.
 - Allow DWARF line 0 in `libdw_a2l_cb()`, as it is a valid
   reference for compiler-generated code.
 - Fix the parent srcline lookup in `libdw_a2l_cb()` to target the
   correct parent node depending on the callchain order
   (ORDER_CALLER/ORDER_CALLEE).

Fixes: b7a2b011e962 ("perf powerpc: Unify the skip-callchain-idx libdw with that for addr2line")
Fixes: 88c51002d06f ("perf addr2line: Add a libdw implementation")
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
v4:
 - Fix memory leaks and duplicated callchains on unwinding errors.
 - Support DWARF line 0 in inline list.
 - Fix callchain parent update in ORDER_CALLER mode.
---
 tools/perf/util/libdw.c   | 84 +++++++++++++++++++++++++++++++--------
 tools/perf/util/srcline.c |  9 ++++-
 tools/perf/util/srcline.h |  1 +
 3 files changed, 77 insertions(+), 17 deletions(-)

diff --git a/tools/perf/util/libdw.c b/tools/perf/util/libdw.c
index 216977884103..e69ff4cea856 100644
--- a/tools/perf/util/libdw.c
+++ b/tools/perf/util/libdw.c
@@ -4,6 +4,7 @@
 #include "srcline.h"
 #include "symbol.h"
 #include "dwarf-aux.h"
+#include "callchain.h"
 #include <fcntl.h>
 #include <unistd.h>
 #include <elfutils/libdwfl.h>
@@ -60,7 +61,11 @@ struct Dwfl *dso__libdw_dwfl(struct dso *dso)
 		return NULL;
 	}
 
-	dwfl_report_end(dwfl, /*removed=*/NULL, /*arg=*/NULL);
+	if (dwfl_report_end(dwfl, NULL, NULL) != 0) {
+		dwfl_end(dwfl);
+		return NULL;
+	}
+
 	dso__set_libdw(dso, dwfl);
 
 	return dwfl;
@@ -72,41 +77,70 @@ struct libdw_a2l_cb_args {
 	struct inline_node *node;
 	char *leaf_srcline;
 	bool leaf_srcline_used;
+	int err;
 };
 
 static int libdw_a2l_cb(Dwarf_Die *die, void *_args)
 {
 	struct libdw_a2l_cb_args *args  = _args;
-	struct symbol *inline_sym = new_inline_sym(args->dso, args->sym, dwarf_diename(die));
+	const char *name = dwarf_diename(die);
+	struct symbol *inline_sym = new_inline_sym(args->dso, args->sym, name ?: "unknown");
 	const char *call_fname = die_get_call_file(die);
+	int call_lineno = die_get_call_lineno(die);
 	char *call_srcline = srcline__unknown;
-	struct inline_list *ilist;
 
-	if (!inline_sym)
-		return -ENOMEM;
+	if (!inline_sym) {
+		args->err = -ENOMEM;
+		return DWARF_CB_ABORT;
+	}
 
 	/* Assign caller information to the parent. */
-	if (call_fname)
-		call_srcline = srcline_from_fileline(call_fname, die_get_call_lineno(die));
+	if (call_fname && call_lineno >= 0)
+		call_srcline = srcline_from_fileline(call_fname, call_lineno);
 
-	list_for_each_entry(ilist, &args->node->val, list) {
-		if (args->leaf_srcline == ilist->srcline)
+	if (!list_empty(&args->node->val)) {
+		struct inline_list *parent;
+
+		if (callchain_param.order == ORDER_CALLEE)
+			parent = list_first_entry(&args->node->val, struct inline_list, list);
+		else
+			parent = list_last_entry(&args->node->val, struct inline_list, list);
+
+		if (args->leaf_srcline == parent->srcline)
 			args->leaf_srcline_used = false;
-		else if (ilist->srcline != srcline__unknown)
-			free(ilist->srcline);
-		ilist->srcline =  call_srcline;
+		else if (parent->srcline != srcline__unknown)
+			free(parent->srcline);
+		parent->srcline = call_srcline;
 		call_srcline = NULL;
-		break;
 	}
 	if (call_srcline && call_srcline != srcline__unknown)
 		free(call_srcline);
 
 	/* Add this symbol to the chain as the leaf. */
 	if (!args->leaf_srcline_used) {
-		inline_list__append_tail(inline_sym, args->leaf_srcline, args->node);
+		if (inline_list__append_tail(inline_sym, args->leaf_srcline, args->node) != 0) {
+			args->err = -ENOMEM;
+			if (inline_sym->inlined)
+				symbol__delete(inline_sym);
+			return DWARF_CB_ABORT;
+		}
 		args->leaf_srcline_used = true;
 	} else {
-		inline_list__append_tail(inline_sym, strdup(args->leaf_srcline), args->node);
+		char *srcline = strdup(args->leaf_srcline);
+
+		if (!srcline) {
+			args->err = -ENOMEM;
+			if (inline_sym->inlined)
+				symbol__delete(inline_sym);
+			return DWARF_CB_ABORT;
+		}
+		if (inline_list__append_tail(inline_sym, srcline, args->node) != 0) {
+			free(srcline);
+			args->err = -ENOMEM;
+			if (inline_sym->inlined)
+				symbol__delete(inline_sym);
+			return DWARF_CB_ABORT;
+		}
 	}
 	return 0;
 }
@@ -162,11 +196,29 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr,
 			.leaf_srcline = srcline_from_fileline(src ?: "<unknown>", lineno),
 		};
 
+		if (!args.leaf_srcline) {
+			if (file && *file) {
+				free(*file);
+				*file = NULL;
+			}
+			return 0;
+		}
+
 		/* Walk from the parent down to the leaf. */
-		cu_walk_functions_at(cudie, addr, libdw_a2l_cb, &args);
+		if (cudie)
+			cu_walk_functions_at(cudie, addr, libdw_a2l_cb, &args);
 
 		if (!args.leaf_srcline_used)
 			free(args.leaf_srcline);
+
+		if (args.err) {
+			if (file && *file) {
+				free(*file);
+				*file = NULL;
+			}
+			inline_node__clear_frames(node);
+			return 0;
+		}
 	}
 	return 1;
 }
diff --git a/tools/perf/util/srcline.c b/tools/perf/util/srcline.c
index db164d258163..62884428fb5a 100644
--- a/tools/perf/util/srcline.c
+++ b/tools/perf/util/srcline.c
@@ -429,10 +429,13 @@ struct inline_node *dso__parse_addr_inlines(struct dso *dso, u64 addr,
 	return addr2inlines(dso_name, addr, dso, sym);
 }
 
-void inline_node__delete(struct inline_node *node)
+void inline_node__clear_frames(struct inline_node *node)
 {
 	struct inline_list *ilist, *tmp;
 
+	if (node == NULL)
+		return;
+
 	list_for_each_entry_safe(ilist, tmp, &node->val, list) {
 		list_del_init(&ilist->list);
 		zfree_srcline(&ilist->srcline);
@@ -441,7 +444,11 @@ void inline_node__delete(struct inline_node *node)
 			symbol__delete(ilist->symbol);
 		free(ilist);
 	}
+}
 
+void inline_node__delete(struct inline_node *node)
+{
+	inline_node__clear_frames(node);
 	free(node);
 }
 
diff --git a/tools/perf/util/srcline.h b/tools/perf/util/srcline.h
index 7c37b3bf9ce7..1018cbc886d6 100644
--- a/tools/perf/util/srcline.h
+++ b/tools/perf/util/srcline.h
@@ -47,6 +47,7 @@ struct inline_node *dso__parse_addr_inlines(struct dso *dso, u64 addr,
 					    struct symbol *sym);
 /* free resources associated to the inline node list */
 void inline_node__delete(struct inline_node *node);
+void inline_node__clear_frames(struct inline_node *node);
 
 /* insert the inline node list into the DSO, which will take ownership */
 void inlines__tree_insert(struct rb_root_cached *tree,
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v4 4/6] perf probe-finder: Fix libdw API contract violations
  2026-05-03 17:10 ` [PATCH v4 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
                     ` (2 preceding siblings ...)
  2026-05-03 17:10   ` [PATCH v4 3/6] perf libdw: " Ian Rogers
@ 2026-05-03 17:10   ` Ian Rogers
  2026-05-03 23:49     ` Namhyung Kim
  2026-05-03 17:10   ` [PATCH v4 5/6] perf annotate-data: " Ian Rogers
                     ` (2 subsequent siblings)
  6 siblings, 1 reply; 31+ messages in thread
From: Ian Rogers @ 2026-05-03 17:10 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Check return values of `dwarf_formsdata`, `dwarf_entrypc`,
`dwarf_highpc`, `dwarf_bytesize`, `dwarf_attr`, `dwarf_decl_line`,
`dwarf_getfuncs`, and `dwarf_formref_die`. Validate `dwarf_diename` and
`dwarf_diecu` results to prevent potential crashes. Fix C90 mixed
declarations.

Additionally:
 - Avoid vfprintf undefined behavior with NULL strings by using the
   `die_name()` helper for `dwarf_diename()` in `pr_*` calls,
   including when warning about tail calls.
 - Prevent NULL pointer dereference in `convert_variable_fields()`
   when processing array elements for variables in registers.
 - Fallback to offset 0 in `line_range_search_cb()` instead of
   skipping functions without `DW_AT_decl_line`.
 - Relax `dwarf_getfuncs` error checking in
   `find_probe_point_by_func()` and `find_line_range_by_func()` to
   prevent premature CU search aborts, ensuring robustness against
   corrupted CUs.

Fixes: 66f69b219716 ("perf probe: Support DW_AT_const_value constant value")
Fixes: 3d918a12a1b3 ("perf probe: Find fentry mcount fuzzed parameter location")
Fixes: bcfc082150c6 ("perf probe: Remove redundant dwarf functions")
Fixes: 221d061182b8 ("perf probe: Fix to search local variables in appropriate scope")
Fixes: b55a87ade383 ("perf probe: Remove die() from probe-finder code")
Fixes: 4c859351226c ("perf probe: Support glob wildcards for function name")
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
v4:
 - Safe DWARF name printing with die_name() to avoid NULL formatting crashes.
 - Fix NULL dereference in register variable array lookups.
 - Fix robust CU search loops by continuing on getfuncs errors.
---
 tools/perf/util/probe-finder.c | 105 +++++++++++++++++++++------------
 1 file changed, 68 insertions(+), 37 deletions(-)

diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c
index 64328abeef8b..7f9761a29c08 100644
--- a/tools/perf/util/probe-finder.c
+++ b/tools/perf/util/probe-finder.c
@@ -79,7 +79,7 @@ static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr,
 	unsigned int regn;
 	Dwarf_Word offs = 0;
 	bool ref = false;
-	const char *regs;
+	const char *regs, *name;
 	int ret, ret2 = 0;
 
 	if (dwarf_attr(vr_die, DW_AT_external, &attr) != NULL)
@@ -93,7 +93,8 @@ static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr,
 		if (!tvar)
 			return 0;
 
-		dwarf_formsdata(&attr, &snum);
+		if (dwarf_formsdata(&attr, &snum) != 0)
+			return -ENOENT;
 		ret = asprintf(&tvar->value, "\\%ld", (long)snum);
 
 		return ret < 0 ? -ENOMEM : 0;
@@ -103,8 +104,7 @@ static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr,
 	if (dwarf_attr(vr_die, DW_AT_location, &attr) == NULL)
 		return -EINVAL;	/* Broken DIE ? */
 	if (dwarf_getlocation_addr(&attr, addr, &op, &nops, 1) <= 0) {
-		ret = dwarf_entrypc(sp_die, &tmp);
-		if (ret)
+		if (dwarf_entrypc(sp_die, &tmp) != 0)
 			return -ENOENT;
 
 		if (probe_conf.show_location_range &&
@@ -115,8 +115,7 @@ static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr,
 			return -ENOENT;
 		}
 
-		ret = dwarf_highpc(sp_die, &tmp);
-		if (ret)
+		if (dwarf_highpc(sp_die, &tmp) != 0)
 			return -ENOENT;
 		/*
 		 * This is fuzzed by fentry mcount. We try to find the
@@ -138,12 +137,16 @@ static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr,
 static_var:
 		if (!tvar)
 			return ret2;
+
 		/* Static variables on memory (not stack), make @varname */
-		ret = strlen(dwarf_diename(vr_die));
+		name = dwarf_diename(vr_die);
+		if (!name)
+			return -ENOENT;
+		ret = strlen(name);
 		tvar->value = zalloc(ret + 2);
 		if (tvar->value == NULL)
 			return -ENOMEM;
-		snprintf(tvar->value, ret + 2, "@%s", dwarf_diename(vr_die));
+		snprintf(tvar->value, ret + 2, "@%s", name);
 		tvar->ref = alloc_trace_arg_ref((long)offs);
 		if (tvar->ref == NULL)
 			return -ENOMEM;
@@ -234,13 +237,14 @@ static int convert_variable_type(Dwarf_Die *vr_die,
 	}
 
 	if (die_get_real_type(vr_die, &type) == NULL) {
-		pr_warning("Failed to get a type information of %s.\n",
-			   dwarf_diename(vr_die));
+		const char *name = dwarf_diename(vr_die);
+
+		pr_warning("Failed to get a type information of %s.\n", name ?: "<unknown>");
 		return -ENOENT;
 	}
 
 	pr_debug("%s type is %s.\n",
-		 dwarf_diename(vr_die), dwarf_diename(&type));
+		 die_name(vr_die), die_name(&type));
 
 	if (cast && (!strcmp(cast, "string") || !strcmp(cast, "ustring"))) {
 		/* String type */
@@ -249,7 +253,7 @@ static int convert_variable_type(Dwarf_Die *vr_die,
 		    ret != DW_TAG_array_type) {
 			pr_warning("Failed to cast into string: "
 				   "%s(%s) is not a pointer nor array.\n",
-				   dwarf_diename(vr_die), dwarf_diename(&type));
+				   die_name(vr_die), die_name(&type));
 			return -EINVAL;
 		}
 		if (die_get_real_type(&type, &type) == NULL) {
@@ -272,7 +276,7 @@ static int convert_variable_type(Dwarf_Die *vr_die,
 		    !die_compare_name(&type, "unsigned char")) {
 			pr_warning("Failed to cast into string: "
 				   "%s is not (unsigned) char *.\n",
-				   dwarf_diename(vr_die));
+				   die_name(vr_die));
 			return -EINVAL;
 		}
 		tvar->type = strdup(cast);
@@ -299,7 +303,7 @@ static int convert_variable_type(Dwarf_Die *vr_die,
 	/* Check the bitwidth */
 	if (ret > MAX_BASIC_TYPE_BITS) {
 		pr_info("%s exceeds max-bitwidth. Cut down to %d bits.\n",
-			dwarf_diename(&type), MAX_BASIC_TYPE_BITS);
+			die_name(&type), MAX_BASIC_TYPE_BITS);
 		ret = MAX_BASIC_TYPE_BITS;
 	}
 	ret = snprintf(buf, 16, "%c%d", prefix, ret);
@@ -333,12 +337,14 @@ static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname,
 		pr_warning("Failed to get the type of %s.\n", varname);
 		return -ENOENT;
 	}
-	pr_debug2("Var real type: %s (%x)\n", dwarf_diename(&type),
+	pr_debug2("Var real type: %s (%x)\n", die_name(&type),
 		  (unsigned)dwarf_dieoffset(&type));
 	tag = dwarf_tag(&type);
 
 	if (field->name[0] == '[' &&
 	    (tag == DW_TAG_array_type || tag == DW_TAG_pointer_type)) {
+		int bsize;
+
 		/* Save original type for next field or type */
 		memcpy(die_mem, &type, sizeof(*die_mem));
 		/* Get the type of this array */
@@ -346,7 +352,7 @@ static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname,
 			pr_warning("Failed to get the type of %s.\n", varname);
 			return -ENOENT;
 		}
-		pr_debug2("Array real type: %s (%x)\n", dwarf_diename(&type),
+		pr_debug2("Array real type: %s (%x)\n", die_name(&type),
 			 (unsigned)dwarf_dieoffset(&type));
 		if (tag == DW_TAG_pointer_type) {
 			ref = zalloc(sizeof(struct probe_trace_arg_ref));
@@ -357,7 +363,15 @@ static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname,
 			else
 				*ref_ptr = ref;
 		}
-		ref->offset += dwarf_bytesize(&type) * field->index;
+		bsize = dwarf_bytesize(&type);
+
+		if (bsize < 0)
+			return -EINVAL;
+		if (!ref) {
+			pr_warning("Array indexing not supported for variables in registers.\n");
+			return -ENOTSUP;
+		}
+		ref->offset += bsize * field->index;
 		ref->user_access = user_access;
 		goto next;
 	} else if (tag == DW_TAG_pointer_type) {
@@ -414,7 +428,7 @@ static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname,
 
 	if (die_find_member(&type, field->name, die_mem) == NULL) {
 		pr_warning("%s(type:%s) has no member %s.\n", varname,
-			   dwarf_diename(&type), field->name);
+			   die_name(&type), field->name);
 		return -EINVAL;
 	}
 
@@ -461,7 +475,7 @@ static int convert_variable(Dwarf_Die *vr_die, struct probe_finder *pf)
 	int ret;
 
 	pr_debug("Converting variable %s into trace event.\n",
-		 dwarf_diename(vr_die));
+		 die_name(vr_die));
 
 	ret = convert_variable_location(vr_die, pf->addr, pf->fb_ops,
 					&pf->sp_die, pf, pf->tvar);
@@ -542,7 +556,7 @@ static int convert_to_trace_point(Dwarf_Die *sp_die, Dwfl_Module *mod,
 	/* Verify the address is correct */
 	if (!dwarf_haspc(sp_die, paddr)) {
 		pr_warning("Specified offset is out of %s\n",
-			   dwarf_diename(sp_die));
+			   die_name(sp_die));
 		return -EINVAL;
 	}
 
@@ -599,7 +613,7 @@ static int call_probe_finder(Dwarf_Die *sc_die, struct probe_finder *pf)
 		if (!die_find_realfunc(&pf->cu_die, pf->addr, &pf->sp_die)) {
 			if (die_find_tailfunc(&pf->cu_die, pf->addr, &pf->sp_die)) {
 				pr_warning("Ignoring tail call from %s\n",
-						dwarf_diename(&pf->sp_die));
+						die_name(&pf->sp_die));
 				return 0;
 			} else {
 				pr_warning("Failed to find probe point in any "
@@ -611,10 +625,16 @@ static int call_probe_finder(Dwarf_Die *sc_die, struct probe_finder *pf)
 		memcpy(&pf->sp_die, sc_die, sizeof(Dwarf_Die));
 
 	/* Get the frame base attribute/ops from subprogram */
-	dwarf_attr(&pf->sp_die, DW_AT_frame_base, &fb_attr);
-	ret = dwarf_getlocation_addr(&fb_attr, pf->addr, &pf->fb_ops, &nops, 1);
-	if (ret <= 0 || nops == 0) {
+	if (dwarf_attr(&pf->sp_die, DW_AT_frame_base, &fb_attr) == NULL) {
 		pf->fb_ops = NULL;
+	} else {
+		ret = dwarf_getlocation_addr(&fb_attr, pf->addr, &pf->fb_ops, &nops, 1);
+		if (ret <= 0 || nops == 0)
+			pf->fb_ops = NULL;
+	}
+
+	if (pf->fb_ops == NULL) {
+		/* Not supported */
 	} else if (nops == 1 && pf->fb_ops[0].atom == DW_OP_call_frame_cfa &&
 		   (pf->cfi_eh != NULL || pf->cfi_dbg != NULL)) {
 		if ((dwarf_cfi_addrframe(pf->cfi_eh, pf->addr, &frame) != 0 &&
@@ -667,8 +687,8 @@ static int find_best_scope_cb(Dwarf_Die *fn_die, void *data)
 		}
 	} else {
 		/* With the line number, find the nearest declared DIE */
-		dwarf_decl_line(fn_die, &lno);
-		if (lno < fsp->line && fsp->diff > fsp->line - lno) {
+		if (dwarf_decl_line(fn_die, &lno) == 0 && lno < fsp->line &&
+		    fsp->diff > fsp->line - lno) {
 			/* Keep a candidate and continue */
 			fsp->diff = fsp->line - lno;
 			memcpy(fsp->die_mem, fn_die, sizeof(Dwarf_Die));
@@ -924,12 +944,12 @@ static int probe_point_inline_cb(Dwarf_Die *in_die, void *data)
 		/* Get probe address */
 		if (die_entrypc(in_die, &addr) != 0) {
 			pr_warning("Failed to get entry address of %s.\n",
-				   dwarf_diename(in_die));
+				   die_name(in_die));
 			return -ENOENT;
 		}
 		if (addr == 0) {
 			pr_debug("%s has no valid entry address. skipped.\n",
-				 dwarf_diename(in_die));
+				 die_name(in_die));
 			return -ENOENT;
 		}
 		pf->addr = addr;
@@ -971,12 +991,13 @@ static int probe_point_search_cb(Dwarf_Die *sp_die, void *data)
 	if (pp->file && fname && strtailcmp(pp->file, fname))
 		return DWARF_CB_OK;
 
-	pr_debug("Matched function: %s [%lx]\n", dwarf_diename(sp_die),
+	pr_debug("Matched function: %s [%lx]\n", die_name(sp_die),
 		 (unsigned long)dwarf_dieoffset(sp_die));
 	pf->fname = fname;
 	pf->abstrace_dieoffset = dwarf_dieoffset(sp_die);
 	if (pp->line) { /* Function relative line */
-		dwarf_decl_line(sp_die, &pf->lno);
+		if (dwarf_decl_line(sp_die, &pf->lno) != 0)
+			return DWARF_CB_OK;
 		pf->lno += pp->line;
 		param->retval = find_probe_point_by_line(pf);
 	} else if (die_is_func_instance(sp_die)) {
@@ -985,7 +1006,7 @@ static int probe_point_search_cb(Dwarf_Die *sp_die, void *data)
 		/* But in some case the entry address is 0 */
 		if (pf->addr == 0) {
 			pr_debug("%s has no entry PC. Skipped\n",
-				 dwarf_diename(sp_die));
+				 die_name(sp_die));
 			param->retval = 0;
 		/* Real function */
 		} else if (pp->lazy_line)
@@ -1018,7 +1039,8 @@ static int find_probe_point_by_func(struct probe_finder *pf)
 {
 	struct dwarf_callback_param _param = {.data = (void *)pf,
 					      .retval = 0};
-	dwarf_getfuncs(&pf->cu_die, probe_point_search_cb, &_param, 0);
+	if (dwarf_getfuncs(&pf->cu_die, probe_point_search_cb, &_param, 0) < 0)
+		pr_debug("Failed to get functions from CU\n");
 	return _param.retval;
 }
 
@@ -1207,7 +1229,8 @@ static int copy_variables_cb(Dwarf_Die *die_mem, void *data)
 		 * points to correct die.
 		 */
 		if (dwarf_attr(die_mem, DW_AT_abstract_origin, &attr)) {
-			dwarf_formref_die(&attr, &var_die);
+			if (dwarf_formref_die(&attr, &var_die) == NULL)
+				goto out;
 			if (pf->abstrace_dieoffset != dwarf_dieoffset(&var_die))
 				goto out;
 		}
@@ -1270,6 +1293,8 @@ static int add_probe_trace_event(Dwarf_Die *sc_die, struct probe_finder *pf)
 	struct probe_trace_event *tev;
 	struct perf_probe_arg *args = NULL;
 	int ret, i;
+	const char *realname;
+	Dwarf_Die cu_die_mem;
 
 	/*
 	 * For some reason (e.g. different column assigned to same address)
@@ -1293,13 +1318,17 @@ static int add_probe_trace_event(Dwarf_Die *sc_die, struct probe_finder *pf)
 	if (ret < 0)
 		goto end;
 
-	tev->point.realname = strdup(dwarf_diename(sc_die));
+	realname = dwarf_diename(sc_die);
+	tev->point.realname = strdup(realname ?: "unknown");
 	if (!tev->point.realname) {
 		ret = -ENOMEM;
 		goto end;
 	}
 
-	tev->lang = dwarf_srclang(dwarf_diecu(sc_die, &pf->cu_die, NULL, NULL));
+	if (dwarf_diecu(sc_die, &cu_die_mem, NULL, NULL) != NULL)
+		tev->lang = dwarf_srclang(&cu_die_mem);
+	else
+		tev->lang = DW_LANG_C; // Fallback
 
 	pr_debug("Probe point found: %s+%lu\n", tev->point.symbol,
 		 tev->point.offset);
@@ -1794,7 +1823,8 @@ static int line_range_search_cb(Dwarf_Die *sp_die, void *data)
 
 	if (die_match_name(sp_die, lr->function) && die_is_func_def(sp_die)) {
 		lf->fname = die_get_decl_file(sp_die);
-		dwarf_decl_line(sp_die, &lr->offset);
+		if (dwarf_decl_line(sp_die, &lr->offset) != 0)
+			lr->offset = 0; // Fallback if no line info
 		pr_debug("fname: %s, lineno:%d\n", lf->fname, lr->offset);
 		lf->lno_s = lr->offset + lr->start;
 		if (lf->lno_s < 0)	/* Overflow */
@@ -1818,7 +1848,8 @@ static int line_range_search_cb(Dwarf_Die *sp_die, void *data)
 static int find_line_range_by_func(struct line_finder *lf)
 {
 	struct dwarf_callback_param param = {.data = (void *)lf, .retval = 0};
-	dwarf_getfuncs(&lf->cu_die, line_range_search_cb, &param, 0);
+	if (dwarf_getfuncs(&lf->cu_die, line_range_search_cb, &param, 0) < 0)
+		pr_debug("Failed to get functions from CU\n");
 	return param.retval;
 }
 
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v4 5/6] perf annotate-data: Fix libdw API contract violations
  2026-05-03 17:10 ` [PATCH v4 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
                     ` (3 preceding siblings ...)
  2026-05-03 17:10   ` [PATCH v4 4/6] perf probe-finder: " Ian Rogers
@ 2026-05-03 17:10   ` Ian Rogers
  2026-05-03 23:53     ` Namhyung Kim
  2026-05-03 17:10   ` [PATCH v4 6/6] perf debuginfo: " Ian Rogers
  2026-05-04  8:12   ` [PATCH v5 0/9] [PATCH v5 0/9] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
  6 siblings, 1 reply; 31+ messages in thread
From: Ian Rogers @ 2026-05-03 17:10 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Check return values of `dwarf_aggregate_size` and `dwarf_formudata`.

Additionally:
 - Avoid `vfprintf` undefined behavior with `NULL` strings by using
   the `die_name()` helper for `dwarf_diename()` in `pr_*` calls.
 - Use `die_get_data_member_location()` (updated to use
   `dwarf_attr_integrate`) to correctly parse location expressions
   for inherited member locations in the fallback path when
   `dwarf_formudata()` fails.

Fixes: 2bc3cf575a16 ("perf annotate-data: Improve debug message with location info")
Fixes: 4a111cadac85 ("perf annotate-data: Add member field in the data type")
Fixes: 8b1042c425f6 ("perf annotate-data: Set bitfield member offset and size properly")
Fixes: fc044c53b99f ("perf annotate-data: Add dso->data_types tree")
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
v4:
 - Safe DWARF name printing in annotate-data.
 - Fix fallback location expression parsing for inherited data members.
---
 tools/perf/util/annotate-data.c | 27 +++++++++++++++++----------
 1 file changed, 17 insertions(+), 10 deletions(-)

diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index 1eff0a27237d..2eacbba51f32 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -74,7 +74,8 @@ void pr_debug_type_name(Dwarf_Die *die, enum type_state_kind kind)
 		break;
 	}
 
-	dwarf_aggregate_size(die, &size);
+	if (dwarf_aggregate_size(die, &size) != 0)
+		size = 0;
 
 	strbuf_init(&sb, 32);
 	die_get_typename_from_type(die, &sb);
@@ -146,9 +147,9 @@ static void pr_debug_scope(Dwarf_Die *scope_die)
 
 	tag = dwarf_tag(scope_die);
 	if (tag == DW_TAG_subprogram)
-		pr_info("[function] %s\n", dwarf_diename(scope_die));
+		pr_info("[function] %s\n", die_name(scope_die));
 	else if (tag == DW_TAG_inlined_subroutine)
-		pr_info("[inlined] %s\n", dwarf_diename(scope_die));
+		pr_info("[inlined] %s\n", die_name(scope_die));
 	else if (tag == DW_TAG_lexical_block)
 		pr_info("[block]\n");
 	else
@@ -250,9 +251,12 @@ static int __add_member_cb(Dwarf_Die *die, void *arg)
 	if (dwarf_aggregate_size(&die_mem, &size) < 0)
 		size = 0;
 
-	if (dwarf_attr_integrate(die, DW_AT_data_member_location, &attr))
-		dwarf_formudata(&attr, &loc);
-	else {
+	if (dwarf_attr_integrate(die, DW_AT_data_member_location, &attr)) {
+		if (dwarf_formudata(&attr, &loc) != 0) {
+			if (die_get_data_member_location(die, &loc) != 0)
+				loc = 0;
+		}
+	} else {
 		/* bitfield member */
 		if (dwarf_attr_integrate(die, DW_AT_data_bit_offset, &attr) &&
 		    dwarf_formudata(&attr, &loc) == 0)
@@ -273,7 +277,9 @@ static int __add_member_cb(Dwarf_Die *die, void *arg)
 				     dwarf_diename(die), (long)bit_size) < 0)
 				member->var_name = NULL;
 		} else {
-			member->var_name = strdup(dwarf_diename(die));
+			const char *name = dwarf_diename(die);
+
+			member->var_name = strdup(name);
 		}
 
 		if (member->var_name == NULL) {
@@ -370,7 +376,8 @@ static struct annotated_data_type *dso__findnew_data_type(struct dso *dso,
 	if (dwarf_tag(type_die) == DW_TAG_typedef)
 		die_get_real_type(type_die, type_die);
 
-	dwarf_aggregate_size(type_die, &size);
+	if (dwarf_aggregate_size(type_die, &size) != 0)
+		size = 0;
 
 	/* Check existing nodes in dso->data_types tree */
 	key.self.type_name = type_name;
@@ -1569,7 +1576,7 @@ static int find_data_type_die(struct data_loc_info *dloc, Dwarf_Die *type_die)
 	offset = loc->offset;
 
 	pr_debug_dtp("CU for %s (die:%#lx)\n",
-		     dwarf_diename(&cu_die), (long)dwarf_dieoffset(&cu_die));
+		     die_name(&cu_die), (long)dwarf_dieoffset(&cu_die));
 
 	if (reg == DWARF_REG_PC) {
 		if (get_global_var_type(&cu_die, dloc, dloc->ip, dloc->var_addr,
@@ -1636,7 +1643,7 @@ static int find_data_type_die(struct data_loc_info *dloc, Dwarf_Die *type_die)
 		}
 
 		pr_debug_dtp("found \"%s\" (die: %#lx) in scope=%d/%d (die: %#lx) ",
-			     dwarf_diename(&var_die), (long)dwarf_dieoffset(&var_die),
+			     die_name(&var_die), (long)dwarf_dieoffset(&var_die),
 			     i+1, nr_scopes, (long)dwarf_dieoffset(&scopes[i]));
 
 		if (reg == DWARF_REG_PC) {
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v4 6/6] perf debuginfo: Fix libdw API contract violations
  2026-05-03 17:10 ` [PATCH v4 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
                     ` (4 preceding siblings ...)
  2026-05-03 17:10   ` [PATCH v4 5/6] perf annotate-data: " Ian Rogers
@ 2026-05-03 17:10   ` Ian Rogers
  2026-05-03 23:54     ` Namhyung Kim
  2026-05-04  8:12   ` [PATCH v5 0/9] [PATCH v5 0/9] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
  6 siblings, 1 reply; 31+ messages in thread
From: Ian Rogers @ 2026-05-03 17:10 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Check return value of `dwfl_report_end` during offline initialization.
Validate `dwfl_module_relocation_info` result before passing to `strcmp`
to avoid potential segmentation faults.

Additionally:
 - Fix a file descriptor leak in `debuginfo__init_offline_dwarf()` when
   `dwfl_report_offline()` or subsequent setup calls fail.

Fixes: 6f1b6291cf73 ("perf tools: Add util/debuginfo.[ch] files")
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
v4:
 - Fix file descriptor leaks in debuginfo init paths.
---
 tools/perf/util/debuginfo.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/tools/perf/util/debuginfo.c b/tools/perf/util/debuginfo.c
index 0e35c13abd04..84a78b30ceac 100644
--- a/tools/perf/util/debuginfo.c
+++ b/tools/perf/util/debuginfo.c
@@ -42,6 +42,7 @@ static int debuginfo__init_offline_dwarf(struct debuginfo *dbg,
 {
 	GElf_Addr dummy;
 	int fd;
+	bool fd_consumed = false;
 
 	fd = open(path, O_RDONLY);
 	if (fd < 0)
@@ -55,6 +56,7 @@ static int debuginfo__init_offline_dwarf(struct debuginfo *dbg,
 	dbg->mod = dwfl_report_offline(dbg->dwfl, "", "", fd);
 	if (!dbg->mod)
 		goto error;
+	fd_consumed = true;
 
 	dbg->dbg = dwfl_module_getdwarf(dbg->mod, &dbg->bias);
 	if (!dbg->dbg)
@@ -62,13 +64,14 @@ static int debuginfo__init_offline_dwarf(struct debuginfo *dbg,
 
 	dwfl_module_build_id(dbg->mod, &dbg->build_id, &dummy);
 
-	dwfl_report_end(dbg->dwfl, NULL, NULL);
+	if (dwfl_report_end(dbg->dwfl, NULL, NULL) != 0)
+		goto error;
 
 	return 0;
 error:
 	if (dbg->dwfl)
 		dwfl_end(dbg->dwfl);
-	else
+	if (!fd_consumed)
 		close(fd);
 	memset(dbg, 0, sizeof(*dbg));
 
@@ -167,7 +170,7 @@ int debuginfo__get_text_offset(struct debuginfo *dbg, Dwarf_Addr *offs,
 	/* Search the relocation related .text section */
 	for (i = 0; i < n; i++) {
 		p = dwfl_module_relocation_info(dbg->mod, i, &shndx);
-		if (strcmp(p, ".text") == 0) {
+		if (p && strcmp(p, ".text") == 0) {
 			/* OK, get the section header */
 			scn = elf_getscn(elf, shndx);
 			if (!scn)
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* Re: [PATCH v4 1/6] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at
  2026-05-03 17:10   ` [PATCH v4 1/6] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at Ian Rogers
@ 2026-05-03 23:33     ` Namhyung Kim
  0 siblings, 0 replies; 31+ messages in thread
From: Namhyung Kim @ 2026-05-03 23:33 UTC (permalink / raw)
  To: Ian Rogers
  Cc: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo, Jiri Olsa,
	Adrian Hunter, James Clark, Zecheng Li, Masami Hiramatsu,
	linux-perf-users, linux-kernel

On Sun, May 03, 2026 at 10:10:27AM -0700, Ian Rogers wrote:
> A segmentation fault was observed in `libdw` when running `perf kmem`
> with `--page stat` on some workloads. The crash occurred deep inside
> `libdw` (specifically in `dwarf_child` and `dwarf_diename`) when
> processing DWARF information.
> 
> The root cause was improper error handling of `dwarf_getfuncs` in
> `die_find_realfunc` and `die_find_tailfunc`.
> 
> `dwarf_getfuncs` returns:
>  - `0` on success (when all functions have been processed).
>  - A positive offset if the callback aborts early (e.g., via
>    `DWARF_CB_ABORT` when a match is found).
>  - `-1` on error.
> 
> The original code used `if (!dwarf_getfuncs(...)) return NULL;`. On
> error (`-1`), `!-1` evaluates to `0` (false), bypassing the error
> check. Execution then proceeded as if a match was found, returning
> uninitialized stack memory (`die_mem`) to the caller
> (`cu_walk_functions_at`). When `cu_walk_functions_at` passed this
> uninitialized memory to `libdw` via `dwarf_diename`, it caused a
> segmentation fault.
> 
> Fix this by correcting the error check to `if (dwarf_getfuncs(...) <= 0)`.
> 
> Fixes: e0d153c69040 ("perf-probe: Move dwarf library routines to dwarf-aux.{c, h}")
> Fixes: d4c537e6bf86 ("perf probe: Ignore tail calls to probed functions")
> Assisted-by: Gemini-CLI:Google Gemini 3
> Signed-off-by: Ian Rogers <irogers@google.com>

Acked-by: Namhyung Kim <namhyung@kernel.org>

Thanks,
Namhyung

> ---
>  tools/perf/util/dwarf-aux.c | 5 ++---
>  1 file changed, 2 insertions(+), 3 deletions(-)
> 
> diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
> index 92db2fccc788..109a166a6d19 100644
> --- a/tools/perf/util/dwarf-aux.c
> +++ b/tools/perf/util/dwarf-aux.c
> @@ -171,7 +171,6 @@ int cu_walk_functions_at(Dwarf_Die *cu_die, Dwarf_Addr addr,
>  	}
>  
>  	return ret;
> -
>  }
>  
>  /**
> @@ -620,7 +619,7 @@ Dwarf_Die *die_find_tailfunc(Dwarf_Die *cu_die, Dwarf_Addr addr,
>  	ad.addr = addr;
>  	ad.die_mem = die_mem;
>  	/* dwarf_getscopes can't find subprogram. */
> -	if (!dwarf_getfuncs(cu_die, __die_search_func_tail_cb, &ad, 0))
> +	if (dwarf_getfuncs(cu_die, __die_search_func_tail_cb, &ad, 0) <= 0)
>  		return NULL;
>  	else
>  		return die_mem;
> @@ -659,7 +658,7 @@ Dwarf_Die *die_find_realfunc(Dwarf_Die *cu_die, Dwarf_Addr addr,
>  	ad.addr = addr;
>  	ad.die_mem = die_mem;
>  	/* dwarf_getscopes can't find subprogram. */
> -	if (!dwarf_getfuncs(cu_die, __die_search_func_cb, &ad, 0))
> +	if (dwarf_getfuncs(cu_die, __die_search_func_cb, &ad, 0) <= 0)
>  		return NULL;
>  	else
>  		return die_mem;
> -- 
> 2.54.0.545.g6539524ca2-goog
> 

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v4 2/6] perf dwarf-aux: Fix libdw API contract violations
  2026-05-03 17:10   ` [PATCH v4 2/6] perf dwarf-aux: Fix libdw API contract violations Ian Rogers
@ 2026-05-03 23:36     ` Namhyung Kim
  0 siblings, 0 replies; 31+ messages in thread
From: Namhyung Kim @ 2026-05-03 23:36 UTC (permalink / raw)
  To: Ian Rogers
  Cc: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo, Jiri Olsa,
	Adrian Hunter, James Clark, Zecheng Li, Masami Hiramatsu,
	linux-perf-users, linux-kernel

On Sun, May 03, 2026 at 10:10:28AM -0700, Ian Rogers wrote:
> Check return values of `dwarf_decl_line` (where non-optional),
> `dwarf_getfuncs`, and `dwarf_lineaddr` to prevent using uninitialized
> stack variables or incorrectly reporting success on failure.
> 
> For the root DIE in `die_walk_lines()`, `dwarf_decl_line` and
> `die_get_decl_file` are optional and their failures are handled
> gracefully to avoid breaking line walking on valid functions.
> 
> Additionally:
>  - Add NULL pointer protection for `strcmp()` in `die_walk_lines()`
>    when `inf` or `decf` are NULL to prevent crashes on generated
>    code.
>  - Use `dwarf_attr_integrate` in `die_get_data_member_location` to
>    correctly resolve inherited member locations (e.g. via abstract
>    origin or specification).
> 
> Fixes: 57f95bf5f882 ("perf probe: Show correct statement line number by perf probe -l")
> Fixes: 3f4460a28fb2 ("perf probe: Filter out redundant inline-instances")
> Fixes: 75186a9b09e4 ("perf probe: Fix to show lines of sys_ functions correctly")
> Fixes: e0d153c69040 ("perf-probe: Move dwarf library routines to dwarf-aux.{c, h}")
> Fixes: 6243b9dc4c99 ("perf probe: Move dwarf specific functions to dwarf-aux.c")
> Assisted-by: Gemini-CLI:Google Gemini 3
> Signed-off-by: Ian Rogers <irogers@google.com>
> ---
> v4:
>  - Fix strcmp(NULL) risk and inherited member location fallbacks in dwarf-aux.c.
> ---
>  tools/perf/util/dwarf-aux.c | 34 ++++++++++++++++------------------
>  tools/perf/util/dwarf-aux.h |  5 +++++
>  2 files changed, 21 insertions(+), 18 deletions(-)
> 
> diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
> index 109a166a6d19..a8ab1c30d0ac 100644
> --- a/tools/perf/util/dwarf-aux.c
> +++ b/tools/perf/util/dwarf-aux.c
> @@ -125,7 +125,8 @@ int cu_find_lineinfo(Dwarf_Die *cu_die, Dwarf_Addr addr,
>  	    && die_entrypc(&die_mem, &faddr) == 0 &&
>  	    faddr == addr) {
>  		*fname = die_get_decl_file(&die_mem);
> -		dwarf_decl_line(&die_mem, lineno);
> +		if (dwarf_decl_line(&die_mem, lineno) != 0)
> +			return -ENOENT;
>  		goto out;
>  	}
>  
> @@ -459,7 +460,7 @@ int die_get_data_member_location(Dwarf_Die *mb_die, Dwarf_Word *offs)
>  	size_t nexpr;
>  	int ret;
>  
> -	if (dwarf_attr(mb_die, DW_AT_data_member_location, &attr) == NULL)
> +	if (dwarf_attr_integrate(mb_die, DW_AT_data_member_location, &attr) == NULL)
>  		return -ENOENT;
>  
>  	if (dwarf_formudata(&attr, offs) != 0) {
> @@ -795,8 +796,7 @@ static int __die_walk_instances_cb(Dwarf_Die *inst, void *data)
>  
>  	/* Ignore redundant instances */
>  	if (dwarf_tag(inst) == DW_TAG_inlined_subroutine) {
> -		dwarf_decl_line(origin, &tmp);
> -		if (die_get_call_lineno(inst) == tmp) {
> +		if (dwarf_decl_line(origin, &tmp) == 0 && die_get_call_lineno(inst) == tmp) {
>  			tmp = die_get_decl_fileno(origin);
>  			if (die_get_call_fileno(inst) == tmp)
>  				return DIE_FIND_CB_CONTINUE;
> @@ -950,11 +950,6 @@ int die_walk_lines(Dwarf_Die *rt_die, line_walk_callback_t callback, void *data)
>  		cu_die = dwarf_diecu(rt_die, &die_mem, NULL, NULL);
>  		dwarf_decl_line(rt_die, &decl);
>  		decf = die_get_decl_file(rt_die);
> -		if (!decf) {
> -			pr_debug2("Failed to get the declared file name of %s\n",
> -				  dwarf_diename(rt_die));
> -			return -EINVAL;
> -		}

Why is this part removed?


>  	} else
>  		cu_die = rt_die;
>  	if (!cu_die) {
> @@ -998,12 +993,11 @@ int die_walk_lines(Dwarf_Die *rt_die, line_walk_callback_t callback, void *data)
>  			if (die_find_inlinefunc(rt_die, addr, &die_mem)) {
>  				/* Call-site check */
>  				inf = die_get_call_file(&die_mem);
> -				if ((inf && !strcmp(inf, decf)) &&
> +				if ((inf == decf || (inf && decf && !strcmp(inf, decf))) &&
>  				    die_get_call_lineno(&die_mem) == lineno)
>  					goto found;
>  
> -				dwarf_decl_line(&die_mem, &inl);
> -				if (inl != decl ||
> +				if (dwarf_decl_line(&die_mem, &inl) != 0 || inl != decl ||
>  				    decf != die_get_decl_file(&die_mem))
>  					continue;
>  			}
> @@ -1034,8 +1028,10 @@ int die_walk_lines(Dwarf_Die *rt_die, line_walk_callback_t callback, void *data)
>  			.data = data,
>  			.retval = 0,
>  		};
> -		dwarf_getfuncs(cu_die, __die_walk_culines_cb, &param, 0);
> -		ret = param.retval;
> +		if (dwarf_getfuncs(cu_die, __die_walk_culines_cb, &param, 0) < 0)
> +			ret = -EINVAL;
> +		else
> +			ret = param.retval;
>  	}
>  
>  	return ret;
> @@ -1939,10 +1935,12 @@ static bool die_get_postprologue_addr(unsigned long entrypc_idx,
>  			break;
>  	}
>  
> -	dwarf_lineaddr(line, postprologue_addr);
> -	if (*postprologue_addr >= highpc)
> -		dwarf_lineaddr(dwarf_onesrcline(lines, i - 1),
> -			       postprologue_addr);
> +	if (dwarf_lineaddr(line, postprologue_addr) != 0)
> +		return false;
> +	if (*postprologue_addr >= highpc) {
> +		if (dwarf_lineaddr(dwarf_onesrcline(lines, i - 1), postprologue_addr) != 0)
> +			return false;
> +	}
>  
>  	return true;
>  }
> diff --git a/tools/perf/util/dwarf-aux.h b/tools/perf/util/dwarf-aux.h
> index a79968a2e573..161f0bf980b6 100644
> --- a/tools/perf/util/dwarf-aux.h
> +++ b/tools/perf/util/dwarf-aux.h
> @@ -10,6 +10,11 @@
>  #include <elfutils/libdwfl.h>
>  #include <elfutils/version.h>
>  
> +static inline const char *die_name(Dwarf_Die *die)
> +{
> +	return dwarf_diename(die) ?: "<unknown>";
> +}
> +

Please move it where it's used.

Thanks,
Namhyung


>  struct strbuf;
>  
>  /* Find the realpath of the target file */
> -- 
> 2.54.0.545.g6539524ca2-goog
> 

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v4 3/6] perf libdw: Fix libdw API contract violations
  2026-05-03 17:10   ` [PATCH v4 3/6] perf libdw: " Ian Rogers
@ 2026-05-03 23:44     ` Namhyung Kim
  0 siblings, 0 replies; 31+ messages in thread
From: Namhyung Kim @ 2026-05-03 23:44 UTC (permalink / raw)
  To: Ian Rogers
  Cc: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo, Jiri Olsa,
	Adrian Hunter, James Clark, Zecheng Li, Masami Hiramatsu,
	linux-perf-users, linux-kernel

On Sun, May 03, 2026 at 10:10:29AM -0700, Ian Rogers wrote:
> Check return values of `dwfl_report_end` and `dwfl_module_addrdie`.
> Check `die_get_call_lineno` for errors.
> 
> Additionally:
>  - Introduce `inline_node__clear_frames()` to clean up partial
>    allocations.

Sounds like it should be a separate commit.


>  - Ensure `*file` is freed and inline frames are cleared on error in
>    `libdw__addr2line()` to prevent memory leaks and duplicated
>    callchains when falling back to other unwinders.
>  - Allow DWARF line 0 in `libdw_a2l_cb()`, as it is a valid
>    reference for compiler-generated code.
>  - Fix the parent srcline lookup in `libdw_a2l_cb()` to target the
>    correct parent node depending on the callchain order
>    (ORDER_CALLER/ORDER_CALLEE).

Also it looks like an independent fix.  While you could put other fixes
into separate commits (up to you), we can merge simple things.

> 
> Fixes: b7a2b011e962 ("perf powerpc: Unify the skip-callchain-idx libdw with that for addr2line")
> Fixes: 88c51002d06f ("perf addr2line: Add a libdw implementation")
> Assisted-by: Gemini-CLI:Google Gemini 3
> Signed-off-by: Ian Rogers <irogers@google.com>
> ---
> v4:
>  - Fix memory leaks and duplicated callchains on unwinding errors.
>  - Support DWARF line 0 in inline list.
>  - Fix callchain parent update in ORDER_CALLER mode.
> ---
>  tools/perf/util/libdw.c   | 84 +++++++++++++++++++++++++++++++--------
>  tools/perf/util/srcline.c |  9 ++++-
>  tools/perf/util/srcline.h |  1 +
>  3 files changed, 77 insertions(+), 17 deletions(-)
> 
> diff --git a/tools/perf/util/libdw.c b/tools/perf/util/libdw.c
> index 216977884103..e69ff4cea856 100644
> --- a/tools/perf/util/libdw.c
> +++ b/tools/perf/util/libdw.c
> @@ -4,6 +4,7 @@
>  #include "srcline.h"
>  #include "symbol.h"
>  #include "dwarf-aux.h"
> +#include "callchain.h"
>  #include <fcntl.h>
>  #include <unistd.h>
>  #include <elfutils/libdwfl.h>
> @@ -60,7 +61,11 @@ struct Dwfl *dso__libdw_dwfl(struct dso *dso)
>  		return NULL;
>  	}
>  
> -	dwfl_report_end(dwfl, /*removed=*/NULL, /*arg=*/NULL);
> +	if (dwfl_report_end(dwfl, NULL, NULL) != 0) {

Better to keep the comments?


> +		dwfl_end(dwfl);
> +		return NULL;
> +	}
> +
>  	dso__set_libdw(dso, dwfl);
>  
>  	return dwfl;
> @@ -72,41 +77,70 @@ struct libdw_a2l_cb_args {
>  	struct inline_node *node;
>  	char *leaf_srcline;
>  	bool leaf_srcline_used;
> +	int err;
>  };
>  
>  static int libdw_a2l_cb(Dwarf_Die *die, void *_args)
>  {
>  	struct libdw_a2l_cb_args *args  = _args;
> -	struct symbol *inline_sym = new_inline_sym(args->dso, args->sym, dwarf_diename(die));
> +	const char *name = dwarf_diename(die);
> +	struct symbol *inline_sym = new_inline_sym(args->dso, args->sym, name ?: "unknown");

Use die_getname() here?


>  	const char *call_fname = die_get_call_file(die);
> +	int call_lineno = die_get_call_lineno(die);
>  	char *call_srcline = srcline__unknown;
> -	struct inline_list *ilist;
>  
> -	if (!inline_sym)
> -		return -ENOMEM;
> +	if (!inline_sym) {
> +		args->err = -ENOMEM;
> +		return DWARF_CB_ABORT;
> +	}
>  
>  	/* Assign caller information to the parent. */
> -	if (call_fname)
> -		call_srcline = srcline_from_fileline(call_fname, die_get_call_lineno(die));
> +	if (call_fname && call_lineno >= 0)
> +		call_srcline = srcline_from_fileline(call_fname, call_lineno);
>  
> -	list_for_each_entry(ilist, &args->node->val, list) {
> -		if (args->leaf_srcline == ilist->srcline)
> +	if (!list_empty(&args->node->val)) {
> +		struct inline_list *parent;
> +
> +		if (callchain_param.order == ORDER_CALLEE)
> +			parent = list_first_entry(&args->node->val, struct inline_list, list);
> +		else
> +			parent = list_last_entry(&args->node->val, struct inline_list, list);
> +
> +		if (args->leaf_srcline == parent->srcline)
>  			args->leaf_srcline_used = false;
> -		else if (ilist->srcline != srcline__unknown)
> -			free(ilist->srcline);
> -		ilist->srcline =  call_srcline;
> +		else if (parent->srcline != srcline__unknown)
> +			free(parent->srcline);
> +		parent->srcline = call_srcline;
>  		call_srcline = NULL;
> -		break;
>  	}
>  	if (call_srcline && call_srcline != srcline__unknown)
>  		free(call_srcline);
>  
>  	/* Add this symbol to the chain as the leaf. */
>  	if (!args->leaf_srcline_used) {
> -		inline_list__append_tail(inline_sym, args->leaf_srcline, args->node);
> +		if (inline_list__append_tail(inline_sym, args->leaf_srcline, args->node) != 0) {
> +			args->err = -ENOMEM;
> +			if (inline_sym->inlined)
> +				symbol__delete(inline_sym);
> +			return DWARF_CB_ABORT;
> +		}
>  		args->leaf_srcline_used = true;
>  	} else {
> -		inline_list__append_tail(inline_sym, strdup(args->leaf_srcline), args->node);
> +		char *srcline = strdup(args->leaf_srcline);
> +
> +		if (!srcline) {
> +			args->err = -ENOMEM;
> +			if (inline_sym->inlined)
> +				symbol__delete(inline_sym);
> +			return DWARF_CB_ABORT;
> +		}
> +		if (inline_list__append_tail(inline_sym, srcline, args->node) != 0) {
> +			free(srcline);
> +			args->err = -ENOMEM;
> +			if (inline_sym->inlined)
> +				symbol__delete(inline_sym);
> +			return DWARF_CB_ABORT;

Looks like the pattern is repeated.  My rule is to factor it out when
it repeats more than twice. :)


> +		}
>  	}
>  	return 0;
>  }
> @@ -162,11 +196,29 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr,
>  			.leaf_srcline = srcline_from_fileline(src ?: "<unknown>", lineno),
>  		};
>  
> +		if (!args.leaf_srcline) {
> +			if (file && *file) {
> +				free(*file);
> +				*file = NULL;
> +			}
> +			return 0;
> +		}
> +
>  		/* Walk from the parent down to the leaf. */
> -		cu_walk_functions_at(cudie, addr, libdw_a2l_cb, &args);
> +		if (cudie)
> +			cu_walk_functions_at(cudie, addr, libdw_a2l_cb, &args);
>  
>  		if (!args.leaf_srcline_used)
>  			free(args.leaf_srcline);
> +
> +		if (args.err) {
> +			if (file && *file) {
> +				free(*file);
> +				*file = NULL;
> +			}
> +			inline_node__clear_frames(node);
> +			return 0;
> +		}
>  	}
>  	return 1;
>  }
> diff --git a/tools/perf/util/srcline.c b/tools/perf/util/srcline.c
> index db164d258163..62884428fb5a 100644
> --- a/tools/perf/util/srcline.c
> +++ b/tools/perf/util/srcline.c
> @@ -429,10 +429,13 @@ struct inline_node *dso__parse_addr_inlines(struct dso *dso, u64 addr,
>  	return addr2inlines(dso_name, addr, dso, sym);
>  }
>  
> -void inline_node__delete(struct inline_node *node)
> +void inline_node__clear_frames(struct inline_node *node)
>  {
>  	struct inline_list *ilist, *tmp;
>  
> +	if (node == NULL)
> +		return;
> +
>  	list_for_each_entry_safe(ilist, tmp, &node->val, list) {
>  		list_del_init(&ilist->list);
>  		zfree_srcline(&ilist->srcline);
> @@ -441,7 +444,11 @@ void inline_node__delete(struct inline_node *node)
>  			symbol__delete(ilist->symbol);
>  		free(ilist);
>  	}
> +}
>  
> +void inline_node__delete(struct inline_node *node)
> +{
> +	inline_node__clear_frames(node);
>  	free(node);
>  }
>  
> diff --git a/tools/perf/util/srcline.h b/tools/perf/util/srcline.h
> index 7c37b3bf9ce7..1018cbc886d6 100644
> --- a/tools/perf/util/srcline.h
> +++ b/tools/perf/util/srcline.h
> @@ -47,6 +47,7 @@ struct inline_node *dso__parse_addr_inlines(struct dso *dso, u64 addr,
>  					    struct symbol *sym);
>  /* free resources associated to the inline node list */
>  void inline_node__delete(struct inline_node *node);
> +void inline_node__clear_frames(struct inline_node *node);
>  
>  /* insert the inline node list into the DSO, which will take ownership */
>  void inlines__tree_insert(struct rb_root_cached *tree,
> -- 
> 2.54.0.545.g6539524ca2-goog
> 

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v4 4/6] perf probe-finder: Fix libdw API contract violations
  2026-05-03 17:10   ` [PATCH v4 4/6] perf probe-finder: " Ian Rogers
@ 2026-05-03 23:49     ` Namhyung Kim
  0 siblings, 0 replies; 31+ messages in thread
From: Namhyung Kim @ 2026-05-03 23:49 UTC (permalink / raw)
  To: Ian Rogers
  Cc: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo, Jiri Olsa,
	Adrian Hunter, James Clark, Zecheng Li, Masami Hiramatsu,
	linux-perf-users, linux-kernel

On Sun, May 03, 2026 at 10:10:30AM -0700, Ian Rogers wrote:
> Check return values of `dwarf_formsdata`, `dwarf_entrypc`,
> `dwarf_highpc`, `dwarf_bytesize`, `dwarf_attr`, `dwarf_decl_line`,
> `dwarf_getfuncs`, and `dwarf_formref_die`. Validate `dwarf_diename` and
> `dwarf_diecu` results to prevent potential crashes. Fix C90 mixed
> declarations.
> 
> Additionally:
>  - Avoid vfprintf undefined behavior with NULL strings by using the
>    `die_name()` helper for `dwarf_diename()` in `pr_*` calls,
>    including when warning about tail calls.
>  - Prevent NULL pointer dereference in `convert_variable_fields()`
>    when processing array elements for variables in registers.
>  - Fallback to offset 0 in `line_range_search_cb()` instead of
>    skipping functions without `DW_AT_decl_line`.
>  - Relax `dwarf_getfuncs` error checking in
>    `find_probe_point_by_func()` and `find_line_range_by_func()` to
>    prevent premature CU search aborts, ensuring robustness against
>    corrupted CUs.
> 
> Fixes: 66f69b219716 ("perf probe: Support DW_AT_const_value constant value")
> Fixes: 3d918a12a1b3 ("perf probe: Find fentry mcount fuzzed parameter location")
> Fixes: bcfc082150c6 ("perf probe: Remove redundant dwarf functions")
> Fixes: 221d061182b8 ("perf probe: Fix to search local variables in appropriate scope")
> Fixes: b55a87ade383 ("perf probe: Remove die() from probe-finder code")
> Fixes: 4c859351226c ("perf probe: Support glob wildcards for function name")
> Assisted-by: Gemini-CLI:Google Gemini 3
> Signed-off-by: Ian Rogers <irogers@google.com>
> ---
[SNIP]
> @@ -1293,13 +1318,17 @@ static int add_probe_trace_event(Dwarf_Die *sc_die, struct probe_finder *pf)
>  	if (ret < 0)
>  		goto end;
>  
> -	tev->point.realname = strdup(dwarf_diename(sc_die));
> +	realname = dwarf_diename(sc_die);
> +	tev->point.realname = strdup(realname ?: "unknown");

Could be:

	tev->point.realname = strdup(die_name(sc_die));


>  	if (!tev->point.realname) {
>  		ret = -ENOMEM;
>  		goto end;
>  	}
>  
> -	tev->lang = dwarf_srclang(dwarf_diecu(sc_die, &pf->cu_die, NULL, NULL));
> +	if (dwarf_diecu(sc_die, &cu_die_mem, NULL, NULL) != NULL)

I think it should fill pf->cu_die.

Thanks,
Namhyung


> +		tev->lang = dwarf_srclang(&cu_die_mem);
> +	else
> +		tev->lang = DW_LANG_C; // Fallback
>  
>  	pr_debug("Probe point found: %s+%lu\n", tev->point.symbol,
>  		 tev->point.offset);
> @@ -1794,7 +1823,8 @@ static int line_range_search_cb(Dwarf_Die *sp_die, void *data)
>  
>  	if (die_match_name(sp_die, lr->function) && die_is_func_def(sp_die)) {
>  		lf->fname = die_get_decl_file(sp_die);
> -		dwarf_decl_line(sp_die, &lr->offset);
> +		if (dwarf_decl_line(sp_die, &lr->offset) != 0)
> +			lr->offset = 0; // Fallback if no line info
>  		pr_debug("fname: %s, lineno:%d\n", lf->fname, lr->offset);
>  		lf->lno_s = lr->offset + lr->start;
>  		if (lf->lno_s < 0)	/* Overflow */
> @@ -1818,7 +1848,8 @@ static int line_range_search_cb(Dwarf_Die *sp_die, void *data)
>  static int find_line_range_by_func(struct line_finder *lf)
>  {
>  	struct dwarf_callback_param param = {.data = (void *)lf, .retval = 0};
> -	dwarf_getfuncs(&lf->cu_die, line_range_search_cb, &param, 0);
> +	if (dwarf_getfuncs(&lf->cu_die, line_range_search_cb, &param, 0) < 0)
> +		pr_debug("Failed to get functions from CU\n");
>  	return param.retval;
>  }
>  
> -- 
> 2.54.0.545.g6539524ca2-goog
> 

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v4 5/6] perf annotate-data: Fix libdw API contract violations
  2026-05-03 17:10   ` [PATCH v4 5/6] perf annotate-data: " Ian Rogers
@ 2026-05-03 23:53     ` Namhyung Kim
  0 siblings, 0 replies; 31+ messages in thread
From: Namhyung Kim @ 2026-05-03 23:53 UTC (permalink / raw)
  To: Ian Rogers
  Cc: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo, Jiri Olsa,
	Adrian Hunter, James Clark, Zecheng Li, Masami Hiramatsu,
	linux-perf-users, linux-kernel

On Sun, May 03, 2026 at 10:10:31AM -0700, Ian Rogers wrote:
> Check return values of `dwarf_aggregate_size` and `dwarf_formudata`.
> 
> Additionally:
>  - Avoid `vfprintf` undefined behavior with `NULL` strings by using
>    the `die_name()` helper for `dwarf_diename()` in `pr_*` calls.
>  - Use `die_get_data_member_location()` (updated to use
>    `dwarf_attr_integrate`) to correctly parse location expressions
>    for inherited member locations in the fallback path when
>    `dwarf_formudata()` fails.
> 
> Fixes: 2bc3cf575a16 ("perf annotate-data: Improve debug message with location info")
> Fixes: 4a111cadac85 ("perf annotate-data: Add member field in the data type")
> Fixes: 8b1042c425f6 ("perf annotate-data: Set bitfield member offset and size properly")
> Fixes: fc044c53b99f ("perf annotate-data: Add dso->data_types tree")
> Assisted-by: Gemini-CLI:Google Gemini 3
> Signed-off-by: Ian Rogers <irogers@google.com>
> ---
> v4:
>  - Safe DWARF name printing in annotate-data.
>  - Fix fallback location expression parsing for inherited data members.
> ---
>  tools/perf/util/annotate-data.c | 27 +++++++++++++++++----------
>  1 file changed, 17 insertions(+), 10 deletions(-)
> 
> diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
> index 1eff0a27237d..2eacbba51f32 100644
> --- a/tools/perf/util/annotate-data.c
> +++ b/tools/perf/util/annotate-data.c
> @@ -74,7 +74,8 @@ void pr_debug_type_name(Dwarf_Die *die, enum type_state_kind kind)
>  		break;
>  	}
>  
> -	dwarf_aggregate_size(die, &size);
> +	if (dwarf_aggregate_size(die, &size) != 0)
> +		size = 0;
>  
>  	strbuf_init(&sb, 32);
>  	die_get_typename_from_type(die, &sb);
> @@ -146,9 +147,9 @@ static void pr_debug_scope(Dwarf_Die *scope_die)
>  
>  	tag = dwarf_tag(scope_die);
>  	if (tag == DW_TAG_subprogram)
> -		pr_info("[function] %s\n", dwarf_diename(scope_die));
> +		pr_info("[function] %s\n", die_name(scope_die));
>  	else if (tag == DW_TAG_inlined_subroutine)
> -		pr_info("[inlined] %s\n", dwarf_diename(scope_die));
> +		pr_info("[inlined] %s\n", die_name(scope_die));
>  	else if (tag == DW_TAG_lexical_block)
>  		pr_info("[block]\n");
>  	else
> @@ -250,9 +251,12 @@ static int __add_member_cb(Dwarf_Die *die, void *arg)
>  	if (dwarf_aggregate_size(&die_mem, &size) < 0)
>  		size = 0;
>  
> -	if (dwarf_attr_integrate(die, DW_AT_data_member_location, &attr))
> -		dwarf_formudata(&attr, &loc);
> -	else {
> +	if (dwarf_attr_integrate(die, DW_AT_data_member_location, &attr)) {
> +		if (dwarf_formudata(&attr, &loc) != 0) {
> +			if (die_get_data_member_location(die, &loc) != 0)
> +				loc = 0;
> +		}
> +	} else {
>  		/* bitfield member */
>  		if (dwarf_attr_integrate(die, DW_AT_data_bit_offset, &attr) &&
>  		    dwarf_formudata(&attr, &loc) == 0)
> @@ -273,7 +277,9 @@ static int __add_member_cb(Dwarf_Die *die, void *arg)
>  				     dwarf_diename(die), (long)bit_size) < 0)
>  				member->var_name = NULL;
>  		} else {
> -			member->var_name = strdup(dwarf_diename(die));
> +			const char *name = dwarf_diename(die);
> +
> +			member->var_name = strdup(name);

This can be:		member->var_name = name ? strdup(name) : NULL;

Thanks,
Namhyung


>  		}
>  
>  		if (member->var_name == NULL) {
> @@ -370,7 +376,8 @@ static struct annotated_data_type *dso__findnew_data_type(struct dso *dso,
>  	if (dwarf_tag(type_die) == DW_TAG_typedef)
>  		die_get_real_type(type_die, type_die);
>  
> -	dwarf_aggregate_size(type_die, &size);
> +	if (dwarf_aggregate_size(type_die, &size) != 0)
> +		size = 0;
>  
>  	/* Check existing nodes in dso->data_types tree */
>  	key.self.type_name = type_name;
> @@ -1569,7 +1576,7 @@ static int find_data_type_die(struct data_loc_info *dloc, Dwarf_Die *type_die)
>  	offset = loc->offset;
>  
>  	pr_debug_dtp("CU for %s (die:%#lx)\n",
> -		     dwarf_diename(&cu_die), (long)dwarf_dieoffset(&cu_die));
> +		     die_name(&cu_die), (long)dwarf_dieoffset(&cu_die));
>  
>  	if (reg == DWARF_REG_PC) {
>  		if (get_global_var_type(&cu_die, dloc, dloc->ip, dloc->var_addr,
> @@ -1636,7 +1643,7 @@ static int find_data_type_die(struct data_loc_info *dloc, Dwarf_Die *type_die)
>  		}
>  
>  		pr_debug_dtp("found \"%s\" (die: %#lx) in scope=%d/%d (die: %#lx) ",
> -			     dwarf_diename(&var_die), (long)dwarf_dieoffset(&var_die),
> +			     die_name(&var_die), (long)dwarf_dieoffset(&var_die),
>  			     i+1, nr_scopes, (long)dwarf_dieoffset(&scopes[i]));
>  
>  		if (reg == DWARF_REG_PC) {
> -- 
> 2.54.0.545.g6539524ca2-goog
> 

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v4 6/6] perf debuginfo: Fix libdw API contract violations
  2026-05-03 17:10   ` [PATCH v4 6/6] perf debuginfo: " Ian Rogers
@ 2026-05-03 23:54     ` Namhyung Kim
  0 siblings, 0 replies; 31+ messages in thread
From: Namhyung Kim @ 2026-05-03 23:54 UTC (permalink / raw)
  To: Ian Rogers
  Cc: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo, Jiri Olsa,
	Adrian Hunter, James Clark, Zecheng Li, Masami Hiramatsu,
	linux-perf-users, linux-kernel

On Sun, May 03, 2026 at 10:10:32AM -0700, Ian Rogers wrote:
> Check return value of `dwfl_report_end` during offline initialization.
> Validate `dwfl_module_relocation_info` result before passing to `strcmp`
> to avoid potential segmentation faults.
> 
> Additionally:
>  - Fix a file descriptor leak in `debuginfo__init_offline_dwarf()` when
>    `dwfl_report_offline()` or subsequent setup calls fail.
> 
> Fixes: 6f1b6291cf73 ("perf tools: Add util/debuginfo.[ch] files")
> Assisted-by: Gemini-CLI:Google Gemini 3
> Signed-off-by: Ian Rogers <irogers@google.com>

Acked-by: Namhyung Kim <namhyung@kernel.org>

Thanks,
Namhyung

> ---
> v4:
>  - Fix file descriptor leaks in debuginfo init paths.
> ---
>  tools/perf/util/debuginfo.c | 9 ++++++---
>  1 file changed, 6 insertions(+), 3 deletions(-)
> 
> diff --git a/tools/perf/util/debuginfo.c b/tools/perf/util/debuginfo.c
> index 0e35c13abd04..84a78b30ceac 100644
> --- a/tools/perf/util/debuginfo.c
> +++ b/tools/perf/util/debuginfo.c
> @@ -42,6 +42,7 @@ static int debuginfo__init_offline_dwarf(struct debuginfo *dbg,
>  {
>  	GElf_Addr dummy;
>  	int fd;
> +	bool fd_consumed = false;
>  
>  	fd = open(path, O_RDONLY);
>  	if (fd < 0)
> @@ -55,6 +56,7 @@ static int debuginfo__init_offline_dwarf(struct debuginfo *dbg,
>  	dbg->mod = dwfl_report_offline(dbg->dwfl, "", "", fd);
>  	if (!dbg->mod)
>  		goto error;
> +	fd_consumed = true;
>  
>  	dbg->dbg = dwfl_module_getdwarf(dbg->mod, &dbg->bias);
>  	if (!dbg->dbg)
> @@ -62,13 +64,14 @@ static int debuginfo__init_offline_dwarf(struct debuginfo *dbg,
>  
>  	dwfl_module_build_id(dbg->mod, &dbg->build_id, &dummy);
>  
> -	dwfl_report_end(dbg->dwfl, NULL, NULL);
> +	if (dwfl_report_end(dbg->dwfl, NULL, NULL) != 0)
> +		goto error;
>  
>  	return 0;
>  error:
>  	if (dbg->dwfl)
>  		dwfl_end(dbg->dwfl);
> -	else
> +	if (!fd_consumed)
>  		close(fd);
>  	memset(dbg, 0, sizeof(*dbg));
>  
> @@ -167,7 +170,7 @@ int debuginfo__get_text_offset(struct debuginfo *dbg, Dwarf_Addr *offs,
>  	/* Search the relocation related .text section */
>  	for (i = 0; i < n; i++) {
>  		p = dwfl_module_relocation_info(dbg->mod, i, &shndx);
> -		if (strcmp(p, ".text") == 0) {
> +		if (p && strcmp(p, ".text") == 0) {
>  			/* OK, get the section header */
>  			scn = elf_getscn(elf, shndx);
>  			if (!scn)
> -- 
> 2.54.0.545.g6539524ca2-goog
> 

^ permalink raw reply	[flat|nested] 31+ messages in thread

* [PATCH v5 0/9] [PATCH v5 0/9] perf DWARF: Fix libdw API contract violations and crashes
  2026-05-03 17:10 ` [PATCH v4 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
                     ` (5 preceding siblings ...)
  2026-05-03 17:10   ` [PATCH v4 6/6] perf debuginfo: " Ian Rogers
@ 2026-05-04  8:12   ` Ian Rogers
  2026-05-04  8:12     ` [PATCH v5 1/9] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at Ian Rogers
                       ` (9 more replies)
  6 siblings, 10 replies; 31+ messages in thread
From: Ian Rogers @ 2026-05-04  8:12 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

This patch series addresses a number of DWARF/libdw error-handling
bugs and contract violations, preventing several real Userspace
segmentation faults and memory/FD leaks.

In v5, the series has been extensively restructured and polished based on 
comprehensive review feedback on v4, focusing on history granularity, 
bisectability, and defensive styling:

 - **Commit Splitting for Review Granularity**: Split the previously large 
     libdw contract fix patch into 4 granular commits to cleanly isolate 
     independent improvements: introducing clear_frames refactoring, fixing 
     ORDER_CALLER parent update corruption, adding line 0 support, and 
     consolidating core contract/leak cleanups.
 
 - **Bisectability & Correctness Hardening**: 
     - Fixed an unused variable compilation failure (-Werror) in the split 
       history to guarantee perfect git bisectability.
     - Fixed a line 0 fallback regression to ensure that if an optional call 
       line is missing but the call file is valid, we fallback to line 0 
       to preserve the filename rather than discarding the caller info entirely.

 - **Style & Robustness Polish**:
     - Standardized all newly introduced C++ style (//) comments to 
       preferred C style (/* ... */) comments.
     - Implemented explicit safe string duplication style fix in annotate-data.
     - Corrected CU DIE propagation context inside probe-finder.
     - Enhanced the Patch 2 commit message to explicitly detail the removal 
       of strict optional attribute aborts (decf) to clarify review queries.

 - **Tags Collected**: Integrated Acked-by tags from Namhyung Kim for Patch 1 
     and Patch 9.

v5:
 - Restructured series from 6 to 9 patches by splitting the libdw commit.
 - Fixed compilation bisectability and DWARF line 0 filename preservation regressions.
 - Standardized comment styles and applied safe duplication formatting fixes.
 - Updated commit messages with detailed optional attribute justifications.

v4:
 - Localized and squashed robust error handling, memory/FD cleanup
   paths, and safe DWARF printing.
https://lore.kernel.org/linux-perf-users/20260503171032.1559338-1-irogers@google.com/

v3:
 - Minor formatting fixes.
https://lore.kernel.org/linux-perf-users/20260503003552.1063540-1-irogers@google.com/

v2:
https://lore.kernel.org/lkml/20260502155656.478642-1-irogers@google.com/

v1:
https://lore.kernel.org/linux-perf-users/20260502064839.282422-1-irogers@google.com/

Ian Rogers (9):
  perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at
  perf dwarf-aux: Fix libdw API contract violations
  perf srcline: Introduce inline_node__clear_frames()
  perf libdw: Fix callchain parent update in ORDER_CALLER mode
  perf libdw: Support DWARF line 0 in inline list
  perf libdw: Fix libdw API contract violations and memory leaks
  perf probe-finder: Fix libdw API contract violations
  perf annotate-data: Fix libdw API contract violations
  perf debuginfo: Fix libdw API contract violations

 tools/perf/util/annotate-data.c |  27 +++++----
 tools/perf/util/debuginfo.c     |   9 ++-
 tools/perf/util/dwarf-aux.c     |  39 ++++++------
 tools/perf/util/dwarf-aux.h     |   5 ++
 tools/perf/util/libdw.c         |  72 +++++++++++++++++-----
 tools/perf/util/probe-finder.c  | 102 ++++++++++++++++++++------------
 tools/perf/util/srcline.c       |   9 ++-
 tools/perf/util/srcline.h       |   1 +
 8 files changed, 179 insertions(+), 85 deletions(-)

-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [PATCH v5 1/9] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at
  2026-05-04  8:12   ` [PATCH v5 0/9] [PATCH v5 0/9] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
@ 2026-05-04  8:12     ` Ian Rogers
  2026-05-04  8:12     ` [PATCH v5 2/9] perf dwarf-aux: Fix libdw API contract violations Ian Rogers
                       ` (8 subsequent siblings)
  9 siblings, 0 replies; 31+ messages in thread
From: Ian Rogers @ 2026-05-04  8:12 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

A segmentation fault was observed in `libdw` when running `perf kmem`
with `--page stat` on some workloads. The crash occurred deep inside
`libdw` (specifically in `dwarf_child` and `dwarf_diename`) when
processing DWARF information.

The root cause was improper error handling of `dwarf_getfuncs` in
`die_find_realfunc` and `die_find_tailfunc`.

`dwarf_getfuncs` returns:
 - `0` on success (when all functions have been processed).
 - A positive offset if the callback aborts early (e.g., via
   `DWARF_CB_ABORT` when a match is found).
 - `-1` on error.

The original code used `if (!dwarf_getfuncs(...)) return NULL;`. On
error (`-1`), `!-1` evaluates to `0` (false), bypassing the error
check. Execution then proceeded as if a match was found, returning
uninitialized stack memory (`die_mem`) to the caller
(`cu_walk_functions_at`). When `cu_walk_functions_at` passed this
uninitialized memory to `libdw` via `dwarf_diename`, it caused a
segmentation fault.

Fix this by correcting the error check to `if (dwarf_getfuncs(...) <= 0)`.

Fixes: e0d153c69040 ("perf-probe: Move dwarf library routines to dwarf-aux.{c, h}")
Fixes: d4c537e6bf86 ("perf probe: Ignore tail calls to probed functions")
Assisted-by: Gemini-CLI:Google Gemini 3
Acked-by: Namhyung Kim <namhyung@kernel.org>
Signed-off-by: Ian Rogers <irogers@google.com>
---
v5:
 - Added Acked-by tag.
---
 tools/perf/util/dwarf-aux.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index 92db2fccc788..109a166a6d19 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -171,7 +171,6 @@ int cu_walk_functions_at(Dwarf_Die *cu_die, Dwarf_Addr addr,
 	}
 
 	return ret;
-
 }
 
 /**
@@ -620,7 +619,7 @@ Dwarf_Die *die_find_tailfunc(Dwarf_Die *cu_die, Dwarf_Addr addr,
 	ad.addr = addr;
 	ad.die_mem = die_mem;
 	/* dwarf_getscopes can't find subprogram. */
-	if (!dwarf_getfuncs(cu_die, __die_search_func_tail_cb, &ad, 0))
+	if (dwarf_getfuncs(cu_die, __die_search_func_tail_cb, &ad, 0) <= 0)
 		return NULL;
 	else
 		return die_mem;
@@ -659,7 +658,7 @@ Dwarf_Die *die_find_realfunc(Dwarf_Die *cu_die, Dwarf_Addr addr,
 	ad.addr = addr;
 	ad.die_mem = die_mem;
 	/* dwarf_getscopes can't find subprogram. */
-	if (!dwarf_getfuncs(cu_die, __die_search_func_cb, &ad, 0))
+	if (dwarf_getfuncs(cu_die, __die_search_func_cb, &ad, 0) <= 0)
 		return NULL;
 	else
 		return die_mem;
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v5 2/9] perf dwarf-aux: Fix libdw API contract violations
  2026-05-04  8:12   ` [PATCH v5 0/9] [PATCH v5 0/9] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
  2026-05-04  8:12     ` [PATCH v5 1/9] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at Ian Rogers
@ 2026-05-04  8:12     ` Ian Rogers
  2026-05-04  8:12     ` [PATCH v5 3/9] perf srcline: Introduce inline_node__clear_frames() Ian Rogers
                       ` (7 subsequent siblings)
  9 siblings, 0 replies; 31+ messages in thread
From: Ian Rogers @ 2026-05-04  8:12 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Check return values of `dwarf_decl_line` (where non-optional),
`dwarf_getfuncs`, and `dwarf_lineaddr` to prevent using uninitialized
stack variables or incorrectly reporting success on failure.

For the root DIE in `die_walk_lines()`, `dwarf_decl_line` and
`die_get_decl_file` are optional and their failures are handled
gracefully to avoid breaking line walking on valid functions.
Specifically, remove the strict `!decf` (declared file) check that
would prematurely abort line walking on generated or artificial
functions lacking this optional attribute.

Additionally:
 - Add NULL pointer protection for `strcmp()` in `die_walk_lines()`
   when `inf` or `decf` are NULL to prevent crashes on generated
   code.
 - Use `dwarf_attr_integrate` in `die_get_data_member_location` to
   correctly resolve inherited member locations (e.g. via abstract
   origin or specification).

Fixes: 57f95bf5f882 ("perf probe: Show correct statement line number by perf probe -l")
Fixes: 3f4460a28fb2 ("perf probe: Filter out redundant inline-instances")
Fixes: 75186a9b09e4 ("perf probe: Fix to show lines of sys_ functions correctly")
Fixes: e0d153c69040 ("perf-probe: Move dwarf library routines to dwarf-aux.{c, h}")
Fixes: 6243b9dc4c99 ("perf probe: Move dwarf specific functions to dwarf-aux.c")
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
v5:
 - Updated commit message to explain optional DWARF attributes and decf check removal.
v4:
 - Fix strcmp(NULL) risk and inherited member location fallbacks in dwarf-aux.c.
---
 tools/perf/util/dwarf-aux.c | 34 +++++++++++++++++-----------------
 tools/perf/util/dwarf-aux.h |  5 +++++
 2 files changed, 22 insertions(+), 17 deletions(-)

diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index 109a166a6d19..d7160f87ac7d 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -125,7 +125,8 @@ int cu_find_lineinfo(Dwarf_Die *cu_die, Dwarf_Addr addr,
 	    && die_entrypc(&die_mem, &faddr) == 0 &&
 	    faddr == addr) {
 		*fname = die_get_decl_file(&die_mem);
-		dwarf_decl_line(&die_mem, lineno);
+		if (dwarf_decl_line(&die_mem, lineno) != 0)
+			return -ENOENT;
 		goto out;
 	}
 
@@ -459,7 +460,7 @@ int die_get_data_member_location(Dwarf_Die *mb_die, Dwarf_Word *offs)
 	size_t nexpr;
 	int ret;
 
-	if (dwarf_attr(mb_die, DW_AT_data_member_location, &attr) == NULL)
+	if (dwarf_attr_integrate(mb_die, DW_AT_data_member_location, &attr) == NULL)
 		return -ENOENT;
 
 	if (dwarf_formudata(&attr, offs) != 0) {
@@ -795,8 +796,7 @@ static int __die_walk_instances_cb(Dwarf_Die *inst, void *data)
 
 	/* Ignore redundant instances */
 	if (dwarf_tag(inst) == DW_TAG_inlined_subroutine) {
-		dwarf_decl_line(origin, &tmp);
-		if (die_get_call_lineno(inst) == tmp) {
+		if (dwarf_decl_line(origin, &tmp) == 0 && die_get_call_lineno(inst) == tmp) {
 			tmp = die_get_decl_fileno(origin);
 			if (die_get_call_fileno(inst) == tmp)
 				return DIE_FIND_CB_CONTINUE;
@@ -950,11 +950,6 @@ int die_walk_lines(Dwarf_Die *rt_die, line_walk_callback_t callback, void *data)
 		cu_die = dwarf_diecu(rt_die, &die_mem, NULL, NULL);
 		dwarf_decl_line(rt_die, &decl);
 		decf = die_get_decl_file(rt_die);
-		if (!decf) {
-			pr_debug2("Failed to get the declared file name of %s\n",
-				  dwarf_diename(rt_die));
-			return -EINVAL;
-		}
 	} else
 		cu_die = rt_die;
 	if (!cu_die) {
@@ -998,11 +993,12 @@ int die_walk_lines(Dwarf_Die *rt_die, line_walk_callback_t callback, void *data)
 			if (die_find_inlinefunc(rt_die, addr, &die_mem)) {
 				/* Call-site check */
 				inf = die_get_call_file(&die_mem);
-				if ((inf && !strcmp(inf, decf)) &&
+				if ((inf == decf || (inf && decf && !strcmp(inf, decf))) &&
 				    die_get_call_lineno(&die_mem) == lineno)
 					goto found;
 
-				dwarf_decl_line(&die_mem, &inl);
+				if (dwarf_decl_line(&die_mem, &inl) != 0)
+					inl = 0;
 				if (inl != decl ||
 				    decf != die_get_decl_file(&die_mem))
 					continue;
@@ -1034,8 +1030,10 @@ int die_walk_lines(Dwarf_Die *rt_die, line_walk_callback_t callback, void *data)
 			.data = data,
 			.retval = 0,
 		};
-		dwarf_getfuncs(cu_die, __die_walk_culines_cb, &param, 0);
-		ret = param.retval;
+		if (dwarf_getfuncs(cu_die, __die_walk_culines_cb, &param, 0) < 0)
+			ret = -EINVAL;
+		else
+			ret = param.retval;
 	}
 
 	return ret;
@@ -1939,10 +1937,12 @@ static bool die_get_postprologue_addr(unsigned long entrypc_idx,
 			break;
 	}
 
-	dwarf_lineaddr(line, postprologue_addr);
-	if (*postprologue_addr >= highpc)
-		dwarf_lineaddr(dwarf_onesrcline(lines, i - 1),
-			       postprologue_addr);
+	if (dwarf_lineaddr(line, postprologue_addr) != 0)
+		return false;
+	if (*postprologue_addr >= highpc) {
+		if (dwarf_lineaddr(dwarf_onesrcline(lines, i - 1), postprologue_addr) != 0)
+			return false;
+	}
 
 	return true;
 }
diff --git a/tools/perf/util/dwarf-aux.h b/tools/perf/util/dwarf-aux.h
index a79968a2e573..161f0bf980b6 100644
--- a/tools/perf/util/dwarf-aux.h
+++ b/tools/perf/util/dwarf-aux.h
@@ -10,6 +10,11 @@
 #include <elfutils/libdwfl.h>
 #include <elfutils/version.h>
 
+static inline const char *die_name(Dwarf_Die *die)
+{
+	return dwarf_diename(die) ?: "<unknown>";
+}
+
 struct strbuf;
 
 /* Find the realpath of the target file */
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v5 3/9] perf srcline: Introduce inline_node__clear_frames()
  2026-05-04  8:12   ` [PATCH v5 0/9] [PATCH v5 0/9] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
  2026-05-04  8:12     ` [PATCH v5 1/9] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at Ian Rogers
  2026-05-04  8:12     ` [PATCH v5 2/9] perf dwarf-aux: Fix libdw API contract violations Ian Rogers
@ 2026-05-04  8:12     ` Ian Rogers
  2026-05-04  8:12     ` [PATCH v5 4/9] perf libdw: Fix callchain parent update in ORDER_CALLER mode Ian Rogers
                       ` (6 subsequent siblings)
  9 siblings, 0 replies; 31+ messages in thread
From: Ian Rogers @ 2026-05-04  8:12 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Introduce inline_node__clear_frames() to clean up partial allocations.
This is a prerequisite for error handling in libdw inline unwinding.

Signed-off-by: Ian Rogers <irogers@google.com>
---
v5:
 - Split from original Patch 3/6 into its own commit.
---
 tools/perf/util/srcline.c | 9 ++++++++-
 tools/perf/util/srcline.h | 1 +
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/tools/perf/util/srcline.c b/tools/perf/util/srcline.c
index db164d258163..62884428fb5a 100644
--- a/tools/perf/util/srcline.c
+++ b/tools/perf/util/srcline.c
@@ -429,10 +429,13 @@ struct inline_node *dso__parse_addr_inlines(struct dso *dso, u64 addr,
 	return addr2inlines(dso_name, addr, dso, sym);
 }
 
-void inline_node__delete(struct inline_node *node)
+void inline_node__clear_frames(struct inline_node *node)
 {
 	struct inline_list *ilist, *tmp;
 
+	if (node == NULL)
+		return;
+
 	list_for_each_entry_safe(ilist, tmp, &node->val, list) {
 		list_del_init(&ilist->list);
 		zfree_srcline(&ilist->srcline);
@@ -441,7 +444,11 @@ void inline_node__delete(struct inline_node *node)
 			symbol__delete(ilist->symbol);
 		free(ilist);
 	}
+}
 
+void inline_node__delete(struct inline_node *node)
+{
+	inline_node__clear_frames(node);
 	free(node);
 }
 
diff --git a/tools/perf/util/srcline.h b/tools/perf/util/srcline.h
index 7c37b3bf9ce7..1018cbc886d6 100644
--- a/tools/perf/util/srcline.h
+++ b/tools/perf/util/srcline.h
@@ -47,6 +47,7 @@ struct inline_node *dso__parse_addr_inlines(struct dso *dso, u64 addr,
 					    struct symbol *sym);
 /* free resources associated to the inline node list */
 void inline_node__delete(struct inline_node *node);
+void inline_node__clear_frames(struct inline_node *node);
 
 /* insert the inline node list into the DSO, which will take ownership */
 void inlines__tree_insert(struct rb_root_cached *tree,
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v5 4/9] perf libdw: Fix callchain parent update in ORDER_CALLER mode
  2026-05-04  8:12   ` [PATCH v5 0/9] [PATCH v5 0/9] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
                       ` (2 preceding siblings ...)
  2026-05-04  8:12     ` [PATCH v5 3/9] perf srcline: Introduce inline_node__clear_frames() Ian Rogers
@ 2026-05-04  8:12     ` Ian Rogers
  2026-05-04  8:12     ` [PATCH v5 5/9] perf libdw: Support DWARF line 0 in inline list Ian Rogers
                       ` (5 subsequent siblings)
  9 siblings, 0 replies; 31+ messages in thread
From: Ian Rogers @ 2026-05-04  8:12 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Fix the parent srcline lookup in `libdw_a2l_cb()` to target the
correct parent node depending on the callchain order
(ORDER_CALLER/ORDER_CALLEE).

This ensures inline callchains are not corrupted when nest depth > 2.

Signed-off-by: Ian Rogers <irogers@google.com>
---
v5:
 - Split from original Patch 3/6 into its own commit.
 - Fixed bisectability failure by removing unused `ilist` variable declaration.
---
 tools/perf/util/libdw.c | 20 +++++++++++++-------
 1 file changed, 13 insertions(+), 7 deletions(-)

diff --git a/tools/perf/util/libdw.c b/tools/perf/util/libdw.c
index 216977884103..301642084c69 100644
--- a/tools/perf/util/libdw.c
+++ b/tools/perf/util/libdw.c
@@ -4,6 +4,7 @@
 #include "srcline.h"
 #include "symbol.h"
 #include "dwarf-aux.h"
+#include "callchain.h"
 #include <fcntl.h>
 #include <unistd.h>
 #include <elfutils/libdwfl.h>
@@ -80,7 +81,6 @@ static int libdw_a2l_cb(Dwarf_Die *die, void *_args)
 	struct symbol *inline_sym = new_inline_sym(args->dso, args->sym, dwarf_diename(die));
 	const char *call_fname = die_get_call_file(die);
 	char *call_srcline = srcline__unknown;
-	struct inline_list *ilist;
 
 	if (!inline_sym)
 		return -ENOMEM;
@@ -89,14 +89,20 @@ static int libdw_a2l_cb(Dwarf_Die *die, void *_args)
 	if (call_fname)
 		call_srcline = srcline_from_fileline(call_fname, die_get_call_lineno(die));
 
-	list_for_each_entry(ilist, &args->node->val, list) {
-		if (args->leaf_srcline == ilist->srcline)
+	if (!list_empty(&args->node->val)) {
+		struct inline_list *parent;
+
+		if (callchain_param.order == ORDER_CALLEE)
+			parent = list_first_entry(&args->node->val, struct inline_list, list);
+		else
+			parent = list_last_entry(&args->node->val, struct inline_list, list);
+
+		if (args->leaf_srcline == parent->srcline)
 			args->leaf_srcline_used = false;
-		else if (ilist->srcline != srcline__unknown)
-			free(ilist->srcline);
-		ilist->srcline =  call_srcline;
+		else if (parent->srcline != srcline__unknown)
+			free(parent->srcline);
+		parent->srcline = call_srcline;
 		call_srcline = NULL;
-		break;
 	}
 	if (call_srcline && call_srcline != srcline__unknown)
 		free(call_srcline);
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v5 5/9] perf libdw: Support DWARF line 0 in inline list
  2026-05-04  8:12   ` [PATCH v5 0/9] [PATCH v5 0/9] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
                       ` (3 preceding siblings ...)
  2026-05-04  8:12     ` [PATCH v5 4/9] perf libdw: Fix callchain parent update in ORDER_CALLER mode Ian Rogers
@ 2026-05-04  8:12     ` Ian Rogers
  2026-05-04  8:12     ` [PATCH v5 6/9] perf libdw: Fix libdw API contract violations and memory leaks Ian Rogers
                       ` (4 subsequent siblings)
  9 siblings, 0 replies; 31+ messages in thread
From: Ian Rogers @ 2026-05-04  8:12 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Allow DWARF line 0 in `libdw_a2l_cb()`, as it is a valid
reference for compiler-generated code.

Filter `die_get_call_lineno` error codes (negative values), but
fallback to line 0 if `call_fname` is present to preserve the
caller's filename instead of discarding it entirely.

Signed-off-by: Ian Rogers <irogers@google.com>
---
v5:
 - Split from original Patch 3/6 into its own commit.
 - Fixed fallback logic for missing call lines to preserve filename by defaulting to line 0.
---
 tools/perf/util/libdw.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/tools/perf/util/libdw.c b/tools/perf/util/libdw.c
index 301642084c69..196b9cdf51b2 100644
--- a/tools/perf/util/libdw.c
+++ b/tools/perf/util/libdw.c
@@ -80,6 +80,7 @@ static int libdw_a2l_cb(Dwarf_Die *die, void *_args)
 	struct libdw_a2l_cb_args *args  = _args;
 	struct symbol *inline_sym = new_inline_sym(args->dso, args->sym, dwarf_diename(die));
 	const char *call_fname = die_get_call_file(die);
+	int call_lineno = die_get_call_lineno(die);
 	char *call_srcline = srcline__unknown;
 
 	if (!inline_sym)
@@ -87,7 +88,7 @@ static int libdw_a2l_cb(Dwarf_Die *die, void *_args)
 
 	/* Assign caller information to the parent. */
 	if (call_fname)
-		call_srcline = srcline_from_fileline(call_fname, die_get_call_lineno(die));
+		call_srcline = srcline_from_fileline(call_fname, call_lineno >= 0 ? call_lineno : 0);
 
 	if (!list_empty(&args->node->val)) {
 		struct inline_list *parent;
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v5 6/9] perf libdw: Fix libdw API contract violations and memory leaks
  2026-05-04  8:12   ` [PATCH v5 0/9] [PATCH v5 0/9] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
                       ` (4 preceding siblings ...)
  2026-05-04  8:12     ` [PATCH v5 5/9] perf libdw: Support DWARF line 0 in inline list Ian Rogers
@ 2026-05-04  8:12     ` Ian Rogers
  2026-05-04  8:12     ` [PATCH v5 7/9] perf probe-finder: Fix libdw API contract violations Ian Rogers
                       ` (3 subsequent siblings)
  9 siblings, 0 replies; 31+ messages in thread
From: Ian Rogers @ 2026-05-04  8:12 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Check return values of `dwfl_report_end` and `dwfl_module_addrdie`
to prevent using uninitialized stack variables or reporting success on
failure.

Additionally:
 - Ensure `*file` is freed and inline frames are cleared on error in
   `libdw__addr2line()` to prevent memory leaks and duplicated
   callchains when falling back to other unwinders.
 - Use `die_name()` safe wrapper inside the inline function unwinding
   callback (`libdw_a2l_cb`).
 - Refactor `libdw_a2l_cb`'s repeated memory error handling/cleanup
   paths using a cleaner goto control flow.

Fixes: b7a2b011e962 ("perf powerpc: Unify the skip-callchain-idx libdw with that for addr2line")
Fixes: 88c51002d06f ("perf addr2line: Add a libdw implementation")
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
v5:
 - Split from original Patch 3/6. Refactored libdw_a2l_cb error handling with goto.
---
 tools/perf/util/libdw.c | 49 ++++++++++++++++++++++++++++++++++++-----
 1 file changed, 43 insertions(+), 6 deletions(-)

diff --git a/tools/perf/util/libdw.c b/tools/perf/util/libdw.c
index 196b9cdf51b2..84713b2a7ad5 100644
--- a/tools/perf/util/libdw.c
+++ b/tools/perf/util/libdw.c
@@ -61,7 +61,10 @@ struct Dwfl *dso__libdw_dwfl(struct dso *dso)
 		return NULL;
 	}
 
-	dwfl_report_end(dwfl, /*removed=*/NULL, /*arg=*/NULL);
+	if (dwfl_report_end(dwfl, /*removed=*/NULL, /*arg=*/NULL) != 0) {
+		dwfl_end(dwfl);
+		return NULL;
+	}
 	dso__set_libdw(dso, dwfl);
 
 	return dwfl;
@@ -73,18 +76,19 @@ struct libdw_a2l_cb_args {
 	struct inline_node *node;
 	char *leaf_srcline;
 	bool leaf_srcline_used;
+	int err;
 };
 
 static int libdw_a2l_cb(Dwarf_Die *die, void *_args)
 {
 	struct libdw_a2l_cb_args *args  = _args;
-	struct symbol *inline_sym = new_inline_sym(args->dso, args->sym, dwarf_diename(die));
+	struct symbol *inline_sym = new_inline_sym(args->dso, args->sym, die_name(die));
 	const char *call_fname = die_get_call_file(die);
 	int call_lineno = die_get_call_lineno(die);
 	char *call_srcline = srcline__unknown;
 
 	if (!inline_sym)
-		return -ENOMEM;
+		goto abort_enomem;
 
 	/* Assign caller information to the parent. */
 	if (call_fname)
@@ -110,12 +114,27 @@ static int libdw_a2l_cb(Dwarf_Die *die, void *_args)
 
 	/* Add this symbol to the chain as the leaf. */
 	if (!args->leaf_srcline_used) {
-		inline_list__append_tail(inline_sym, args->leaf_srcline, args->node);
+		if (inline_list__append_tail(inline_sym, args->leaf_srcline, args->node) != 0)
+			goto abort_delete_sym;
 		args->leaf_srcline_used = true;
 	} else {
-		inline_list__append_tail(inline_sym, strdup(args->leaf_srcline), args->node);
+		char *srcline = strdup(args->leaf_srcline);
+
+		if (!srcline)
+			goto abort_delete_sym;
+		if (inline_list__append_tail(inline_sym, srcline, args->node) != 0) {
+			free(srcline);
+			goto abort_delete_sym;
+		}
 	}
 	return 0;
+
+abort_delete_sym:
+	if (inline_sym->inlined)
+		symbol__delete(inline_sym);
+abort_enomem:
+	args->err = -ENOMEM;
+	return DWARF_CB_ABORT;
 }
 
 int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr,
@@ -169,11 +188,29 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr,
 			.leaf_srcline = srcline_from_fileline(src ?: "<unknown>", lineno),
 		};
 
+		if (!args.leaf_srcline) {
+			if (file && *file) {
+				free(*file);
+				*file = NULL;
+			}
+			return 0;
+		}
+
 		/* Walk from the parent down to the leaf. */
-		cu_walk_functions_at(cudie, addr, libdw_a2l_cb, &args);
+		if (cudie)
+			cu_walk_functions_at(cudie, addr, libdw_a2l_cb, &args);
 
 		if (!args.leaf_srcline_used)
 			free(args.leaf_srcline);
+
+		if (args.err) {
+			if (file && *file) {
+				free(*file);
+				*file = NULL;
+			}
+			inline_node__clear_frames(node);
+			return 0;
+		}
 	}
 	return 1;
 }
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v5 7/9] perf probe-finder: Fix libdw API contract violations
  2026-05-04  8:12   ` [PATCH v5 0/9] [PATCH v5 0/9] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
                       ` (5 preceding siblings ...)
  2026-05-04  8:12     ` [PATCH v5 6/9] perf libdw: Fix libdw API contract violations and memory leaks Ian Rogers
@ 2026-05-04  8:12     ` Ian Rogers
  2026-05-04  8:12     ` [PATCH v5 8/9] perf annotate-data: " Ian Rogers
                       ` (2 subsequent siblings)
  9 siblings, 0 replies; 31+ messages in thread
From: Ian Rogers @ 2026-05-04  8:12 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Check return values of `dwarf_formsdata`, `dwarf_entrypc`,
`dwarf_highpc`, `dwarf_bytesize`, `dwarf_attr`, `dwarf_decl_line`,
`dwarf_getfuncs`, and `dwarf_formref_die`. Validate `dwarf_diename` and
`dwarf_diecu` results to prevent potential crashes. Fix C90 mixed
declarations.

Additionally:
 - Avoid vfprintf undefined behavior with NULL strings by using the
   `die_name()` helper for `dwarf_diename()` in `pr_*` calls,
   including when warning about tail calls.
 - Prevent NULL pointer dereference in `convert_variable_fields()`
   when processing array elements for variables in registers.
 - Fallback to offset 0 in `line_range_search_cb()` instead of
   skipping functions without `DW_AT_decl_line`.
 - Relax `dwarf_getfuncs` error checking in
   `find_probe_point_by_func()` and `find_line_range_by_func()` to
   prevent premature CU search aborts, ensuring robustness against
   corrupted CUs.

Fixes: 66f69b219716 ("perf probe: Support DW_AT_const_value constant value")
Fixes: 3d918a12a1b3 ("perf probe: Find fentry mcount fuzzed parameter location")
Fixes: bcfc082150c6 ("perf probe: Remove redundant dwarf functions")
Fixes: 221d061182b8 ("perf probe: Fix to search local variables in appropriate scope")
Fixes: b55a87ade383 ("perf probe: Remove die() from probe-finder code")
Fixes: 4c859351226c ("perf probe: Support glob wildcards for function name")
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
v5:
 - Safe DWARF name printing using die_name().
 - Corrected CU DIE propagation.
 - Standardized comment style to /* ... */.
v4:
 - Safe DWARF name printing with die_name() to avoid NULL formatting crashes.
 - Fix NULL dereference in register variable array lookups.
 - Fix robust CU search loops by continuing on getfuncs errors.
---
 tools/perf/util/probe-finder.c | 102 +++++++++++++++++++++------------
 1 file changed, 65 insertions(+), 37 deletions(-)

diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c
index 64328abeef8b..f3f9a1573502 100644
--- a/tools/perf/util/probe-finder.c
+++ b/tools/perf/util/probe-finder.c
@@ -79,7 +79,7 @@ static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr,
 	unsigned int regn;
 	Dwarf_Word offs = 0;
 	bool ref = false;
-	const char *regs;
+	const char *regs, *name;
 	int ret, ret2 = 0;
 
 	if (dwarf_attr(vr_die, DW_AT_external, &attr) != NULL)
@@ -93,7 +93,8 @@ static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr,
 		if (!tvar)
 			return 0;
 
-		dwarf_formsdata(&attr, &snum);
+		if (dwarf_formsdata(&attr, &snum) != 0)
+			return -ENOENT;
 		ret = asprintf(&tvar->value, "\\%ld", (long)snum);
 
 		return ret < 0 ? -ENOMEM : 0;
@@ -103,8 +104,7 @@ static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr,
 	if (dwarf_attr(vr_die, DW_AT_location, &attr) == NULL)
 		return -EINVAL;	/* Broken DIE ? */
 	if (dwarf_getlocation_addr(&attr, addr, &op, &nops, 1) <= 0) {
-		ret = dwarf_entrypc(sp_die, &tmp);
-		if (ret)
+		if (dwarf_entrypc(sp_die, &tmp) != 0)
 			return -ENOENT;
 
 		if (probe_conf.show_location_range &&
@@ -115,8 +115,7 @@ static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr,
 			return -ENOENT;
 		}
 
-		ret = dwarf_highpc(sp_die, &tmp);
-		if (ret)
+		if (dwarf_highpc(sp_die, &tmp) != 0)
 			return -ENOENT;
 		/*
 		 * This is fuzzed by fentry mcount. We try to find the
@@ -138,12 +137,16 @@ static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr,
 static_var:
 		if (!tvar)
 			return ret2;
+
 		/* Static variables on memory (not stack), make @varname */
-		ret = strlen(dwarf_diename(vr_die));
+		name = dwarf_diename(vr_die);
+		if (!name)
+			return -ENOENT;
+		ret = strlen(name);
 		tvar->value = zalloc(ret + 2);
 		if (tvar->value == NULL)
 			return -ENOMEM;
-		snprintf(tvar->value, ret + 2, "@%s", dwarf_diename(vr_die));
+		snprintf(tvar->value, ret + 2, "@%s", name);
 		tvar->ref = alloc_trace_arg_ref((long)offs);
 		if (tvar->ref == NULL)
 			return -ENOMEM;
@@ -234,13 +237,14 @@ static int convert_variable_type(Dwarf_Die *vr_die,
 	}
 
 	if (die_get_real_type(vr_die, &type) == NULL) {
-		pr_warning("Failed to get a type information of %s.\n",
-			   dwarf_diename(vr_die));
+		const char *name = dwarf_diename(vr_die);
+
+		pr_warning("Failed to get a type information of %s.\n", name ?: "<unknown>");
 		return -ENOENT;
 	}
 
 	pr_debug("%s type is %s.\n",
-		 dwarf_diename(vr_die), dwarf_diename(&type));
+		 die_name(vr_die), die_name(&type));
 
 	if (cast && (!strcmp(cast, "string") || !strcmp(cast, "ustring"))) {
 		/* String type */
@@ -249,7 +253,7 @@ static int convert_variable_type(Dwarf_Die *vr_die,
 		    ret != DW_TAG_array_type) {
 			pr_warning("Failed to cast into string: "
 				   "%s(%s) is not a pointer nor array.\n",
-				   dwarf_diename(vr_die), dwarf_diename(&type));
+				   die_name(vr_die), die_name(&type));
 			return -EINVAL;
 		}
 		if (die_get_real_type(&type, &type) == NULL) {
@@ -272,7 +276,7 @@ static int convert_variable_type(Dwarf_Die *vr_die,
 		    !die_compare_name(&type, "unsigned char")) {
 			pr_warning("Failed to cast into string: "
 				   "%s is not (unsigned) char *.\n",
-				   dwarf_diename(vr_die));
+				   die_name(vr_die));
 			return -EINVAL;
 		}
 		tvar->type = strdup(cast);
@@ -299,7 +303,7 @@ static int convert_variable_type(Dwarf_Die *vr_die,
 	/* Check the bitwidth */
 	if (ret > MAX_BASIC_TYPE_BITS) {
 		pr_info("%s exceeds max-bitwidth. Cut down to %d bits.\n",
-			dwarf_diename(&type), MAX_BASIC_TYPE_BITS);
+			die_name(&type), MAX_BASIC_TYPE_BITS);
 		ret = MAX_BASIC_TYPE_BITS;
 	}
 	ret = snprintf(buf, 16, "%c%d", prefix, ret);
@@ -333,12 +337,14 @@ static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname,
 		pr_warning("Failed to get the type of %s.\n", varname);
 		return -ENOENT;
 	}
-	pr_debug2("Var real type: %s (%x)\n", dwarf_diename(&type),
+	pr_debug2("Var real type: %s (%x)\n", die_name(&type),
 		  (unsigned)dwarf_dieoffset(&type));
 	tag = dwarf_tag(&type);
 
 	if (field->name[0] == '[' &&
 	    (tag == DW_TAG_array_type || tag == DW_TAG_pointer_type)) {
+		int bsize;
+
 		/* Save original type for next field or type */
 		memcpy(die_mem, &type, sizeof(*die_mem));
 		/* Get the type of this array */
@@ -346,7 +352,7 @@ static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname,
 			pr_warning("Failed to get the type of %s.\n", varname);
 			return -ENOENT;
 		}
-		pr_debug2("Array real type: %s (%x)\n", dwarf_diename(&type),
+		pr_debug2("Array real type: %s (%x)\n", die_name(&type),
 			 (unsigned)dwarf_dieoffset(&type));
 		if (tag == DW_TAG_pointer_type) {
 			ref = zalloc(sizeof(struct probe_trace_arg_ref));
@@ -357,7 +363,15 @@ static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname,
 			else
 				*ref_ptr = ref;
 		}
-		ref->offset += dwarf_bytesize(&type) * field->index;
+		bsize = dwarf_bytesize(&type);
+
+		if (bsize < 0)
+			return -EINVAL;
+		if (!ref) {
+			pr_warning("Array indexing not supported for variables in registers.\n");
+			return -ENOTSUP;
+		}
+		ref->offset += bsize * field->index;
 		ref->user_access = user_access;
 		goto next;
 	} else if (tag == DW_TAG_pointer_type) {
@@ -414,7 +428,7 @@ static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname,
 
 	if (die_find_member(&type, field->name, die_mem) == NULL) {
 		pr_warning("%s(type:%s) has no member %s.\n", varname,
-			   dwarf_diename(&type), field->name);
+			   die_name(&type), field->name);
 		return -EINVAL;
 	}
 
@@ -461,7 +475,7 @@ static int convert_variable(Dwarf_Die *vr_die, struct probe_finder *pf)
 	int ret;
 
 	pr_debug("Converting variable %s into trace event.\n",
-		 dwarf_diename(vr_die));
+		 die_name(vr_die));
 
 	ret = convert_variable_location(vr_die, pf->addr, pf->fb_ops,
 					&pf->sp_die, pf, pf->tvar);
@@ -542,7 +556,7 @@ static int convert_to_trace_point(Dwarf_Die *sp_die, Dwfl_Module *mod,
 	/* Verify the address is correct */
 	if (!dwarf_haspc(sp_die, paddr)) {
 		pr_warning("Specified offset is out of %s\n",
-			   dwarf_diename(sp_die));
+			   die_name(sp_die));
 		return -EINVAL;
 	}
 
@@ -599,7 +613,7 @@ static int call_probe_finder(Dwarf_Die *sc_die, struct probe_finder *pf)
 		if (!die_find_realfunc(&pf->cu_die, pf->addr, &pf->sp_die)) {
 			if (die_find_tailfunc(&pf->cu_die, pf->addr, &pf->sp_die)) {
 				pr_warning("Ignoring tail call from %s\n",
-						dwarf_diename(&pf->sp_die));
+						die_name(&pf->sp_die));
 				return 0;
 			} else {
 				pr_warning("Failed to find probe point in any "
@@ -611,10 +625,16 @@ static int call_probe_finder(Dwarf_Die *sc_die, struct probe_finder *pf)
 		memcpy(&pf->sp_die, sc_die, sizeof(Dwarf_Die));
 
 	/* Get the frame base attribute/ops from subprogram */
-	dwarf_attr(&pf->sp_die, DW_AT_frame_base, &fb_attr);
-	ret = dwarf_getlocation_addr(&fb_attr, pf->addr, &pf->fb_ops, &nops, 1);
-	if (ret <= 0 || nops == 0) {
+	if (dwarf_attr(&pf->sp_die, DW_AT_frame_base, &fb_attr) == NULL) {
 		pf->fb_ops = NULL;
+	} else {
+		ret = dwarf_getlocation_addr(&fb_attr, pf->addr, &pf->fb_ops, &nops, 1);
+		if (ret <= 0 || nops == 0)
+			pf->fb_ops = NULL;
+	}
+
+	if (pf->fb_ops == NULL) {
+		/* Not supported */
 	} else if (nops == 1 && pf->fb_ops[0].atom == DW_OP_call_frame_cfa &&
 		   (pf->cfi_eh != NULL || pf->cfi_dbg != NULL)) {
 		if ((dwarf_cfi_addrframe(pf->cfi_eh, pf->addr, &frame) != 0 &&
@@ -667,8 +687,8 @@ static int find_best_scope_cb(Dwarf_Die *fn_die, void *data)
 		}
 	} else {
 		/* With the line number, find the nearest declared DIE */
-		dwarf_decl_line(fn_die, &lno);
-		if (lno < fsp->line && fsp->diff > fsp->line - lno) {
+		if (dwarf_decl_line(fn_die, &lno) == 0 && lno < fsp->line &&
+		    fsp->diff > fsp->line - lno) {
 			/* Keep a candidate and continue */
 			fsp->diff = fsp->line - lno;
 			memcpy(fsp->die_mem, fn_die, sizeof(Dwarf_Die));
@@ -924,12 +944,12 @@ static int probe_point_inline_cb(Dwarf_Die *in_die, void *data)
 		/* Get probe address */
 		if (die_entrypc(in_die, &addr) != 0) {
 			pr_warning("Failed to get entry address of %s.\n",
-				   dwarf_diename(in_die));
+				   die_name(in_die));
 			return -ENOENT;
 		}
 		if (addr == 0) {
 			pr_debug("%s has no valid entry address. skipped.\n",
-				 dwarf_diename(in_die));
+				 die_name(in_die));
 			return -ENOENT;
 		}
 		pf->addr = addr;
@@ -971,12 +991,13 @@ static int probe_point_search_cb(Dwarf_Die *sp_die, void *data)
 	if (pp->file && fname && strtailcmp(pp->file, fname))
 		return DWARF_CB_OK;
 
-	pr_debug("Matched function: %s [%lx]\n", dwarf_diename(sp_die),
+	pr_debug("Matched function: %s [%lx]\n", die_name(sp_die),
 		 (unsigned long)dwarf_dieoffset(sp_die));
 	pf->fname = fname;
 	pf->abstrace_dieoffset = dwarf_dieoffset(sp_die);
 	if (pp->line) { /* Function relative line */
-		dwarf_decl_line(sp_die, &pf->lno);
+		if (dwarf_decl_line(sp_die, &pf->lno) != 0)
+			return DWARF_CB_OK;
 		pf->lno += pp->line;
 		param->retval = find_probe_point_by_line(pf);
 	} else if (die_is_func_instance(sp_die)) {
@@ -985,7 +1006,7 @@ static int probe_point_search_cb(Dwarf_Die *sp_die, void *data)
 		/* But in some case the entry address is 0 */
 		if (pf->addr == 0) {
 			pr_debug("%s has no entry PC. Skipped\n",
-				 dwarf_diename(sp_die));
+				 die_name(sp_die));
 			param->retval = 0;
 		/* Real function */
 		} else if (pp->lazy_line)
@@ -1018,7 +1039,8 @@ static int find_probe_point_by_func(struct probe_finder *pf)
 {
 	struct dwarf_callback_param _param = {.data = (void *)pf,
 					      .retval = 0};
-	dwarf_getfuncs(&pf->cu_die, probe_point_search_cb, &_param, 0);
+	if (dwarf_getfuncs(&pf->cu_die, probe_point_search_cb, &_param, 0) < 0)
+		pr_debug("Failed to get functions from CU\n");
 	return _param.retval;
 }
 
@@ -1207,7 +1229,8 @@ static int copy_variables_cb(Dwarf_Die *die_mem, void *data)
 		 * points to correct die.
 		 */
 		if (dwarf_attr(die_mem, DW_AT_abstract_origin, &attr)) {
-			dwarf_formref_die(&attr, &var_die);
+			if (dwarf_formref_die(&attr, &var_die) == NULL)
+				goto out;
 			if (pf->abstrace_dieoffset != dwarf_dieoffset(&var_die))
 				goto out;
 		}
@@ -1293,13 +1316,16 @@ static int add_probe_trace_event(Dwarf_Die *sc_die, struct probe_finder *pf)
 	if (ret < 0)
 		goto end;
 
-	tev->point.realname = strdup(dwarf_diename(sc_die));
+	tev->point.realname = strdup(die_name(sc_die));
 	if (!tev->point.realname) {
 		ret = -ENOMEM;
 		goto end;
 	}
 
-	tev->lang = dwarf_srclang(dwarf_diecu(sc_die, &pf->cu_die, NULL, NULL));
+	if (dwarf_diecu(sc_die, &pf->cu_die, NULL, NULL) != NULL)
+		tev->lang = dwarf_srclang(&pf->cu_die);
+	else
+		tev->lang = DW_LANG_C; /* Fallback */
 
 	pr_debug("Probe point found: %s+%lu\n", tev->point.symbol,
 		 tev->point.offset);
@@ -1794,7 +1820,8 @@ static int line_range_search_cb(Dwarf_Die *sp_die, void *data)
 
 	if (die_match_name(sp_die, lr->function) && die_is_func_def(sp_die)) {
 		lf->fname = die_get_decl_file(sp_die);
-		dwarf_decl_line(sp_die, &lr->offset);
+		if (dwarf_decl_line(sp_die, &lr->offset) != 0)
+			lr->offset = 0; /* Fallback if no line info */
 		pr_debug("fname: %s, lineno:%d\n", lf->fname, lr->offset);
 		lf->lno_s = lr->offset + lr->start;
 		if (lf->lno_s < 0)	/* Overflow */
@@ -1818,7 +1845,8 @@ static int line_range_search_cb(Dwarf_Die *sp_die, void *data)
 static int find_line_range_by_func(struct line_finder *lf)
 {
 	struct dwarf_callback_param param = {.data = (void *)lf, .retval = 0};
-	dwarf_getfuncs(&lf->cu_die, line_range_search_cb, &param, 0);
+	if (dwarf_getfuncs(&lf->cu_die, line_range_search_cb, &param, 0) < 0)
+		pr_debug("Failed to get functions from CU\n");
 	return param.retval;
 }
 
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v5 8/9] perf annotate-data: Fix libdw API contract violations
  2026-05-04  8:12   ` [PATCH v5 0/9] [PATCH v5 0/9] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
                       ` (6 preceding siblings ...)
  2026-05-04  8:12     ` [PATCH v5 7/9] perf probe-finder: Fix libdw API contract violations Ian Rogers
@ 2026-05-04  8:12     ` Ian Rogers
  2026-05-04  8:12     ` [PATCH v5 9/9] perf debuginfo: " Ian Rogers
  2026-05-04 23:50     ` [PATCH v5 0/9] [PATCH v5 0/9] perf DWARF: Fix libdw API contract violations and crashes Namhyung Kim
  9 siblings, 0 replies; 31+ messages in thread
From: Ian Rogers @ 2026-05-04  8:12 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Check return values of `dwarf_aggregate_size` and `dwarf_formudata`.

Additionally:
 - Avoid `vfprintf` undefined behavior with `NULL` strings by using
   the `die_name()` helper for `dwarf_diename()` in `pr_*` calls.
 - Use `die_get_data_member_location()` (updated to use
   `dwarf_attr_integrate`) to correctly parse location expressions
   for inherited member locations in the fallback path when
   `dwarf_formudata()` fails.

Fixes: 2bc3cf575a16 ("perf annotate-data: Improve debug message with location info")
Fixes: 4a111cadac85 ("perf annotate-data: Add member field in the data type")
Fixes: 8b1042c425f6 ("perf annotate-data: Set bitfield member offset and size properly")
Fixes: fc044c53b99f ("perf annotate-data: Add dso->data_types tree")
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
v5:
 - Safe string duplication for member variable names.
v4:
 - Safe DWARF name printing in annotate-data.
 - Fix fallback location expression parsing for inherited data members.
---
 tools/perf/util/annotate-data.c | 27 +++++++++++++++++----------
 1 file changed, 17 insertions(+), 10 deletions(-)

diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index 1eff0a27237d..63e3c54fab42 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -74,7 +74,8 @@ void pr_debug_type_name(Dwarf_Die *die, enum type_state_kind kind)
 		break;
 	}
 
-	dwarf_aggregate_size(die, &size);
+	if (dwarf_aggregate_size(die, &size) != 0)
+		size = 0;
 
 	strbuf_init(&sb, 32);
 	die_get_typename_from_type(die, &sb);
@@ -146,9 +147,9 @@ static void pr_debug_scope(Dwarf_Die *scope_die)
 
 	tag = dwarf_tag(scope_die);
 	if (tag == DW_TAG_subprogram)
-		pr_info("[function] %s\n", dwarf_diename(scope_die));
+		pr_info("[function] %s\n", die_name(scope_die));
 	else if (tag == DW_TAG_inlined_subroutine)
-		pr_info("[inlined] %s\n", dwarf_diename(scope_die));
+		pr_info("[inlined] %s\n", die_name(scope_die));
 	else if (tag == DW_TAG_lexical_block)
 		pr_info("[block]\n");
 	else
@@ -250,9 +251,12 @@ static int __add_member_cb(Dwarf_Die *die, void *arg)
 	if (dwarf_aggregate_size(&die_mem, &size) < 0)
 		size = 0;
 
-	if (dwarf_attr_integrate(die, DW_AT_data_member_location, &attr))
-		dwarf_formudata(&attr, &loc);
-	else {
+	if (dwarf_attr_integrate(die, DW_AT_data_member_location, &attr)) {
+		if (dwarf_formudata(&attr, &loc) != 0) {
+			if (die_get_data_member_location(die, &loc) != 0)
+				loc = 0;
+		}
+	} else {
 		/* bitfield member */
 		if (dwarf_attr_integrate(die, DW_AT_data_bit_offset, &attr) &&
 		    dwarf_formudata(&attr, &loc) == 0)
@@ -273,7 +277,9 @@ static int __add_member_cb(Dwarf_Die *die, void *arg)
 				     dwarf_diename(die), (long)bit_size) < 0)
 				member->var_name = NULL;
 		} else {
-			member->var_name = strdup(dwarf_diename(die));
+			const char *name = dwarf_diename(die);
+
+			member->var_name = name ? strdup(name) : NULL;
 		}
 
 		if (member->var_name == NULL) {
@@ -370,7 +376,8 @@ static struct annotated_data_type *dso__findnew_data_type(struct dso *dso,
 	if (dwarf_tag(type_die) == DW_TAG_typedef)
 		die_get_real_type(type_die, type_die);
 
-	dwarf_aggregate_size(type_die, &size);
+	if (dwarf_aggregate_size(type_die, &size) != 0)
+		size = 0;
 
 	/* Check existing nodes in dso->data_types tree */
 	key.self.type_name = type_name;
@@ -1569,7 +1576,7 @@ static int find_data_type_die(struct data_loc_info *dloc, Dwarf_Die *type_die)
 	offset = loc->offset;
 
 	pr_debug_dtp("CU for %s (die:%#lx)\n",
-		     dwarf_diename(&cu_die), (long)dwarf_dieoffset(&cu_die));
+		     die_name(&cu_die), (long)dwarf_dieoffset(&cu_die));
 
 	if (reg == DWARF_REG_PC) {
 		if (get_global_var_type(&cu_die, dloc, dloc->ip, dloc->var_addr,
@@ -1636,7 +1643,7 @@ static int find_data_type_die(struct data_loc_info *dloc, Dwarf_Die *type_die)
 		}
 
 		pr_debug_dtp("found \"%s\" (die: %#lx) in scope=%d/%d (die: %#lx) ",
-			     dwarf_diename(&var_die), (long)dwarf_dieoffset(&var_die),
+			     die_name(&var_die), (long)dwarf_dieoffset(&var_die),
 			     i+1, nr_scopes, (long)dwarf_dieoffset(&scopes[i]));
 
 		if (reg == DWARF_REG_PC) {
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v5 9/9] perf debuginfo: Fix libdw API contract violations
  2026-05-04  8:12   ` [PATCH v5 0/9] [PATCH v5 0/9] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
                       ` (7 preceding siblings ...)
  2026-05-04  8:12     ` [PATCH v5 8/9] perf annotate-data: " Ian Rogers
@ 2026-05-04  8:12     ` Ian Rogers
  2026-05-04 23:50     ` [PATCH v5 0/9] [PATCH v5 0/9] perf DWARF: Fix libdw API contract violations and crashes Namhyung Kim
  9 siblings, 0 replies; 31+ messages in thread
From: Ian Rogers @ 2026-05-04  8:12 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Adrian Hunter, James Clark, Zecheng Li,
	Masami Hiramatsu, linux-perf-users, linux-kernel
  Cc: Ian Rogers

Check return value of `dwfl_report_end` during offline initialization.
Validate `dwfl_module_relocation_info` result before passing to `strcmp`
to avoid potential segmentation faults.

Additionally:
 - Fix a file descriptor leak in `debuginfo__init_offline_dwarf()` when
   `dwfl_report_offline()` or subsequent setup calls fail.

Fixes: 6f1b6291cf73 ("perf tools: Add util/debuginfo.[ch] files")
Assisted-by: Gemini-CLI:Google Gemini 3
Acked-by: Namhyung Kim <namhyung@kernel.org>
Signed-off-by: Ian Rogers <irogers@google.com>
---
v5:
 - Added Acked-by tag.
v4:
 - Fix file descriptor leaks in debuginfo init paths.
---
 tools/perf/util/debuginfo.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/tools/perf/util/debuginfo.c b/tools/perf/util/debuginfo.c
index 0e35c13abd04..84a78b30ceac 100644
--- a/tools/perf/util/debuginfo.c
+++ b/tools/perf/util/debuginfo.c
@@ -42,6 +42,7 @@ static int debuginfo__init_offline_dwarf(struct debuginfo *dbg,
 {
 	GElf_Addr dummy;
 	int fd;
+	bool fd_consumed = false;
 
 	fd = open(path, O_RDONLY);
 	if (fd < 0)
@@ -55,6 +56,7 @@ static int debuginfo__init_offline_dwarf(struct debuginfo *dbg,
 	dbg->mod = dwfl_report_offline(dbg->dwfl, "", "", fd);
 	if (!dbg->mod)
 		goto error;
+	fd_consumed = true;
 
 	dbg->dbg = dwfl_module_getdwarf(dbg->mod, &dbg->bias);
 	if (!dbg->dbg)
@@ -62,13 +64,14 @@ static int debuginfo__init_offline_dwarf(struct debuginfo *dbg,
 
 	dwfl_module_build_id(dbg->mod, &dbg->build_id, &dummy);
 
-	dwfl_report_end(dbg->dwfl, NULL, NULL);
+	if (dwfl_report_end(dbg->dwfl, NULL, NULL) != 0)
+		goto error;
 
 	return 0;
 error:
 	if (dbg->dwfl)
 		dwfl_end(dbg->dwfl);
-	else
+	if (!fd_consumed)
 		close(fd);
 	memset(dbg, 0, sizeof(*dbg));
 
@@ -167,7 +170,7 @@ int debuginfo__get_text_offset(struct debuginfo *dbg, Dwarf_Addr *offs,
 	/* Search the relocation related .text section */
 	for (i = 0; i < n; i++) {
 		p = dwfl_module_relocation_info(dbg->mod, i, &shndx);
-		if (strcmp(p, ".text") == 0) {
+		if (p && strcmp(p, ".text") == 0) {
 			/* OK, get the section header */
 			scn = elf_getscn(elf, shndx);
 			if (!scn)
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* Re: [PATCH v5 0/9] [PATCH v5 0/9] perf DWARF: Fix libdw API contract violations and crashes
  2026-05-04  8:12   ` [PATCH v5 0/9] [PATCH v5 0/9] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
                       ` (8 preceding siblings ...)
  2026-05-04  8:12     ` [PATCH v5 9/9] perf debuginfo: " Ian Rogers
@ 2026-05-04 23:50     ` Namhyung Kim
  9 siblings, 0 replies; 31+ messages in thread
From: Namhyung Kim @ 2026-05-04 23:50 UTC (permalink / raw)
  To: Ian Rogers
  Cc: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo, Jiri Olsa,
	Adrian Hunter, James Clark, Zecheng Li, Masami Hiramatsu,
	linux-perf-users, linux-kernel

Hi Ian,

Just updated Masami's email address.  Please use it for later.

Thanks,
Namhyung

On Mon, May 04, 2026 at 01:12:18AM -0700, Ian Rogers wrote:
> This patch series addresses a number of DWARF/libdw error-handling
> bugs and contract violations, preventing several real Userspace
> segmentation faults and memory/FD leaks.
> 
> In v5, the series has been extensively restructured and polished based on 
> comprehensive review feedback on v4, focusing on history granularity, 
> bisectability, and defensive styling:
> 
>  - **Commit Splitting for Review Granularity**: Split the previously large 
>      libdw contract fix patch into 4 granular commits to cleanly isolate 
>      independent improvements: introducing clear_frames refactoring, fixing 
>      ORDER_CALLER parent update corruption, adding line 0 support, and 
>      consolidating core contract/leak cleanups.
>  
>  - **Bisectability & Correctness Hardening**: 
>      - Fixed an unused variable compilation failure (-Werror) in the split 
>        history to guarantee perfect git bisectability.
>      - Fixed a line 0 fallback regression to ensure that if an optional call 
>        line is missing but the call file is valid, we fallback to line 0 
>        to preserve the filename rather than discarding the caller info entirely.
> 
>  - **Style & Robustness Polish**:
>      - Standardized all newly introduced C++ style (//) comments to 
>        preferred C style (/* ... */) comments.
>      - Implemented explicit safe string duplication style fix in annotate-data.
>      - Corrected CU DIE propagation context inside probe-finder.
>      - Enhanced the Patch 2 commit message to explicitly detail the removal 
>        of strict optional attribute aborts (decf) to clarify review queries.
> 
>  - **Tags Collected**: Integrated Acked-by tags from Namhyung Kim for Patch 1 
>      and Patch 9.
> 
> v5:
>  - Restructured series from 6 to 9 patches by splitting the libdw commit.
>  - Fixed compilation bisectability and DWARF line 0 filename preservation regressions.
>  - Standardized comment styles and applied safe duplication formatting fixes.
>  - Updated commit messages with detailed optional attribute justifications.
> 
> v4:
>  - Localized and squashed robust error handling, memory/FD cleanup
>    paths, and safe DWARF printing.
> https://lore.kernel.org/linux-perf-users/20260503171032.1559338-1-irogers@google.com/
> 
> v3:
>  - Minor formatting fixes.
> https://lore.kernel.org/linux-perf-users/20260503003552.1063540-1-irogers@google.com/
> 
> v2:
> https://lore.kernel.org/lkml/20260502155656.478642-1-irogers@google.com/
> 
> v1:
> https://lore.kernel.org/linux-perf-users/20260502064839.282422-1-irogers@google.com/
> 
> Ian Rogers (9):
>   perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at
>   perf dwarf-aux: Fix libdw API contract violations
>   perf srcline: Introduce inline_node__clear_frames()
>   perf libdw: Fix callchain parent update in ORDER_CALLER mode
>   perf libdw: Support DWARF line 0 in inline list
>   perf libdw: Fix libdw API contract violations and memory leaks
>   perf probe-finder: Fix libdw API contract violations
>   perf annotate-data: Fix libdw API contract violations
>   perf debuginfo: Fix libdw API contract violations
> 
>  tools/perf/util/annotate-data.c |  27 +++++----
>  tools/perf/util/debuginfo.c     |   9 ++-
>  tools/perf/util/dwarf-aux.c     |  39 ++++++------
>  tools/perf/util/dwarf-aux.h     |   5 ++
>  tools/perf/util/libdw.c         |  72 +++++++++++++++++-----
>  tools/perf/util/probe-finder.c  | 102 ++++++++++++++++++++------------
>  tools/perf/util/srcline.c       |   9 ++-
>  tools/perf/util/srcline.h       |   1 +
>  8 files changed, 179 insertions(+), 85 deletions(-)
> 
> -- 
> 2.54.0.545.g6539524ca2-goog
> 

^ permalink raw reply	[flat|nested] 31+ messages in thread

end of thread, other threads:[~2026-05-04 23:50 UTC | newest]

Thread overview: 31+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-03  0:35 [PATCH v3 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
2026-05-03  0:35 ` [PATCH v3 1/6] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at Ian Rogers
2026-05-03  0:35 ` [PATCH v3 2/6] perf dwarf-aux: Fix libdw API contract violations Ian Rogers
2026-05-03  0:35 ` [PATCH v3 3/6] perf libdw: " Ian Rogers
2026-05-03  0:35 ` [PATCH v3 4/6] perf probe-finder: " Ian Rogers
2026-05-03  0:35 ` [PATCH v3 5/6] perf annotate-data: " Ian Rogers
2026-05-03  0:35 ` [PATCH v3 6/6] perf debuginfo: " Ian Rogers
2026-05-03 17:10 ` [PATCH v4 0/6] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
2026-05-03 17:10   ` [PATCH v4 1/6] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at Ian Rogers
2026-05-03 23:33     ` Namhyung Kim
2026-05-03 17:10   ` [PATCH v4 2/6] perf dwarf-aux: Fix libdw API contract violations Ian Rogers
2026-05-03 23:36     ` Namhyung Kim
2026-05-03 17:10   ` [PATCH v4 3/6] perf libdw: " Ian Rogers
2026-05-03 23:44     ` Namhyung Kim
2026-05-03 17:10   ` [PATCH v4 4/6] perf probe-finder: " Ian Rogers
2026-05-03 23:49     ` Namhyung Kim
2026-05-03 17:10   ` [PATCH v4 5/6] perf annotate-data: " Ian Rogers
2026-05-03 23:53     ` Namhyung Kim
2026-05-03 17:10   ` [PATCH v4 6/6] perf debuginfo: " Ian Rogers
2026-05-03 23:54     ` Namhyung Kim
2026-05-04  8:12   ` [PATCH v5 0/9] [PATCH v5 0/9] perf DWARF: Fix libdw API contract violations and crashes Ian Rogers
2026-05-04  8:12     ` [PATCH v5 1/9] perf dwarf-aux: Fix libdw segmentation fault in cu_walk_functions_at Ian Rogers
2026-05-04  8:12     ` [PATCH v5 2/9] perf dwarf-aux: Fix libdw API contract violations Ian Rogers
2026-05-04  8:12     ` [PATCH v5 3/9] perf srcline: Introduce inline_node__clear_frames() Ian Rogers
2026-05-04  8:12     ` [PATCH v5 4/9] perf libdw: Fix callchain parent update in ORDER_CALLER mode Ian Rogers
2026-05-04  8:12     ` [PATCH v5 5/9] perf libdw: Support DWARF line 0 in inline list Ian Rogers
2026-05-04  8:12     ` [PATCH v5 6/9] perf libdw: Fix libdw API contract violations and memory leaks Ian Rogers
2026-05-04  8:12     ` [PATCH v5 7/9] perf probe-finder: Fix libdw API contract violations Ian Rogers
2026-05-04  8:12     ` [PATCH v5 8/9] perf annotate-data: " Ian Rogers
2026-05-04  8:12     ` [PATCH v5 9/9] perf debuginfo: " Ian Rogers
2026-05-04 23:50     ` [PATCH v5 0/9] [PATCH v5 0/9] perf DWARF: Fix libdw API contract violations and crashes Namhyung Kim

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox