mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v1 1/2] perf libdw: Fix Dwfl discovery with split files
@ 2026-09-14 17:07 Ian Rogers
  2026-09-14 17:07 ` [PATCH v1 2/2] perf dso: Separate libbfd and cmd addr2line caches to fix confusion Ian Rogers
  2026-09-16  6:35 ` [PATCH v2 0/3] perf srcline: Fix addr2line cache and fallback bugs Ian Rogers
  0 siblings, 2 replies; 6+ messages in thread
From: Ian Rogers @ 2026-09-14 17:07 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
	Thomas Richter, linux-perf-users, linux-kernel

This patch dynamically frees the cached Dwfl object and alternative a2l
caches whenever a newly discovered split-debug file is assigned to a
dso, and scopes accessors behind a mutex. To safely implement cache
teardowns upon rediscovery without introducing races with active readers,
the teardown is performed locked.

Signed-off-by: Ian Rogers <irogers@google.com>
Assisted-by: Antigravity:gemini-3.1-pro
---
 .../arch/powerpc/util/skip-callchain-idx.c    |  4 +-
 tools/perf/util/addr2line.c                   | 35 ++++++++---
 tools/perf/util/dso.c                         |  9 +++
 tools/perf/util/dso.h                         |  5 +-
 tools/perf/util/libbfd.c                      | 28 ++++++---
 tools/perf/util/libdw.c                       | 39 ++++++++----
 tools/perf/util/srcline.c                     | 61 ++++++++++++------
 tools/perf/util/unwind-libdw.c                |  2 +
 tools/perf/util/unwind-libunwind.c            | 62 ++++++++++++++-----
 9 files changed, 178 insertions(+), 67 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..472714cfad38 100644
--- a/tools/perf/arch/powerpc/util/skip-callchain-idx.c
+++ b/tools/perf/arch/powerpc/util/skip-callchain-idx.c
@@ -152,9 +152,10 @@ static int check_return_addr(struct dso *dso, Dwarf_Addr mapped_pc)
 	Dwarf_Addr	end = mapped_pc;
 	bool		signalp;
 
+	mutex_lock(dso__lock(dso));
 	dwfl = dso__libdw_dwfl(dso);
 	if (!dwfl)
-		return -1;
+		goto out;
 
 	mod = dwfl_addrmodule(dwfl, mapped_pc);
 	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);
 
 out:
+	mutex_unlock(dso__lock(dso));
 	return rc;
 }
 
diff --git a/tools/perf/util/addr2line.c b/tools/perf/util/addr2line.c
index 4b0d349ed334..7c7eea3cc8dd 100644
--- a/tools/perf/util/addr2line.c
+++ b/tools/perf/util/addr2line.c
@@ -284,7 +284,7 @@ int cmd__addr2line(const char *dso_name, u64 addr,
 		   struct inline_node *node,
 		   struct symbol *sym __maybe_unused)
 {
-	struct child_process *a2l = dso__a2l(dso);
+	struct child_process *a2l;
 	char *record_function = NULL;
 	char *record_filename = NULL;
 	unsigned int record_line_nr = 0;
@@ -296,10 +296,14 @@ int cmd__addr2line(const char *dso_name, u64 addr,
 	ssize_t written;
 	struct io io = { .eof = false };
 	enum cmd_a2l_style cmd_a2l_style;
+	const char *current_dso_name;
+
+	mutex_lock(dso__lock(dso));
+	a2l = dso__a2l(dso);
 
 	if (!a2l) {
 		if (!filename__has_section(dso_name, ".debug_line"))
-			goto out;
+			goto out_unlock;
 
 		dso__set_a2l(dso,
 			     addr2line_subprocess_init(symbol_conf.addr2line_path, dso_name));
@@ -309,11 +313,19 @@ 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);
-		goto out;
+		goto out_unlock;
 	}
 	cmd_a2l_style = cmd_addr2line_configure(a2l, dso_name);
 	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
@@ -414,12 +426,21 @@ int cmd__addr2line(const char *dso_name, u64 addr,
 	}
 
 out:
+	mutex_lock(dso__lock(dso));
 	free(record_function);
 	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);
-	}
+	mutex_unlock(dso__lock(dso));
+	return ret;
+
+out_unlock:
+	mutex_unlock(dso__lock(dso));
 	return ret;
 }
 
diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c
index 42bfe30a3b51..df39e6ca88e6 100644
--- a/tools/perf/util/dso.c
+++ b/tools/perf/util/dso.c
@@ -2075,3 +2075,12 @@ struct debuginfo *dso__debuginfo(struct dso *dso)
 	free(name);
 	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);
+}
diff --git a/tools/perf/util/dso.h b/tools/perf/util/dso.h
index 55c4aaa53c38..7966c7048c85 100644
--- a/tools/perf/util/dso.h
+++ b/tools/perf/util/dso.h
@@ -735,10 +735,7 @@ static inline const char *dso__symsrc_filename(const struct dso *dso)
 	return RC_CHK_ACCESS(dso)->symsrc_filename;
 }
 
-static inline void dso__set_symsrc_filename(struct dso *dso, char *val)
-{
-	RC_CHK_ACCESS(dso)->symsrc_filename = val;
-}
+void dso__set_symsrc_filename(struct dso *dso, char *val);
 
 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 33dc6158b2b1..d0a27b14ee0d 100644
--- a/tools/perf/util/libbfd.c
+++ b/tools/perf/util/libbfd.c
@@ -226,7 +226,10 @@ int libbfd__addr2line(const char *dso_name, u64 addr,
 		      struct symbol *sym)
 {
 	int ret = 0;
-	struct a2l_data *a2l = dso__a2l(dso);
+	struct a2l_data *a2l;
+
+	mutex_lock(dso__lock(dso));
+	a2l = dso__a2l(dso);
 
 	if (!a2l) {
 		a2l = addr2line_init(dso_name);
@@ -236,7 +239,8 @@ int libbfd__addr2line(const char *dso_name, u64 addr,
 	if (a2l == NULL) {
 		if (!symbol_conf.addr2line_disable_warn)
 			pr_warning("addr2line_init failed for %s\n", dso_name);
-		return 0;
+		ret = -1;
+		goto out;
 	}
 
 	a2l->addr = addr;
@@ -244,14 +248,18 @@ int libbfd__addr2line(const char *dso_name, u64 addr,
 
 	bfd_map_over_sections(a2l->abfd, find_address_in_section, a2l);
 
-	if (!a2l->found)
-		return 0;
+	if (!a2l->found) {
+		ret = 0;
+		goto out;
+	}
 
 	if (unwind_inlines) {
 		int cnt = 0;
 
-		if (node && inline_list__append_dso_a2l(dso, node, sym))
-			return 0;
+		if (node && inline_list__append_dso_a2l(dso, node, sym)) {
+			ret = 0;
+			goto out;
+		}
 
 		while (bfd_find_inliner_info(a2l->abfd, &a2l->filename,
 					     &a2l->funcname, &a2l->line) &&
@@ -261,8 +269,10 @@ int libbfd__addr2line(const char *dso_name, u64 addr,
 				a2l->filename = NULL;
 
 			if (node != NULL) {
-				if (inline_list__append_dso_a2l(dso, node, sym))
-					return 0;
+				if (inline_list__append_dso_a2l(dso, node, sym)) {
+					ret = 0;
+					goto out;
+				}
 				// found at least one inline frame
 				ret = 1;
 			}
@@ -277,6 +287,8 @@ int libbfd__addr2line(const char *dso_name, u64 addr,
 	if (line)
 		*line = a2l->line;
 
+out:
+	mutex_unlock(dso__lock(dso));
 	return ret;
 }
 
diff --git a/tools/perf/util/libdw.c b/tools/perf/util/libdw.c
index 4ca7e7e4fbe9..c4504cceb313 100644
--- a/tools/perf/util/libdw.c
+++ b/tools/perf/util/libdw.c
@@ -35,7 +35,7 @@ struct Dwfl *dso__libdw_dwfl(struct dso *dso)
 	if (dwfl)
 		return dwfl;
 
-	dso_name = dso__long_name(dso);
+	dso_name = dso__symsrc_filename(dso) ?: dso__long_name(dso);
 	/*
 	 * Initialize Dwfl session.
 	 * 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 inline_node *node, struct symbol *sym)
 {
-	Dwfl *dwfl = dso__libdw_dwfl(dso);
+	Dwfl *dwfl;
 	Dwfl_Module *mod;
 	Dwfl_Line *dwline;
 	Dwarf_Addr bias;
 	const char *src;
 	int lineno = 0;
+	int ret = 0;
 
+	mutex_lock(dso__lock(dso));
+	dwfl = dso__libdw_dwfl(dso);
 	if (!dwfl)
-		return 0;
+		goto out;
 
 	mod = dwfl_addrmodule(dwfl, addr);
-	if (!mod)
-		return 0;
+	if (!mod) {
+		ret = 0;
+		goto out;
+	}
 
 	/*
 	 * Get/ignore the dwarf information. Determine the bias, difference
 	 * 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)) {
+		ret = -1;
+		goto out;
+	}
 
 	/* Find source line information for the address. */
 	dwline = dwfl_module_getsrc(mod, addr + bias);
-	if (!dwline)
-		return 0;
+	if (!dwline) {
+		ret = -1;
+		goto out;
+	}
 
 	/* Get line information. */
 	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);
 				*file = NULL;
 			}
-			return 0;
+			ret = 0;
+			goto out;
 		}
 
 		/* 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;
 			}
 			inline_node__clear_frames(node);
-			return 0;
+			ret = 0;
+			goto out;
 		}
 	}
-	return 1;
+	ret = 1;
+
+out:
+	mutex_unlock(dso__lock(dso));
+	return ret;
 }
diff --git a/tools/perf/util/srcline.c b/tools/perf/util/srcline.c
index b082178c279b..e60dea472507 100644
--- a/tools/perf/util/srcline.c
+++ b/tools/perf/util/srcline.c
@@ -1,40 +1,48 @@
 // SPDX-License-Identifier: GPL-2.0
 #include "srcline.h"
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <linux/string.h>
+#include <linux/zalloc.h>
+
 #include "addr2line.h"
-#include "dso.h"
 #include "callchain.h"
+#include "debug.h"
+#include "dso.h"
 #include "libbfd.h"
+#include "libdw.h"
 #include "llvm.h"
 #include "symbol.h"
-#include "libdw.h"
-#include "debug.h"
 #include "util.h"
 
-#include <inttypes.h>
-#include <string.h>
-#include <linux/string.h>
-#include <linux/zalloc.h>
-
 bool srcline_full_filename;
 
 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;
+	char *ret = NULL;
 
+	mutex_lock(dso__lock(dso));
 	if (dso__symsrc_filename(dso))
 		dso_name = dso__symsrc_filename(dso);
 	else
 		dso_name = dso__long_name(dso);
 
 	if (dso_name[0] == '[')
-		return NULL;
+		goto out;
 
 	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)
@@ -258,7 +266,7 @@ char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym,
 	char *file = NULL;
 	unsigned line = 0;
 	char *srcline;
-	const char *dso_name;
+	char *dso_name;
 
 	if (!dso__has_srcline(dso))
 		goto out;
@@ -268,8 +276,11 @@ char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym,
 		goto out_err;
 
 	if (!addr2line(dso_name, addr, &file, &line, dso,
-		       unwind_inlines, /*node=*/NULL, sym))
+		       unwind_inlines, /*node=*/NULL, sym)) {
+		free(dso_name);
 		goto out_err;
+	}
+	free(dso_name);
 
 	srcline = srcline_from_fileline(file, line);
 	free(file);
@@ -277,16 +288,20 @@ char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym,
 	if (!srcline)
 		goto out_err;
 
+	mutex_lock(dso__lock(dso));
 	dso__set_a2l_fails(dso, 0);
+	mutex_unlock(dso__lock(dso));
 
 	return srcline;
 
 out_err:
+	mutex_lock(dso__lock(dso));
 	dso__set_a2l_fails(dso, dso__a2l_fails(dso) + 1);
 	if (dso__a2l_fails(dso) > A2L_FAIL_LIMIT) {
 		dso__set_has_srcline(dso, false);
 		dso__free_a2l(dso);
 	}
+	mutex_unlock(dso__lock(dso));
 out:
 	if (!show_addr)
 		return (show_sym && sym) ?
@@ -305,7 +320,7 @@ char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym,
 char *get_srcline_split(struct dso *dso, u64 addr, unsigned *line)
 {
 	char *file = NULL;
-	const char *dso_name;
+	char *dso_name;
 
 	if (!dso__has_srcline(dso))
 		return NULL;
@@ -315,18 +330,25 @@ char *get_srcline_split(struct dso *dso, u64 addr, unsigned *line)
 		goto out_err;
 
 	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;
+	}
+	free(dso_name);
 
+	mutex_lock(dso__lock(dso));
 	dso__set_a2l_fails(dso, 0);
+	mutex_unlock(dso__lock(dso));
 	return file;
 
 out_err:
+	mutex_lock(dso__lock(dso));
 	dso__set_a2l_fails(dso, dso__a2l_fails(dso) + 1);
 	if (dso__a2l_fails(dso) > A2L_FAIL_LIMIT) {
 		dso__set_has_srcline(dso, false);
 		dso__free_a2l(dso);
 	}
+	mutex_unlock(dso__lock(dso));
 
 	return NULL;
 }
