* [PATCH v1 0/2] perf libdw: Improve split-debug handling and error fallback
@ 2026-08-24 6:28 Ian Rogers
2026-08-24 6:28 ` [PATCH v1 1/2] perf libdw: Pass dso_name explicitly to properly anchor split-debug builds Ian Rogers
2026-08-24 6:28 ` [PATCH v1 2/2] perf libdw: Optimize fallback logic for missing DWARF Ian Rogers
0 siblings, 2 replies; 5+ messages in thread
From: Ian Rogers @ 2026-08-24 6:28 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
Tanushree Shah, Michael Liang, Alessio Podda, Shimin Guo,
linux-perf-users, linux-kernel
This patch series improves libdw's handling of split debuginfo files and its
fallback logic when DWARF data is unstructured or completely absent.
When perf's libdw backend failed to read unwinding or inlining states
due to un-mapped lines (returning 0), the addr2line_style routine fell
back to executing the system's `addr2line` potentially launching many
sub-processes.
Patch 1 Ensures split debug information is properly handled.
Patch 2 checks `dwfl_errmsg(-1)` for "no DWARF" presence to avoid
fallbacks.
Ian Rogers (2):
perf libdw: Pass dso_name explicitly to properly anchor split-debug
builds
perf libdw: Optimize fallback logic for missing DWARF
.../arch/powerpc/util/skip-callchain-idx.c | 2 +-
tools/perf/util/dso.h | 5 +++--
tools/perf/util/libdw.c | 21 ++++++++++++-------
tools/perf/util/libdw.h | 5 +++--
tools/perf/util/srcline.c | 4 +++-
tools/perf/util/unwind-libdw.c | 15 ++++++++++---
6 files changed, 36 insertions(+), 16 deletions(-)
--
2.55.0.766.g2966f0265a-goog
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH v1 1/2] perf libdw: Pass dso_name explicitly to properly anchor split-debug builds 2026-08-24 6:28 [PATCH v1 0/2] perf libdw: Improve split-debug handling and error fallback Ian Rogers @ 2026-08-24 6:28 ` Ian Rogers 2026-08-24 6:37 ` sashiko-bot 2026-08-24 6:28 ` [PATCH v1 2/2] perf libdw: Optimize fallback logic for missing DWARF Ian Rogers 1 sibling, 1 reply; 5+ messages in thread From: Ian Rogers @ 2026-08-24 6:28 UTC (permalink / raw) To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark, Tanushree Shah, Michael Liang, Alessio Podda, Shimin Guo, linux-perf-users, linux-kernel When resolving source lines via get_srcline(), srcline_dso_name() explicitly checks for and returns the split-debug file path (dso__symsrc_filename, which is cached during dso__load()) rather than the default executable path (dso__long_name). Because libdw__addr2line defaulted to dso__long_name without accepting a passed dso_name, the Dwfl_Module was hardcoded to initialize against the stripped executable instead of the intended split debug file. By explicitly passing the successfully resolved dso_name down the call path from addr2line() to dso__libdw_dwfl(), libdw accurately anchors to the dynamically discovered split-debug file, fixing dwarf resolution for these binaries. Assisted-by: Antigravity:gemini-3.6-flash Signed-off-by: Ian Rogers <irogers@google.com> --- tools/perf/arch/powerpc/util/skip-callchain-idx.c | 2 +- tools/perf/util/dso.h | 5 +++-- tools/perf/util/libdw.c | 10 +++++----- tools/perf/util/libdw.h | 5 +++-- tools/perf/util/srcline.c | 2 +- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/tools/perf/arch/powerpc/util/skip-callchain-idx.c b/tools/perf/arch/powerpc/util/skip-callchain-idx.c index e57f10798fa6..f7dd27faaec2 100644 --- a/tools/perf/arch/powerpc/util/skip-callchain-idx.c +++ b/tools/perf/arch/powerpc/util/skip-callchain-idx.c @@ -152,7 +152,7 @@ static int check_return_addr(struct dso *dso, Dwarf_Addr mapped_pc) Dwarf_Addr end = mapped_pc; bool signalp; - dwfl = dso__libdw_dwfl(dso); + dwfl = dso__libdw_dwfl(dso, NULL); if (!dwfl) return -1; diff --git a/tools/perf/util/dso.h b/tools/perf/util/dso.h index 55c4aaa53c38..3271e1ecaa9e 100644 --- a/tools/perf/util/dso.h +++ b/tools/perf/util/dso.h @@ -380,9 +380,10 @@ static inline void dso__set_libdw(struct dso *dso, void *val) struct Dwfl; #ifdef HAVE_LIBDW_SUPPORT -struct Dwfl *dso__libdw_dwfl(struct dso *dso); +struct Dwfl *dso__libdw_dwfl(struct dso *dso, const char *dso_name); #else -static inline struct Dwfl *dso__libdw_dwfl(struct dso *dso __maybe_unused) +static inline struct Dwfl *dso__libdw_dwfl(struct dso *dso __maybe_unused, + const char *dso_name __maybe_unused) { return NULL; } diff --git a/tools/perf/util/libdw.c b/tools/perf/util/libdw.c index 4ca7e7e4fbe9..f53caee980b0 100644 --- a/tools/perf/util/libdw.c +++ b/tools/perf/util/libdw.c @@ -25,17 +25,17 @@ void dso__free_libdw(struct dso *dso) } } -struct Dwfl *dso__libdw_dwfl(struct dso *dso) +struct Dwfl *dso__libdw_dwfl(struct dso *dso, const char *dso_name) { Dwfl *dwfl = dso__libdw(dso); - const char *dso_name; Dwfl_Module *mod; int fd; if (dwfl) return dwfl; - dso_name = dso__long_name(dso); + if (!dso_name) + dso_name = dso__long_name(dso); /* * Initialize Dwfl session. * We need to open the DSO file to report it to libdw. @@ -163,11 +163,11 @@ static int libdw_a2l_cb(Dwarf_Die *die, void *_args) return DWARF_CB_ABORT; } -int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr, +int libdw__addr2line(const char *dso_name, u64 addr, char **file, unsigned int *line_nr, struct dso *dso, bool unwind_inlines, struct inline_node *node, struct symbol *sym) { - Dwfl *dwfl = dso__libdw_dwfl(dso); + Dwfl *dwfl = dso__libdw_dwfl(dso, dso_name); Dwfl_Module *mod; Dwfl_Line *dwline; Dwarf_Addr bias; diff --git a/tools/perf/util/libdw.h b/tools/perf/util/libdw.h index b12094737415..4bdd28d44e25 100644 --- a/tools/perf/util/libdw.h +++ b/tools/perf/util/libdw.h @@ -25,7 +25,7 @@ struct symbol; * * Returns 1 on success (found), 0 on failure (not found). */ -int libdw__addr2line(u64 addr, char **file, +int libdw__addr2line(const char *dso_name, u64 addr, char **file, unsigned int *line_nr, struct dso *dso, bool unwind_inlines, struct inline_node *node, struct symbol *sym); @@ -40,7 +40,8 @@ void dso__free_libdw(struct dso *dso); #else /* HAVE_LIBDW_SUPPORT */ -static inline int libdw__addr2line(u64 addr __maybe_unused, char **file __maybe_unused, +static inline int libdw__addr2line(const char *dso_name, u64 addr __maybe_unused, + char **file __maybe_unused, unsigned int *line_nr __maybe_unused, struct dso *dso __maybe_unused, bool unwind_inlines __maybe_unused, diff --git a/tools/perf/util/srcline.c b/tools/perf/util/srcline.c index b082178c279b..8e954f0c860a 100644 --- a/tools/perf/util/srcline.c +++ b/tools/perf/util/srcline.c @@ -155,7 +155,7 @@ static int addr2line(const char *dso_name, u64 addr, char **file, unsigned int * for (size_t i = 0; i < ARRAY_SIZE(symbol_conf.addr2line_style); i++) { switch (symbol_conf.addr2line_style[i]) { case A2L_STYLE_LIBDW: - ret = libdw__addr2line(addr, file, line_nr, dso, unwind_inlines, + ret = libdw__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines, node, sym); break; case A2L_STYLE_LLVM: -- 2.55.0.766.g2966f0265a-goog ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v1 1/2] perf libdw: Pass dso_name explicitly to properly anchor split-debug builds 2026-08-24 6:28 ` [PATCH v1 1/2] perf libdw: Pass dso_name explicitly to properly anchor split-debug builds Ian Rogers @ 2026-08-24 6:37 ` sashiko-bot 0 siblings, 0 replies; 5+ messages in thread From: sashiko-bot @ 2026-08-24 6:37 UTC (permalink / raw) To: Ian Rogers; +Cc: linux-perf-users Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] State caching bug in dso__libdw_dwfl ignores dso_name and breaks split-debug resolution. - [Low] Missing __maybe_unused attribute on the dso_name parameter in the libdw__addr2line() fallback stub. -- --- Patch [1]: [PATCH v1 1/2] perf libdw: Pass dso_name explicitly to properly anchor split-debug builds --- commit d6c05f5d9fb09508b13daaab6b752a240e96edf3 Author: Ian Rogers <irogers@google.com> perf libdw: Pass dso_name explicitly to properly anchor split-debug builds This commit modifies libdw__addr2line and dso__libdw_dwfl to accept an explicit dso_name parameter. It aims to accurately anchor libdw to the dynamically discovered split-debug file instead of the stripped executable. > diff --git a/tools/perf/util/libdw.c b/tools/perf/util/libdw.c > index 4ca7e7e4fbe93..f53caee980b0b 100644 > --- a/tools/perf/util/libdw.c > +++ b/tools/perf/util/libdw.c > @@ -25,17 +25,17 @@ void dso__free_libdw(struct dso *dso) > } > } > > -struct Dwfl *dso__libdw_dwfl(struct dso *dso) > +struct Dwfl *dso__libdw_dwfl(struct dso *dso, const char *dso_name) > { > Dwfl *dwfl = dso__libdw(dso); > - const char *dso_name; > Dwfl_Module *mod; > int fd; > > if (dwfl) > return dwfl; [Severity: Medium] Does this early return prevent the newly passed dso_name from being used if a Dwfl object is already cached? For example, in tools/perf/arch/powerpc/util/skip-callchain-idx.c, check_return_addr() calls: dwfl = dso__libdw_dwfl(dso, NULL); This early call passes NULL for dso_name, which caches the stripped executable's Dwfl. When get_srcline() later calls libdw__addr2line() with the newly discovered split-debug file, won't this early return just provide the already cached stripped executable's Dwfl and ignore the passed split-debug dso_name? [ ... ] > diff --git a/tools/perf/util/libdw.h b/tools/perf/util/libdw.h > index b120947374159..4bdd28d44e25e 100644 > --- a/tools/perf/util/libdw.h > +++ b/tools/perf/util/libdw.h [ ... ] > @@ -40,7 +40,8 @@ void dso__free_libdw(struct dso *dso); > > #else /* HAVE_LIBDW_SUPPORT */ > > -static inline int libdw__addr2line(u64 addr __maybe_unused, char **file __maybe_unused, > +static inline int libdw__addr2line(const char *dso_name, u64 addr __maybe_unused, > + char **file __maybe_unused, [Severity: Low] Will this cause a build failure when compiling with HAVE_LIBDW_SUPPORT disabled (e.g., NO_LIBDW=1)? The fallback stub introduces the dso_name parameter but doesn't use it, and lacks the __maybe_unused attribute present on the other unused parameters in the same signature. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260824062841.1529489-1-irogers@google.com?part=1 ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v1 2/2] perf libdw: Optimize fallback logic for missing DWARF 2026-08-24 6:28 [PATCH v1 0/2] perf libdw: Improve split-debug handling and error fallback Ian Rogers 2026-08-24 6:28 ` [PATCH v1 1/2] perf libdw: Pass dso_name explicitly to properly anchor split-debug builds Ian Rogers @ 2026-08-24 6:28 ` Ian Rogers 2026-08-24 6:40 ` sashiko-bot 1 sibling, 1 reply; 5+ messages in thread From: Ian Rogers @ 2026-08-24 6:28 UTC (permalink / raw) To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark, Tanushree Shah, Michael Liang, Alessio Podda, Shimin Guo, linux-perf-users, linux-kernel When libdw encounters a DWARF definition it cannot accurately un-inline or an address block natively missing structural line maps, dwfl_module_getsrc() falls back immediately with 0. This causes perf's addr2line_style loop to fall back to launching the cmd__addr2line() sub-process wrapper. In environments generating massive numbers of unmapped traces, this forces thousands of redundant `addr2line -e` fork/execs in a polling loop. This patch intercepts failure scenarios: 1. Returns -1 when dwfl_module_getsrc() returns NULL (meaning the DWARF is parsed but the line mapping is natively missing). 2. Evaluates `ret < 0` in addr2line() to dynamically bypass the subprocess fallback and return 0. This precisely acts as a failure condition, incrementing the dso->a2l_fails counter and accurately triggering the A2L_FAIL_LIMIT throttling without shelling out. 3. Incorporates exact error string checking for "no DWARF" to safely abort alternative fallbacks directly within both libdw resolving and the unwinder logic (unwind-libdw). This preserves fallbacks if unwinder or DWARF parsing genuinely aborts, but avoids falling back when mappings are absent. Assisted-by: Antigravity:gemini-3.6-flash Signed-off-by: Ian Rogers <irogers@google.com> --- tools/perf/util/libdw.c | 11 +++++++++-- tools/perf/util/srcline.c | 2 ++ tools/perf/util/unwind-libdw.c | 15 ++++++++++++--- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/tools/perf/util/libdw.c b/tools/perf/util/libdw.c index f53caee980b0..7c681b03c654 100644 --- a/tools/perf/util/libdw.c +++ b/tools/perf/util/libdw.c @@ -186,8 +186,15 @@ int libdw__addr2line(const char *dso_name, u64 addr, char **file, unsigned int * * between the regular ELF addr2line addresses and those to use with * libdw. */ - if (!dwfl_module_getdwarf(mod, &bias)) - return 0; + if (!dwfl_module_getdwarf(mod, &bias)) { + const char *err = dwfl_errmsg(-1); + + /* + * Abort fallbacks specifically when DWARF is completely missing, + * but allow alternative backends to try if parsing failed. + */ + return (err && strstr(err, "no DWARF")) ? -1 : 0; + } /* Find source line information for the address. */ dwline = dwfl_module_getsrc(mod, addr + bias); diff --git a/tools/perf/util/srcline.c b/tools/perf/util/srcline.c index 8e954f0c860a..8283a741c018 100644 --- a/tools/perf/util/srcline.c +++ b/tools/perf/util/srcline.c @@ -176,6 +176,8 @@ static int addr2line(const char *dso_name, u64 addr, char **file, unsigned int * } if (ret > 0) return ret; + if (ret < 0) + return 0; } return 0; diff --git a/tools/perf/util/unwind-libdw.c b/tools/perf/util/unwind-libdw.c index 63a5c2253174..69d4585c870c 100644 --- a/tools/perf/util/unwind-libdw.c +++ b/tools/perf/util/unwind-libdw.c @@ -441,8 +441,17 @@ int libdw__get_entries(unwind_entry_cb_t cb, void *arg, } out: - if (err) - pr_debug("unwind: failed with '%s'\n", dwfl_errmsg(-1)); + if (err) { + const char *msg = dwfl_errmsg(-1); + + pr_debug("unwind: failed with '%s'\n", msg); + /* + * Abort fallbacks specifically when unwinding information is completely + * missing, but allow fallback unwinders to try if parsing simply failed. + */ + if (msg && strstr(msg, "no DWARF")) + err = -EINVAL; + } for (i = 0; i < ui->idx; i++) map_symbol__exit(&ui->entries[i].ms); @@ -460,6 +469,6 @@ int libdw__get_entries(unwind_entry_cb_t cb, void *arg, * < 0 : fatal error (e.g. -ENOMEM). Aborts unwinding entirely. */ if (err) - return (err == -ENOMEM) ? -ENOMEM : (entries > 0 ? 1 : 0); + return (err == -ENOMEM || err == -EINVAL) ? err : (entries > 0 ? 1 : 0); return entries; } -- 2.55.0.766.g2966f0265a-goog ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v1 2/2] perf libdw: Optimize fallback logic for missing DWARF 2026-08-24 6:28 ` [PATCH v1 2/2] perf libdw: Optimize fallback logic for missing DWARF Ian Rogers @ 2026-08-24 6:40 ` sashiko-bot 0 siblings, 0 replies; 5+ messages in thread From: sashiko-bot @ 2026-08-24 6:40 UTC (permalink / raw) To: Ian Rogers; +Cc: linux-perf-users Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: New issues: - [High] The modified return logic for `-EINVAL` fatally aborts all unwinder fallbacks on initialization errors and truncates partial callchains. - [High] Unsupported fallback styles incorrectly abort the entire address resolution loop. - [Medium] The patch modifies error handling for `dwfl_module_getdwarf()` instead of `dwfl_module_getsrc()`, failing to bypass the fallback loop as intended. Pre-existing issues: - [High] Passing potentially `NULL` pointer to `%s` format specifier causes a segmentation fault under musl libc. -- --- Patch [2]: [PATCH v1 2/2] perf libdw: Optimize fallback logic for missing DWARF --- commit 5f18acea82554e76916d774c6228aad56add9f6a Author: Ian Rogers <irogers@google.com> perf libdw: Optimize fallback logic for missing DWARF This commit attempts to optimize fallback logic for missing DWARF information in perf. It modifies libdw__addr2line() to return -1 on "no DWARF" error to bypass the subprocess fallback in addr2line(). It also updates libdw__get_entries() to return -EINVAL if unwinding fails due to missing DWARF information. > diff --git a/tools/perf/util/libdw.c b/tools/perf/util/libdw.c > index f53caee980b0b..7c681b03c6543 100644 > --- a/tools/perf/util/libdw.c > +++ b/tools/perf/util/libdw.c > @@ -186,8 +186,15 @@ int libdw__addr2line(const char *dso_name, u64 addr, char **file, unsigned int * > * between the regular ELF addr2line addresses and those to use with > * libdw. > */ > - if (!dwfl_module_getdwarf(mod, &bias)) > - return 0; > + if (!dwfl_module_getdwarf(mod, &bias)) { > + const char *err = dwfl_errmsg(-1); > + > + /* > + * Abort fallbacks specifically when DWARF is completely missing, > + * but allow alternative backends to try if parsing failed. > + */ > + return (err && strstr(err, "no DWARF")) ? -1 : 0; > + } [Severity: Medium] The commit message mentions that when dwfl_module_getsrc() encounters an issue, it returns 0 and causes redundant addr2line forks. However, this patch applies the new error checking and -1 return to dwfl_module_getdwarf() instead of dwfl_module_getsrc() on line 196: dwline = dwfl_module_getsrc(mod, addr + bias); if (!dwline) return 0; Because the unmodified check for dwfl_module_getsrc() still returns 0, does this miss the intended optimization described in the commit message and continue to trigger subprocess fallbacks when line maps are missing? [ ... ] > diff --git a/tools/perf/util/srcline.c b/tools/perf/util/srcline.c > index 8e954f0c860a0..8283a741c0186 100644 > --- a/tools/perf/util/srcline.c > +++ b/tools/perf/util/srcline.c > @@ -176,6 +176,8 @@ static int addr2line(const char *dso_name, u64 addr, char **file, unsigned int * > } > if (ret > 0) > return ret; > + if (ret < 0) > + return 0; > } > > return 0; [Severity: High] Unsupported fallback styles, like llvm__addr2line() or libbfd__addr2line() when not compiled in, return -1 as stubs. If a user has an uncompiled backend configured early in their symbol_conf.addr2line_style list, won't this new (ret < 0) condition incorrectly match the -1 from the stub and abort the entire resolution loop, bypassing any subsequent valid fallbacks like A2L_STYLE_CMD? [ ... ] > diff --git a/tools/perf/util/unwind-libdw.c b/tools/perf/util/unwind-libdw.c > index 63a5c2253174f..69d4585c870c8 100644 > --- a/tools/perf/util/unwind-libdw.c > +++ b/tools/perf/util/unwind-libdw.c > @@ -441,8 +441,17 @@ int libdw__get_entries(unwind_entry_cb_t cb, void *arg, > } > > out: > - if (err) > - pr_debug("unwind: failed with '%s'\n", dwfl_errmsg(-1)); > + if (err) { > + const char *msg = dwfl_errmsg(-1); > + > + pr_debug("unwind: failed with '%s'\n", msg); [Severity: High] This is a pre-existing issue, but if perf_reg_value() fails earlier in this function, err is set to -EINVAL and it jumps to out. Since no libdw function failed, dwfl_errmsg(-1) can return NULL. The msg pointer is then immediately passed to pr_debug with a '%s' format specifier before the subsequent msg NULL check. Under musl libc, won't passing a NULL pointer to %s cause a segmentation fault? > + /* > + * Abort fallbacks specifically when unwinding information is completely > + * missing, but allow fallback unwinders to try if parsing simply failed. > + */ > + if (msg && strstr(msg, "no DWARF")) > + err = -EINVAL; > + } > > for (i = 0; i < ui->idx; i++) > map_symbol__exit(&ui->entries[i].ms); > @@ -460,6 +469,6 @@ int libdw__get_entries(unwind_entry_cb_t cb, void *arg, > * < 0 : fatal error (e.g. -ENOMEM). Aborts unwinding entirely. > */ > if (err) > - return (err == -ENOMEM) ? -ENOMEM : (entries > 0 ? 1 : 0); > + return (err == -ENOMEM || err == -EINVAL) ? err : (entries > 0 ? 1 : 0); > return entries; > } [Severity: High] If perf_reg_value() or dwfl_begin() fail earlier during initialization, err is set to -EINVAL. With this new return logic, returning -EINVAL (< 0) signals a fatal error to the unwinder caller. Does this completely abort unwinding fallbacks and truncate partial callchains instead of allowing subsequent fallbacks (like kernel callchains) to proceed, which returning 0 would previously allow? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260824062841.1529489-1-irogers@google.com?part=2 ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-08-24 6:40 UTC | newest] Thread overview: 5+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-24 6:28 [PATCH v1 0/2] perf libdw: Improve split-debug handling and error fallback Ian Rogers 2026-08-24 6:28 ` [PATCH v1 1/2] perf libdw: Pass dso_name explicitly to properly anchor split-debug builds Ian Rogers 2026-08-24 6:37 ` sashiko-bot 2026-08-24 6:28 ` [PATCH v1 2/2] perf libdw: Optimize fallback logic for missing DWARF Ian Rogers 2026-08-24 6:40 ` sashiko-bot
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox