perf libdw: Fix Dwfl discovery with split files

dso__libdw_dwfl() opens the Dwfl with dso__long_name(), the file the
samples came from. When the debug information lives in a separate file,
symbol loading finds it and records it as the dso's symsrc filename, but
the Dwfl still refers to the original file, so libdw has no DWARF to
resolve addresses against.

Open the Dwfl with the symsrc filename when one is known and fall back
to the long name when it isn't.

The symsrc file is found while symbols are loaded, which can happen
after the Dwfl has been created and cached, so drop the cached Dwfl and
the addr2line cache built from it when the symsrc filename is set.  That
teardown can run while another thread is using the cache, so take the
dso lock across both the teardown and the Dwfl accessors, and re-read
dso__symsrc_filename() under the dso lock in libbfd__addr2line() and
cmd__addr2line() to prevent a TOCTOU race with cache invalidation.

Fixes: b7a2b011e9 ("perf powerpc: Unify the skip-callchain-idx libdw with that for addr2line")
Signed-off-by: Ian Rogers <irogers@google.com>
Assisted-by: Antigravity:gemini-3.1-pro
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
Ian Rogers 2026-09-16 22:04:48 -07:00 committed by Arnaldo Carvalho de Melo
parent 0824af00a8
commit bf96e296d5
9 changed files with 187 additions and 69 deletions

View File

@ -152,9 +152,10 @@ static int check_return_addr(struct dso *dso, Dwarf_Addr mapped_pc)
Dwarf_Addr end = mapped_pc; Dwarf_Addr end = mapped_pc;
bool signalp; bool signalp;
mutex_lock(dso__lock(dso));
dwfl = dso__libdw_dwfl(dso); dwfl = dso__libdw_dwfl(dso);
if (!dwfl) if (!dwfl)
return -1; goto out;
mod = dwfl_addrmodule(dwfl, mapped_pc); mod = dwfl_addrmodule(dwfl, mapped_pc);
if (!mod) { if (!mod) {
@ -183,6 +184,7 @@ static int check_return_addr(struct dso *dso, Dwarf_Addr mapped_pc)
rc = check_return_reg(ra_regno, frame); rc = check_return_reg(ra_regno, frame);
out: out:
mutex_unlock(dso__lock(dso));
return rc; return rc;
} }

View File

