From 62d7cdc8d58702feb31b320f1713642e3977d7f3 Mon Sep 17 00:00:00 2001 From: Ian Rogers Date: Sun, 25 Oct 2026 23:50:43 -0700 Subject: [PATCH] perf libdw: Optimize fallback logic for missing DWARF When libdw encounters a DWARF definition it cannot accurately un-inline or an address block natively missing structural line maps, dwfl_module_getsrc() returns NULL. Previously, libdw__addr2line() returned -1 in both dwfl_module_getdwarf() and dwfl_module_getsrc() failure paths while addr2line() in srcline.c only stopped its fallback loop on ret > 0. This caused perf's addr2line_style loop to fall back to llvm, libbfd, and the cmd__addr2line() subprocess wrapper on every unmapped address or stripped DSO. Update the addr2line_style pipeline return semantics consistently: 1. Backends return 0 to denote conclusively missing information (e.g. valid ELF but no DWARF inside, or parsed DWARF with no mapped lines), stopping the fallback loop. 2. Backends (and uncompiled stubs) return < 0 to denote initialization or backend errors, instructing the loop to iterate to the next configured fallback. 3. Backends return > 0 on successful source line resolution. Fixes: bf96e296d52d74a1 ("perf libdw: Fix Dwfl discovery with split files") Signed-off-by: Ian Rogers Cc: Adrian Hunter Cc: Alessio Podda Cc: Alireza Haghdoost Cc: Ingo Molnar Cc: James Clark Cc: Jiri Olsa Cc: Michael Liang Cc: Namhyung Kim Cc: Peter Zijlstra Cc: Shimin Guo Cc: Tanushree Shah Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/addr2line.c | 28 ++++++++++--- tools/perf/util/dso.h | 2 +- tools/perf/util/libbfd.c | 6 +-- tools/perf/util/libdw.c | 25 ++++++++--- tools/perf/util/libdw.h | 5 ++- tools/perf/util/llvm-c-helpers.cpp | 66 +++++++++++++++++++----------- tools/perf/util/llvm.c | 12 ++++-- tools/perf/util/srcline.c | 21 +++++++--- 8 files changed, 115 insertions(+), 50 deletions(-) diff --git a/tools/perf/util/addr2line.c b/tools/perf/util/addr2line.c index 6f64f6b42442..960dd48701bb 100644 --- a/tools/perf/util/addr2line.c +++ b/tools/perf/util/addr2line.c @@ -298,6 +298,7 @@ int cmd__addr2line(const char *dso_name, u64 addr, struct io io = { .eof = false }; enum cmd_a2l_style cmd_a2l_style; const char *current_dso_name; + bool a2l_synced = false; mutex_lock(dso__lock(dso)); current_dso_name = dso__symsrc_filename(dso) ?: dso_name; @@ -316,11 +317,14 @@ int cmd__addr2line(const char *dso_name, u64 addr, if (a2l == NULL) { if (!symbol_conf.addr2line_disable_warn) pr_warning("%s %s: addr2line_subprocess_init failed\n", __func__, dso_name); + ret = -1; goto out_unlock; } cmd_a2l_style = cmd_addr2line_configure(a2l, dso_name); - if (cmd_a2l_style == BROKEN) + if (cmd_a2l_style == BROKEN) { + ret = -1; goto out_unlock; + } /* * Take ownership of the a2l subprocess so we can safely perform @@ -344,6 +348,7 @@ int cmd__addr2line(const char *dso_name, u64 addr, if (written != len) { if (!symbol_conf.addr2line_disable_warn) pr_warning("%s %s: could not send request\n", __func__, dso_name); + ret = -1; goto out; } io__init(&io, a2l->out, buf, sizeof(buf)); @@ -353,6 +358,7 @@ int cmd__addr2line(const char *dso_name, u64 addr, case -1: if (!symbol_conf.addr2line_disable_warn) pr_warning("%s %s: could not read first record\n", __func__, dso_name); + ret = -1; goto out; case 0: /* @@ -373,6 +379,7 @@ int cmd__addr2line(const char *dso_name, u64 addr, break; case 0: /* The sentinel as expected. */ + a2l_synced = true; break; default: if (!symbol_conf.addr2line_disable_warn) @@ -388,17 +395,23 @@ int cmd__addr2line(const char *dso_name, u64 addr, if (file) { *file = strdup(record_filename); - ret = 1; + if (!*file) { + ret = -ENOMEM; + goto out; + } } if (line_nr) *line_nr = record_line_nr; + ret = 1; if (unwind_inlines) { if (node && inline_list__append_record(dso, node, sym, record_function, record_filename, record_line_nr)) { - ret = 0; + if (file && *file) + zfree(file); + ret = -ENOMEM; goto out; } } @@ -421,12 +434,17 @@ int cmd__addr2line(const char *dso_name, u64 addr, record_function, record_filename, record_line_nr)) { - ret = 0; + if (file && *file) + zfree(file); + inline_node__clear_frames(node); + ret = -ENOMEM; goto out; } ret = 1; /* found at least one inline frame */ } } + if (record_status == 0) + a2l_synced = true; out: mutex_lock(dso__lock(dso)); @@ -434,7 +452,7 @@ int cmd__addr2line(const char *dso_name, u64 addr, free(record_filename); current_dso_name = dso__symsrc_filename(dso) ?: dso__long_name(dso); - if (!io.eof && dso__a2l(dso) == NULL && current_dso_name && + if (a2l_synced && !io.eof && dso__a2l(dso) == NULL && current_dso_name && !strcmp(current_dso_name, dso_name)) dso__set_a2l(dso, a2l); else diff --git a/tools/perf/util/dso.h b/tools/perf/util/dso.h index 3329f417810c..d5f7382d4e13 100644 --- a/tools/perf/util/dso.h +++ b/tools/perf/util/dso.h @@ -760,7 +760,7 @@ static inline const char *dso__symsrc_filename(const struct dso *dso) return RC_CHK_ACCESS(dso)->symsrc_filename; } -void dso__set_symsrc_filename(struct dso *dso, char *val); +void dso__set_symsrc_filename(struct dso *dso, char *val) EXCLUSIVE_LOCKS_REQUIRED(dso__lock(dso)); static inline void dso__free_symsrc_filename(struct dso *dso) { diff --git a/tools/perf/util/libbfd.c b/tools/perf/util/libbfd.c index a4dcb406c79e..992e2c2a7198 100644 --- a/tools/perf/util/libbfd.c +++ b/tools/perf/util/libbfd.c @@ -288,7 +288,7 @@ int libbfd__addr2line(const char *dso_name, u64 addr, if (node && inline_list__append_dso_a2l(dso, node, sym)) { inline_node__clear_frames(node); - ret = 0; + ret = -ENOMEM; goto out; } @@ -302,7 +302,7 @@ int libbfd__addr2line(const char *dso_name, u64 addr, if (node != NULL) { if (inline_list__append_dso_a2l(dso, node, sym)) { inline_node__clear_frames(node); - ret = 0; + ret = -ENOMEM; goto out; } } @@ -312,7 +312,7 @@ int libbfd__addr2line(const char *dso_name, u64 addr, if (file) { *file = a2l->filename ? strdup(a2l->filename) : NULL; if (!*file) { - /* Leave ret as 0 so that another addr2line is tried. */ + ret = a2l->filename ? -ENOMEM : 0; goto out; } } diff --git a/tools/perf/util/libdw.c b/tools/perf/util/libdw.c index c4504cceb313..e660623338d5 100644 --- a/tools/perf/util/libdw.c +++ b/tools/perf/util/libdw.c @@ -7,6 +7,7 @@ #include "callchain.h" #include #include +#include #include static const Dwfl_Callbacks offline_callbacks = { @@ -175,10 +176,15 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr, int lineno = 0; int ret = 0; + if (!dso__has_srcline(dso)) + return 0; + mutex_lock(dso__lock(dso)); dwfl = dso__libdw_dwfl(dso); - if (!dwfl) + if (!dwfl) { + ret = -1; goto out; + } mod = dwfl_addrmodule(dwfl, addr); if (!mod) { @@ -192,14 +198,23 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr, * libdw. */ if (!dwfl_module_getdwarf(mod, &bias)) { - ret = -1; + /* + * Free the Dwfl session so the mmap'd ELF binary is unmapped + * immediately, and mark the DSO as lacking srcline/DWARF info + * so neither libdw nor slower fallback backends (addr2line + * subprocess, libbfd) are repeatedly invoked on stripped DSOs. + */ + dso__free_libdw(dso); + dso__set_has_srcline(dso, false); + ret = 0; goto out; } /* Find source line information for the address. */ dwline = dwfl_module_getsrc(mod, addr + bias); if (!dwline) { - ret = -1; + /* conclusively missing information -> abort fallbacks */ + ret = 0; goto out; } @@ -228,7 +243,7 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr, free(*file); *file = NULL; } - ret = 0; + ret = -ENOMEM; goto out; } @@ -245,7 +260,7 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr, *file = NULL; } inline_node__clear_frames(node); - ret = 0; + ret = args.err; goto out; } } diff --git a/tools/perf/util/libdw.h b/tools/perf/util/libdw.h index b12094737415..c8f319ce35df 100644 --- a/tools/perf/util/libdw.h +++ b/tools/perf/util/libdw.h @@ -23,7 +23,8 @@ struct symbol; * finds the source line information for the given address, and optionally * resolves inline function call chains. * - * Returns 1 on success (found), 0 on failure (not found). + * Returns 1 on success (found), 0 when DWARF/line info is conclusively + * missing, and < 0 on initialization or uncompiled error. */ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr, struct dso *dso, @@ -47,7 +48,7 @@ static inline int libdw__addr2line(u64 addr __maybe_unused, char **file __maybe_ struct inline_node *node __maybe_unused, struct symbol *sym __maybe_unused) { - return 0; + return -1; } static inline void dso__free_libdw(struct dso *dso __maybe_unused) diff --git a/tools/perf/util/llvm-c-helpers.cpp b/tools/perf/util/llvm-c-helpers.cpp index 004081bd12c9..d990166100f0 100644 --- a/tools/perf/util/llvm-c-helpers.cpp +++ b/tools/perf/util/llvm-c-helpers.cpp @@ -50,20 +50,20 @@ static LLVMSymbolizer *get_symbolizer() return instance; } -/* Returns 0 on error, 1 on success. */ +/* Returns < 0 on error, 0 when line info is missing, 1 on success. */ static int extract_file_and_line(const DILineInfo &line_info, char **file, unsigned int *line) { - if (file) { - if (line_info.FileName == "") { - /* Match the convention of libbfd. */ + if (line_info.FileName == "") { + if (file) *file = nullptr; - } else { - /* The caller expects to get something it can free(). */ - *file = strdup(line_info.FileName.c_str()); - if (*file == nullptr) - return 0; - } + return 0; + } + if (file) { + /* The caller expects to get something it can free(). */ + *file = strdup(line_info.FileName.c_str()); + if (*file == nullptr) + return -ENOMEM; } if (line) *line = line_info.Line; @@ -86,30 +86,42 @@ int llvm_addr2line(const char *dso_name, u64 addr, Expected res_or_err = symbolizer->symbolizeInlinedCode(dso_name, sectioned_addr); - if (!res_or_err) - return 0; + if (!res_or_err) { + consumeError(res_or_err.takeError()); + return -1; + } unsigned num_frames = res_or_err->getNumberOfFrames(); if (num_frames == 0) return 0; - if (extract_file_and_line(res_or_err->getFrame(0), - file, line) == 0) - return 0; + int ret = extract_file_and_line(res_or_err->getFrame(0), + file, line); + if (ret <= 0) + return ret; *inline_frames = (llvm_a2l_frame *)calloc( num_frames, sizeof(**inline_frames)); - if (*inline_frames == nullptr) - return 0; + if (*inline_frames == nullptr) { + if (file) + zfree(file); + return -ENOMEM; + } for (unsigned i = 0; i < num_frames; ++i) { const DILineInfo &src = res_or_err->getFrame(i); llvm_a2l_frame &dst = (*inline_frames)[i]; - if (src.FileName == "") - /* Match the convention of libbfd. */ - dst.filename = nullptr; - else - dst.filename = strdup(src.FileName.c_str()); + if (src.FileName == "") { + for (unsigned j = 0; j < i; ++j) { + zfree(&(*inline_frames)[j].filename); + zfree(&(*inline_frames)[j].funcname); + } + zfree(inline_frames); + if (file) + zfree(file); + return 0; + } + dst.filename = strdup(src.FileName.c_str()); dst.funcname = strdup(src.FunctionName.c_str()); dst.line = src.Line; @@ -120,7 +132,9 @@ int llvm_addr2line(const char *dso_name, u64 addr, zfree(&(*inline_frames)[j].funcname); } zfree(inline_frames); - return 0; + if (file) + zfree(file); + return -ENOMEM; } } @@ -131,8 +145,10 @@ int llvm_addr2line(const char *dso_name, u64 addr, Expected res_or_err = symbolizer->symbolizeCode(dso_name, sectioned_addr); - if (!res_or_err) - return 0; + if (!res_or_err) { + consumeError(res_or_err.takeError()); + return -1; + } return extract_file_and_line(*res_or_err, file, line); } } diff --git a/tools/perf/util/llvm.c b/tools/perf/util/llvm.c index 722e71aab100..0ecb1a53359e 100644 --- a/tools/perf/util/llvm.c +++ b/tools/perf/util/llvm.c @@ -44,8 +44,11 @@ int llvm__addr2line(const char *dso_name __maybe_unused, u64 addr __maybe_unused int num_frames = llvm_addr2line(dso_name, addr, file, line, node && unwind_inlines, &inline_frames); - if (num_frames == 0 || !inline_frames) { - /* Error, or we didn't want inlines. */ + if (num_frames <= 0 || !inline_frames) { + /* + * Error (< 0 -> iterate fallbacks), missing line info (0), or + * we resolved lines without wanting inlines (1). + */ return num_frames; } @@ -60,8 +63,11 @@ int llvm__addr2line(const char *dso_name __maybe_unused, u64 addr __maybe_unused inline_frames[i].line); } if (inline_list__append(inline_sym, srcline, node) != 0) { + if (file) + zfree(file); + inline_node__clear_frames(node); free_llvm_inline_frames(inline_frames, num_frames); - return 0; + return -1; } } free_llvm_inline_frames(inline_frames, num_frames); diff --git a/tools/perf/util/srcline.c b/tools/perf/util/srcline.c index a8d16106f619..23951c35fb66 100644 --- a/tools/perf/util/srcline.c +++ b/tools/perf/util/srcline.c @@ -181,9 +181,9 @@ static int addr2line(const char *dso_name, u64 addr, char **file, unsigned int * break; case A2L_STYLE_UNKNOWN: default: - break; + return 0; } - if (ret > 0) + if (ret >= 0) return ret; } @@ -250,6 +250,10 @@ static struct inline_node *addr2inlines(const char *dso_name, u64 addr, addr2line(dso_name, addr, /*file=*/NULL, /*line_nr=*/NULL, dso, /*unwind_inlines=*/true, node, sym); + if (!dso__has_srcline(dso) && list_empty(&node->val)) { + inline_node__delete(node); + return NULL; + } return node; } @@ -276,8 +280,8 @@ char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym, if (dso_name == NULL) goto out_err; - if (!addr2line(dso_name, addr, &file, &line, dso, - unwind_inlines, /*node=*/NULL, sym)) { + if (addr2line(dso_name, addr, &file, &line, dso, + unwind_inlines, /*node=*/NULL, sym) <= 0) { free(dso_name); goto out_err; } @@ -302,6 +306,7 @@ char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym, dso__set_has_srcline(dso, false); dso__free_a2l(dso); dso__free_a2l_libbfd(dso); + dso__free_libdw(dso); } mutex_unlock(dso__lock(dso)); out: @@ -331,8 +336,8 @@ char *get_srcline_split(struct dso *dso, u64 addr, unsigned *line) if (dso_name == NULL) goto out_err; - if (!addr2line(dso_name, addr, &file, line, dso, /*unwind_inlines=*/true, - /*node=*/NULL, /*sym=*/NULL)) { + if (addr2line(dso_name, addr, &file, line, dso, /*unwind_inlines=*/true, + /*node=*/NULL, /*sym=*/NULL) <= 0) { free(dso_name); goto out_err; } @@ -350,6 +355,7 @@ char *get_srcline_split(struct dso *dso, u64 addr, unsigned *line) dso__set_has_srcline(dso, false); dso__free_a2l(dso); dso__free_a2l_libbfd(dso); + dso__free_libdw(dso); } mutex_unlock(dso__lock(dso)); @@ -448,6 +454,9 @@ struct inline_node *dso__parse_addr_inlines(struct dso *dso, u64 addr, char *dso_name; struct inline_node *node; + if (!dso__has_srcline(dso)) + return NULL; + dso_name = srcline_dso_name(dso); if (dso_name == NULL) return NULL;