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: bf96e296d5 ("perf libdw: Fix Dwfl discovery with split files")
Signed-off-by: Ian Rogers <irogers@google.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alessio Podda <aleph.pi.gh@gmail.com>
Cc: Alireza Haghdoost <haghdoost@uber.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Clark <james.clark@linaro.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Michael Liang <mliang@purestorage.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Shimin Guo <shimin.guo@skydio.com>
Cc: Tanushree Shah <tshah@linux.ibm.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
Ian Rogers 2026-10-25 23:50:43 -07:00 committed by Arnaldo Carvalho de Melo
parent cb28bc71af
commit 62d7cdc8d5
8 changed files with 115 additions and 50 deletions

View File

@ -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

View File

@ -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)
{

View File

@ -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;
}
}

View File

@ -7,6 +7,7 @@
#include "callchain.h"
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <elfutils/libdwfl.h>
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;
}
}

View File

@ -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)

View File

@ -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 == "<invalid>") {
/* Match the convention of libbfd. */
if (line_info.FileName == "<invalid>") {
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<DIInliningInfo> 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 == "<invalid>")
/* Match the convention of libbfd. */
dst.filename = nullptr;
else
dst.filename = strdup(src.FileName.c_str());
if (src.FileName == "<invalid>") {
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<DILineInfo> 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);
}
}

View File

@ -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);

View File

@ -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;