@@ -420,13 +442,16 @@ void srcline__tree_delete(struct rb_root_cached *tree)
 struct inline_node *dso__parse_addr_inlines(struct dso *dso, u64 addr,
 					    struct symbol *sym)
 {
-	const char *dso_name;
+	char *dso_name;
+	struct inline_node *node;
 
 	dso_name = srcline_dso_name(dso);
 	if (dso_name == 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)
diff --git a/tools/perf/util/unwind-libdw.c b/tools/perf/util/unwind-libdw.c
index 8d9fa6fd81c3..60989d1ed4a0 100644
--- a/tools/perf/util/unwind-libdw.c
+++ b/tools/perf/util/unwind-libdw.c
@@ -42,8 +42,10 @@ static int __find_debuginfo(Dwfl_Module *mod __maybe_unused, void **userdata,
 	const struct dso *dso = *userdata;
 
 	assert(dso);
+	mutex_lock(dso__lock((struct dso *)dso));
 	if (dso__symsrc_filename(dso) && strcmp(file_name, dso__symsrc_filename(dso)))
 		*debuginfo_file_name = strdup(dso__symsrc_filename(dso));
+	mutex_unlock(dso__lock((struct dso *)dso));
 	return -1;
 }
 
diff --git a/tools/perf/util/unwind-libunwind.c b/tools/perf/util/unwind-libunwind.c
index 73d191ce51a5..3bccab303282 100644
--- a/tools/perf/util/unwind-libunwind.c
+++ b/tools/perf/util/unwind-libunwind.c
@@ -1,20 +1,26 @@
 // SPDX-License-Identifier: GPL-2.0
+#include "unwind.h"
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <elf.h>
+#include <fcntl.h>
+#include <gelf.h>
+
+#include <dwarf-regs.h>
+
 #include "callchain.h"
 #include "debug.h"
 #include "dso.h"
 #include "env.h"
+#include "libunwind-arch/libunwind-arch.h"
 #include "map.h"
 #include "perf_regs.h"
 #include "session.h"
 #include "symbol.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_APPL_MASK	0x70	/* how the value is to be applied */
@@ -293,10 +299,19 @@ static int read_unwind_spec_debug_frame(struct dso *dso,
 		}
 
 		if (ofs <= 0) {
-			fd = open(dso__symsrc_filename(dso), O_RDONLY);
-			if (fd >= 0) {
-				ofs = elf_section_offset(fd, ".debug_frame");
-				close(fd);
+			char *alloc_name;
+
+			mutex_lock(dso__lock(dso));
+			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 +336,7 @@ static int read_unwind_spec_debug_frame(struct dso *dso,
 				}
 			}
 			if (ofs > 0) {
+				mutex_lock(dso__lock(dso));
 				if (dso__symsrc_filename(dso) != NULL) {
 					pr_warning(
 						"%s: overwrite symsrc(%s,%s)\n",
@@ -330,6 +346,7 @@ static int read_unwind_spec_debug_frame(struct dso *dso,
 					dso__free_symsrc_filename(dso);
 				}
 				dso__set_symsrc_filename(dso, debuglink);
+				mutex_unlock(dso__lock(dso));
 			} else {
 				free(debuglink);
 			}
@@ -429,12 +446,23 @@ int __libunwind__find_proc_info(void *as, uint64_t ip, void *pi, int need_unwind
 			dso__data_put_fd(dso);
 		}
 
-		symfile = dso__symsrc_filename(dso) ?: dso__name(dso);
-
-		if (libunwind_arch__dwarf_find_debug_frame(ui->e_machine, /*found=*/0, &di, ip,
-							   base, symfile, start, map__end(map))) {
-			ret = libunwind_arch__dwarf_search_unwind_table(ui->e_machine, as, ip, &di, pi,
-									need_unwind_info, arg);
+		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 (symfile) {
+			if (libunwind_arch__dwarf_find_debug_frame(ui->e_machine,
+								   /*found=*/0, &di, ip,
+								   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);
-- 
2.55.0.1032.g73a4cd73de-goog


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

end of thread, other threads:[~2026-09-16  6:36 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-14 17:07 [PATCH v1 1/2] perf libdw: Fix Dwfl discovery with split files Ian Rogers
2026-09-14 17:07 ` [PATCH v1 2/2] perf dso: Separate libbfd and cmd addr2line caches to fix confusion Ian Rogers
2026-09-16  6:35 ` [PATCH v2 0/3] perf srcline: Fix addr2line cache and fallback bugs Ian Rogers
2026-09-16  6:35   ` [PATCH v2 1/3] perf libdw: Fix Dwfl discovery with split files Ian Rogers
2026-09-16  6:35   ` [PATCH v2 2/3] perf dso: Separate libbfd and cmd addr2line caches to fix confusion Ian Rogers
2026-09-16  6:35   ` [PATCH v2 3/3] perf libbfd: Report success when an address is found Ian Rogers

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

all inboxes | Powered by JetHome®