@ -15,6 +15,7 @@
#include <signal.h> #include <signal.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h>
#define MAX_INLINE_NEST 1024 #define MAX_INLINE_NEST 1024
@ -284,7 +285,7 @@ int cmd__addr2line(const char *dso_name, u64 addr,
struct inline_node *node, struct inline_node *node,
struct symbol *sym __maybe_unused) struct symbol *sym __maybe_unused)
{ {
struct child_process *a2l = dso__a2l(dso); struct child_process *a2l;
char *record_function = NULL; char *record_function = NULL;
char *record_filename = NULL; char *record_filename = NULL;
unsigned int record_line_nr = 0; unsigned int record_line_nr = 0;
@ -296,24 +297,38 @@ int cmd__addr2line(const char *dso_name, u64 addr,
ssize_t written; ssize_t written;
struct io io = { .eof = false }; struct io io = { .eof = false };
enum cmd_a2l_style cmd_a2l_style; enum cmd_a2l_style cmd_a2l_style;
const char *current_dso_name;
mutex_lock(dso__lock(dso));
current_dso_name = dso__symsrc_filename(dso) ?: dso_name;
a2l = dso__a2l(dso);
if (!a2l) { if (!a2l) {
if (!filename__has_section(dso_name, ".debug_line")) if (!filename__has_section(current_dso_name, ".debug_line"))
goto out; goto out_unlock;
dso__set_a2l(dso, dso__set_a2l(dso,
addr2line_subprocess_init(symbol_conf.addr2line_path, dso_name)); addr2line_subprocess_init(symbol_conf.addr2line_path,
current_dso_name));
a2l = dso__a2l(dso); a2l = dso__a2l(dso);
} }
if (a2l == NULL) { if (a2l == NULL) {
if (!symbol_conf.addr2line_disable_warn) if (!symbol_conf.addr2line_disable_warn)
pr_warning("%s %s: addr2line_subprocess_init failed\n", __func__, dso_name); pr_warning("%s %s: addr2line_subprocess_init failed\n", __func__, dso_name);
goto out; goto out_unlock;
} }
cmd_a2l_style = cmd_addr2line_configure(a2l, dso_name); cmd_a2l_style = cmd_addr2line_configure(a2l, dso_name);
if (cmd_a2l_style == BROKEN) if (cmd_a2l_style == BROKEN)
goto out; goto out_unlock;
/*
* Take ownership of the a2l subprocess so we can safely perform
* blocking IPC without holding the dso lock. If another thread
* resolves a symbol concurrently, it will spawn a new a2l process.
*/
dso__set_a2l(dso, NULL);
mutex_unlock(dso__lock(dso));
/* /*
* Send our request and then *deliberately* send something that can't be * Send our request and then *deliberately* send something that can't be
@ -414,12 +429,21 @@ int cmd__addr2line(const char *dso_name, u64 addr,
} }
out: out:
mutex_lock(dso__lock(dso));
free(record_function); free(record_function);
free(record_filename); free(record_filename);
if (io.eof) {
dso__set_a2l(dso, NULL); current_dso_name = dso__symsrc_filename(dso) ?: dso__long_name(dso);
if (!io.eof && dso__a2l(dso) == NULL && current_dso_name &&
!strcmp(current_dso_name, dso_name))
dso__set_a2l(dso, a2l);
else
addr2line_subprocess_cleanup(a2l); addr2line_subprocess_cleanup(a2l);
} mutex_unlock(dso__lock(dso));
return ret;
out_unlock:
mutex_unlock(dso__lock(dso));
return ret; return ret;
} }

View File

@ -2075,3 +2075,12 @@ struct debuginfo *dso__debuginfo(struct dso *dso)
free(name); free(name);
return dinfo; return dinfo;
} }
void dso__set_symsrc_filename(struct dso *dso, char *val)
{
RC_CHK_ACCESS(dso)->symsrc_filename = val;
dso__free_libdw(dso);
dso__free_a2l(dso);
dso__set_has_srcline(dso, true);
dso__set_a2l_fails(dso, 0);
}

View File

@ -735,10 +735,7 @@ static inline const char *dso__symsrc_filename(const struct dso *dso)
return RC_CHK_ACCESS(dso)->symsrc_filename; return RC_CHK_ACCESS(dso)->symsrc_filename;
} }
static inline void dso__set_symsrc_filename(struct dso *dso, char *val) void dso__set_symsrc_filename(struct dso *dso, char *val);
{
RC_CHK_ACCESS(dso)->symsrc_filename = val;
}
static inline void dso__free_symsrc_filename(struct dso *dso) static inline void dso__free_symsrc_filename(struct dso *dso)
{ {

View File

@ -226,7 +226,11 @@ int libbfd__addr2line(const char *dso_name, u64 addr,
struct symbol *sym) struct symbol *sym)
{ {
int ret = 0; int ret = 0;
struct a2l_data *a2l = dso__a2l(dso); struct a2l_data *a2l;
mutex_lock(dso__lock(dso));
dso_name = dso__symsrc_filename(dso) ?: dso_name;
a2l = dso__a2l(dso);
if (!a2l) { if (!a2l) {
a2l = addr2line_init(dso_name); a2l = addr2line_init(dso_name);
@ -236,7 +240,8 @@ int libbfd__addr2line(const char *dso_name, u64 addr,
if (a2l == NULL) { if (a2l == NULL) {
if (!symbol_conf.addr2line_disable_warn) if (!symbol_conf.addr2line_disable_warn)
pr_warning("addr2line_init failed for %s\n", dso_name); pr_warning("addr2line_init failed for %s\n", dso_name);
return 0; ret = -1;
goto out;
} }
a2l->addr = addr; a2l->addr = addr;
@ -244,14 +249,18 @@ int libbfd__addr2line(const char *dso_name, u64 addr,
bfd_map_over_sections(a2l->abfd, find_address_in_section, a2l); bfd_map_over_sections(a2l->abfd, find_address_in_section, a2l);
if (!a2l->found) if (!a2l->found) {
return 0; ret = 0;
goto out;
}
if (unwind_inlines) { if (unwind_inlines) {
int cnt = 0; int cnt = 0;
if (node && inline_list__append_dso_a2l(dso, node, sym)) if (node && inline_list__append_dso_a2l(dso, node, sym)) {
return 0; ret = 0;
goto out;
}
while (bfd_find_inliner_info(a2l->abfd, &a2l->filename, while (bfd_find_inliner_info(a2l->abfd, &a2l->filename,
&a2l->funcname, &a2l->line) && &a2l->funcname, &a2l->line) &&
@ -261,8 +270,10 @@ int libbfd__addr2line(const char *dso_name, u64 addr,
a2l->filename = NULL; a2l->filename = NULL;
if (node != NULL) { if (node != NULL) {
if (inline_list__append_dso_a2l(dso, node, sym)) if (inline_list__append_dso_a2l(dso, node, sym)) {
return 0; ret = 0;
goto out;
}
// found at least one inline frame // found at least one inline frame
ret = 1; ret = 1;
} }
@ -277,6 +288,8 @@ int libbfd__addr2line(const char *dso_name, u64 addr,
if (line) if (line)
*line = a2l->line; *line = a2l->line;
out:
mutex_unlock(dso__lock(dso));
return ret; return ret;
} }

View File

@ -35,7 +35,7 @@ struct Dwfl *dso__libdw_dwfl(struct dso *dso)
if (dwfl) if (dwfl)
return dwfl; return dwfl;
dso_name = dso__long_name(dso); dso_name = dso__symsrc_filename(dso) ?: dso__long_name(dso);
/* /*
* Initialize Dwfl session. * Initialize Dwfl session.
* We need to open the DSO file to report it to libdw. * We need to open the DSO file to report it to libdw.
@ -167,32 +167,41 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr,
struct dso *dso, bool unwind_inlines, struct dso *dso, bool unwind_inlines,
struct inline_node *node, struct symbol *sym) struct inline_node *node, struct symbol *sym)
{ {
Dwfl *dwfl = dso__libdw_dwfl(dso); Dwfl *dwfl;
Dwfl_Module *mod; Dwfl_Module *mod;
Dwfl_Line *dwline; Dwfl_Line *dwline;
Dwarf_Addr bias; Dwarf_Addr bias;
const char *src; const char *src;
int lineno = 0; int lineno = 0;
int ret = 0;
mutex_lock(dso__lock(dso));
dwfl = dso__libdw_dwfl(dso);
if (!dwfl) if (!dwfl)
return 0; goto out;
mod = dwfl_addrmodule(dwfl, addr); mod = dwfl_addrmodule(dwfl, addr);
if (!mod) if (!mod) {
return 0; ret = 0;
goto out;
}
/* /*
* Get/ignore the dwarf information. Determine the bias, difference * Get/ignore the dwarf information. Determine the bias, difference
* between the regular ELF addr2line addresses and those to use with * between the regular ELF addr2line addresses and those to use with
* libdw. * libdw.
*/ */
if (!dwfl_module_getdwarf(mod, &bias)) if (!dwfl_module_getdwarf(mod, &bias)) {
return 0; ret = -1;
goto out;
}
/* Find source line information for the address. */ /* Find source line information for the address. */
dwline = dwfl_module_getsrc(mod, addr + bias); dwline = dwfl_module_getsrc(mod, addr + bias);
if (!dwline) if (!dwline) {
return 0; ret = -1;
goto out;
}
/* Get line information. */ /* Get line information. */
src = dwfl_lineinfo(dwline, /*addr=*/NULL, &lineno, /*col=*/NULL, /*mtime=*/NULL, src = dwfl_lineinfo(dwline, /*addr=*/NULL, &lineno, /*col=*/NULL, /*mtime=*/NULL,
@ -219,7 +228,8 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr,
free(*file); free(*file);
*file = NULL; *file = NULL;
} }
return 0; ret = 0;
goto out;
} }
/* Walk from the parent down to the leaf. */ /* Walk from the parent down to the leaf. */
@ -235,8 +245,13 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr,
*file = NULL; *file = NULL;
} }
inline_node__clear_frames(node); inline_node__clear_frames(node);
return 0; ret = 0;
goto out;
} }
} }
return 1; ret = 1;
out:
mutex_unlock(dso__lock(dso));
return ret;
} }

View File

@ -1,40 +1,49 @@
// SPDX-License-Identifier: GPL-2.0 // SPDX-License-Identifier: GPL-2.0
#include "srcline.h" #include "srcline.h"
#include "addr2line.h"
#include "dso.h"
#include "callchain.h"
#include "libbfd.h"
#include "llvm.h"
#include "symbol.h"
#include "libdw.h"
#include "debug.h"
#include "util.h"
#include <inttypes.h> #include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <linux/string.h> #include <linux/string.h>
#include <linux/zalloc.h> #include <linux/zalloc.h>
#include "addr2line.h"
#include "callchain.h"
#include "debug.h"
#include "dso.h"
#include "libbfd.h"
#include "libdw.h"
#include "llvm.h"
#include "symbol.h"
#include "util.h"
bool srcline_full_filename; bool srcline_full_filename;
char *srcline__unknown = (char *)"??:0"; char *srcline__unknown = (char *)"??:0";
static const char *srcline_dso_name(struct dso *dso) static char *srcline_dso_name(struct dso *dso)
{ {
const char *dso_name; const char *dso_name;
char *ret = NULL;
mutex_lock(dso__lock(dso));
if (dso__symsrc_filename(dso)) if (dso__symsrc_filename(dso))
dso_name = dso__symsrc_filename(dso); dso_name = dso__symsrc_filename(dso);
else else
dso_name = dso__long_name(dso); dso_name = dso__long_name(dso);
if (dso_name[0] == '[') if (dso_name[0] == '[')
return NULL; goto out;
if (is_perf_pid_map_name(dso_name)) if (is_perf_pid_map_name(dso_name))
return NULL; goto out;
return dso_name; ret = strdup(dso_name);
out:
mutex_unlock(dso__lock(dso));
return ret;
} }
int inline_list__append(struct symbol *symbol, char *srcline, struct inline_node *node) int inline_list__append(struct symbol *symbol, char *srcline, struct inline_node *node)
@ -258,7 +267,7 @@ char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym,
char *file = NULL; char *file = NULL;
unsigned line = 0; unsigned line = 0;
char *srcline; char *srcline;
const char *dso_name; char *dso_name;
if (!dso__has_srcline(dso)) if (!dso__has_srcline(dso))
goto out; goto out;
@ -268,8 +277,11 @@ char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym,
goto out_err; goto out_err;
if (!addr2line(dso_name, addr, &file, &line, dso, if (!addr2line(dso_name, addr, &file, &line, dso,
unwind_inlines, /*node=*/NULL, sym)) unwind_inlines, /*node=*/NULL, sym)) {
free(dso_name);
goto out_err; goto out_err;
}
free(dso_name);
srcline = srcline_from_fileline(file, line); srcline = srcline_from_fileline(file, line);
free(file); free(file);
@ -277,16 +289,20 @@ char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym,
if (!srcline) if (!srcline)
goto out_err; goto out_err;
mutex_lock(dso__lock(dso));
dso__set_a2l_fails(dso, 0); dso__set_a2l_fails(dso, 0);
mutex_unlock(dso__lock(dso));
return srcline; return srcline;
out_err: out_err:
mutex_lock(dso__lock(dso));
dso__set_a2l_fails(dso, dso__a2l_fails(dso) + 1); dso__set_a2l_fails(dso, dso__a2l_fails(dso) + 1);
if (dso__a2l_fails(dso) > A2L_FAIL_LIMIT) { if (dso__a2l_fails(dso) > A2L_FAIL_LIMIT) {
dso__set_has_srcline(dso, false); dso__set_has_srcline(dso, false);
dso__free_a2l(dso); dso__free_a2l(dso);
} }
mutex_unlock(dso__lock(dso));
out: out:
if (!show_addr) if (!show_addr)
return (show_sym && sym) ? return (show_sym && sym) ?
@ -305,7 +321,7 @@ char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym,
char *get_srcline_split(struct dso *dso, u64 addr, unsigned *line) char *get_srcline_split(struct dso *dso, u64 addr, unsigned *line)
{ {
char *file = NULL; char *file = NULL;
const char *dso_name; char *dso_name;
if (!dso__has_srcline(dso)) if (!dso__has_srcline(dso))
return NULL; return NULL;
@ -315,18 +331,25 @@ char *get_srcline_split(struct dso *dso, u64 addr, unsigned *line)
goto out_err; goto out_err;
if (!addr2line(dso_name, addr, &file, line, dso, /*unwind_inlines=*/true, if (!addr2line(dso_name, addr, &file, line, dso, /*unwind_inlines=*/true,
/*node=*/NULL, /*sym=*/NULL)) /*node=*/NULL, /*sym=*/NULL)) {
free(dso_name);
goto out_err; goto out_err;
}
free(dso_name);
mutex_lock(dso__lock(dso));
dso__set_a2l_fails(dso, 0); dso__set_a2l_fails(dso, 0);
mutex_unlock(dso__lock(dso));
return file; return file;
out_err: out_err:
mutex_lock(dso__lock(dso));
dso__set_a2l_fails(dso, dso__a2l_fails(dso) + 1); dso__set_a2l_fails(dso, dso__a2l_fails(dso) + 1);
if (dso__a2l_fails(dso) > A2L_FAIL_LIMIT) { if (dso__a2l_fails(dso) > A2L_FAIL_LIMIT) {
dso__set_has_srcline(dso, false); dso__set_has_srcline(dso, false);
dso__free_a2l(dso); dso__free_a2l(dso);
} }
mutex_unlock(dso__lock(dso));
return NULL; return NULL;
} }
@ -420,13 +443,16 @@ void srcline__tree_delete(struct rb_root_cached *tree)
struct inline_node *dso__parse_addr_inlines(struct dso *dso, u64 addr, struct inline_node *dso__parse_addr_inlines(struct dso *dso, u64 addr,
struct symbol *sym) struct symbol *sym)
{ {
const char *dso_name; char *dso_name;
struct inline_node *node;
dso_name = srcline_dso_name(dso); dso_name = srcline_dso_name(dso);
if (dso_name == NULL) if (dso_name == NULL)
return NULL; return NULL;
return addr2inlines(dso_name, addr, dso, sym); node = addr2inlines(dso_name, addr, dso, sym);
free(dso_name);
return node;
} }
void inline_node__clear_frames(struct inline_node *node) void inline_node__clear_frames(struct inline_node *node)

View File

@ -42,8 +42,10 @@ static int __find_debuginfo(Dwfl_Module *mod __maybe_unused, void **userdata,
const struct dso *dso = *userdata; const struct dso *dso = *userdata;
assert(dso); assert(dso);
mutex_lock(dso__lock((struct dso *)dso));
if (dso__symsrc_filename(dso) && strcmp(file_name, dso__symsrc_filename(dso))) if (dso__symsrc_filename(dso) && strcmp(file_name, dso__symsrc_filename(dso)))
*debuginfo_file_name = strdup(dso__symsrc_filename(dso)); *debuginfo_file_name = strdup(dso__symsrc_filename(dso));
mutex_unlock(dso__lock((struct dso *)dso));
return -1; return -1;
} }

View File

@ -1,20 +1,28 @@
// SPDX-License-Identifier: GPL-2.0 // SPDX-License-Identifier: GPL-2.0
#include "unwind.h"
#include <inttypes.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <elf.h>
#include <fcntl.h>
#include <gelf.h>
#include <unistd.h>
#include <dwarf-regs.h>
#include "callchain.h" #include "callchain.h"
#include "debug.h" #include "debug.h"
#include "dso.h" #include "dso.h"
#include "env.h" #include "env.h"
#include "libunwind-arch/libunwind-arch.h"
#include "map.h" #include "map.h"
#include "perf_regs.h" #include "perf_regs.h"
#include "session.h" #include "session.h"
#include "symbol.h" #include "symbol.h"
#include "thread.h" #include "thread.h"
#include "unwind.h"
#include "libunwind-arch/libunwind-arch.h"
#include <dwarf-regs.h>
#include <elf.h>
#include <fcntl.h>
#include <gelf.h>
#include <inttypes.h>
#define DW_EH_PE_FORMAT_MASK 0x0f /* format of the encoded value */ #define DW_EH_PE_FORMAT_MASK 0x0f /* format of the encoded value */
#define DW_EH_PE_APPL_MASK 0x70 /* how the value is to be applied */ #define DW_EH_PE_APPL_MASK 0x70 /* how the value is to be applied */
@ -293,10 +301,19 @@ static int read_unwind_spec_debug_frame(struct dso *dso,
} }
if (ofs <= 0) { if (ofs <= 0) {
fd = open(dso__symsrc_filename(dso), O_RDONLY); char *alloc_name;
if (fd >= 0) {
ofs = elf_section_offset(fd, ".debug_frame"); mutex_lock(dso__lock(dso));
close(fd); alloc_name = dso__symsrc_filename(dso) ?
strdup(dso__symsrc_filename(dso)) : NULL;
mutex_unlock(dso__lock(dso));
if (alloc_name) {
fd = open(alloc_name, O_RDONLY);
if (fd >= 0) {
ofs = elf_section_offset(fd, ".debug_frame");
close(fd);
}
free(alloc_name);
} }
} }
@ -321,6 +338,7 @@ static int read_unwind_spec_debug_frame(struct dso *dso,
} }
} }
if (ofs > 0) { if (ofs > 0) {
mutex_lock(dso__lock(dso));
if (dso__symsrc_filename(dso) != NULL) { if (dso__symsrc_filename(dso) != NULL) {
pr_warning( pr_warning(
"%s: overwrite symsrc(%s,%s)\n", "%s: overwrite symsrc(%s,%s)\n",
@ -330,6 +348,7 @@ static int read_unwind_spec_debug_frame(struct dso *dso,
dso__free_symsrc_filename(dso); dso__free_symsrc_filename(dso);
} }
dso__set_symsrc_filename(dso, debuglink); dso__set_symsrc_filename(dso, debuglink);
mutex_unlock(dso__lock(dso));
} else { } else {
free(debuglink); free(debuglink);
} }
@ -429,12 +448,23 @@ int __libunwind__find_proc_info(void *as, uint64_t ip, void *pi, int need_unwind
dso__data_put_fd(dso); dso__data_put_fd(dso);
} }
symfile = dso__symsrc_filename(dso) ?: dso__name(dso); mutex_lock(dso__lock(dso));
symfile = dso__symsrc_filename(dso) ?
strdup(dso__symsrc_filename(dso)) :
strdup(dso__name(dso));
mutex_unlock(dso__lock(dso));
if (libunwind_arch__dwarf_find_debug_frame(ui->e_machine, /*found=*/0, &di, ip, if (symfile) {
base, symfile, start, map__end(map))) { if (libunwind_arch__dwarf_find_debug_frame(ui->e_machine,
ret = libunwind_arch__dwarf_search_unwind_table(ui->e_machine, as, ip, &di, pi, /*found=*/0, &di, ip,
need_unwind_info, arg); base, symfile, start,
map__end(map))) {
ret = libunwind_arch__dwarf_search_unwind_table(ui->e_machine, as,
ip, &di, pi,
need_unwind_info,
arg);
}
free((char *)symfile);
} }
} }
map__put(map); map__put(map);