[PATCH v2 0/3] perf srcline: Fix addr2line cache and fallback bugs

Ian Rogers posted 3 patches 1 week, 1 day ago
There is a newer version of this series
.../arch/powerpc/util/skip-callchain-idx.c    |  4 +-
tools/perf/util/addr2line.c                   | 35 ++++++++---
tools/perf/util/dso.c                         | 58 +++++++++++------
tools/perf/util/dso.h                         | 26 ++++++--
tools/perf/util/libbfd.c                      | 52 ++++++++++-----
tools/perf/util/libdw.c                       | 39 ++++++++----
tools/perf/util/srcline.c                     | 63 +++++++++++++------
tools/perf/util/unwind-libdw.c                |  2 +
tools/perf/util/unwind-libunwind.c            | 62 +++++++++++++-----
9 files changed, 245 insertions(+), 96 deletions(-)
[PATCH v2 0/3] perf srcline: Fix addr2line cache and fallback bugs
Posted by Ian Rogers 1 week, 1 day ago
Three fixes to source line resolution, all on the addr2line paths.

1) libdw is pointed at the wrong file. dso__libdw_dwfl() opens the Dwfl
   with the name of the file the samples came from, but when the debug
   information is in a separate file, symbol loading records that as the
   dso's symsrc filename and the Dwfl is left referring to a file with
   no DWARF in it.

2) The dso addr2line cache is shared between implementations. The libbfd
   reader caches a struct a2l_data and the command line fallback caches
   a struct child_process through the same pointer, so once a dso has
   fallen back from one to the other the cached object is read back as
   the wrong type.

3) libbfd only reports success when the caller asked for a file name.
   addr2inlines() doesn't, so srcline.c treats a resolved address as a
   failure and tries the next implementation, which appends its own
   frames to the ones libbfd already appended. Every frame that isn't
   inlined is then reported twice. This is the default when perf is
   built with libbfd but without libdw:

     $ perf record --call-graph dwarf -- perf test -w inlineloop 1
     $ perf script --fields +srcline
     ...
                 56051a99503a inlineloop+0x8a (perf)
         inlineloop.c:47
                 56051a99503a inlineloop+0x8a (perf)
         inlineloop.c:47
     ...

   With addr2line.style set to "libbfd,addr2line" so the fallback is
   taken, the script output for that workload drops from 288 lines to
   176, and each repeated frame goes from appearing 16 times to 8.

Changes in v2:
 - Add patch 3, so that a resolved address isn't retried by the next
   addr2line implementation and reported twice.
 - Patch 1: rewrite the commit message to describe the wrong file the
   Dwfl is opened with, which is the actual bug, rather than the cache
   teardown that follows from it, and add the Fixes tag.
 - Rebase onto perf-tools-next.

This series is independent of "perf build: Fix builds with clang and
BUILD_NONDISTRO" posted earlier and applies with or without it.

Testing:

 - "perf test addr2line" and the hists tests pass.
 - Builds in 13 configurations, including BUILD_NONDISTRO=1 where the
   libbfd reader is actually compiled, and with clang and gcc.
 - Every patch builds on its own, so the series stays bisectable.

Ian Rogers (3):
  perf libdw: Fix Dwfl discovery with split files
  perf dso: Separate libbfd and cmd addr2line caches to fix confusion
  perf libbfd: Report success when an address is found

 .../arch/powerpc/util/skip-callchain-idx.c    |  4 +-
 tools/perf/util/addr2line.c                   | 35 ++++++++---
 tools/perf/util/dso.c                         | 58 +++++++++++------
 tools/perf/util/dso.h                         | 26 ++++++--
 tools/perf/util/libbfd.c                      | 52 ++++++++++-----
 tools/perf/util/libdw.c                       | 39 ++++++++----
 tools/perf/util/srcline.c                     | 63 +++++++++++++------
 tools/perf/util/unwind-libdw.c                |  2 +
 tools/perf/util/unwind-libunwind.c            | 62 +++++++++++++-----
 9 files changed, 245 insertions(+), 96 deletions(-)


base-commit: 91b0782fc9e9d2f0a40b5256146e014802fdbb36
prerequisite-patch-id: b6fdc526887b71fb66f5fe0c0d41f7ef9493861e
prerequisite-patch-id: d47077f674c700f4f6296e9367f7ddfe004aea89
prerequisite-patch-id: ed0db23450840601762fe85ca1bef06ce6d28fe7
prerequisite-patch-id: d84e6b96d533ee6ed549e26e94216e2da60f7f74
prerequisite-patch-id: 6e8f19551d771621a5037096626cfe7e271b36f9
prerequisite-patch-id: 3768dfd588c7439deb741bce44c74763011c6be7
prerequisite-patch-id: babac99a4faa3b1525e44d3f34ab33a88103334c
prerequisite-patch-id: 5279827453fea1536abc620aa33c887c44e41ee9
prerequisite-patch-id: bdc0d648a577142b9270fb59b54496bc2bf8d5ea
-- 
2.55.0.1032.g73a4cd73de-goog
[PATCH v3 0/3] perf srcline: Fix addr2line cache and fallback bugs
Posted by Ian Rogers 1 week, 1 day ago
Three fixes to source line resolution, all on the addr2line paths.

1) libdw is pointed at the wrong file. dso__libdw_dwfl() opens the Dwfl
   with the name of the file the samples came from, but when the debug
   information is in a separate file, symbol loading records that as the
   dso's symsrc filename and the Dwfl is left referring to a file with
   no DWARF in it.

2) The dso addr2line cache is shared between implementations. The libbfd
   reader caches a struct a2l_data and the command line fallback caches
   a struct child_process through the same pointer, so once a dso has
   fallen back from one to the other the cached object is read back as
   the wrong type.

3) libbfd only reports success when the caller asked for a file name.
   addr2inlines() doesn't, so srcline.c treats a resolved address as a
   failure and tries the next implementation, which appends its own
   frames to the ones libbfd already appended. Every frame that isn't
   inlined is then reported twice. This is the default when perf is
   built with libbfd but without libdw:

     $ perf record --call-graph dwarf -- perf test -w inlineloop 1
     $ perf script --fields +srcline
     ...
                 56051a99503a inlineloop+0x8a (perf)
         inlineloop.c:47
                 56051a99503a inlineloop+0x8a (perf)
         inlineloop.c:47
     ...

   With addr2line.style set to "libbfd,addr2line" so the fallback is
   taken, the script output for that workload drops from 288 lines to
   176, and each repeated frame goes from appearing 16 times to 8.

Changes in v3:
 - Patch 1: re-read dso__symsrc_filename() under dso__lock() in
   libbfd__addr2line() and cmd__addr2line() before initializing the
   addr2line cache. In v2, __get_srcline() fetched dso_name before the
   lock was acquired, leaving a TOCTOU window where another thread
   calling dso__set_symsrc_filename() could invalidate the cache and
   cause the new cache to be initialized with the stale dso_name.

Changes in v2:
 - Add patch 3, so that a resolved address isn't retried by the next
   addr2line implementation and reported twice.
 - Patch 1: rewrite the commit message to describe the wrong file the
   Dwfl is opened with, which is the actual bug, rather than the cache
   teardown that follows from it, and add the Fixes tag.
 - Rebase onto perf-tools-next.

This series is independent of "perf build: Fix builds with clang and
BUILD_NONDISTRO" posted earlier and applies with or without it.

Testing:

 - "perf test addr2line" and the hists tests pass.
 - Builds in 13 configurations, including BUILD_NONDISTRO=1 where the
   libbfd reader is actually compiled, and with clang and gcc.
 - Every patch builds on its own, so the series stays bisectable.

Ian Rogers (3):
  perf libdw: Fix Dwfl discovery with split files
  perf dso: Separate libbfd and cmd addr2line caches to fix confusion
  perf libbfd: Report success when an address is found

 .../arch/powerpc/util/skip-callchain-idx.c    |  4 +-
 tools/perf/util/addr2line.c                   | 41 +++++++++---
 tools/perf/util/dso.c                         | 58 +++++++++++------
 tools/perf/util/dso.h                         | 26 ++++++--
 tools/perf/util/libbfd.c                      | 53 +++++++++++-----
 tools/perf/util/libdw.c                       | 39 ++++++++----
 tools/perf/util/srcline.c                     | 63 +++++++++++++------
 tools/perf/util/unwind-libdw.c                |  2 +
 tools/perf/util/unwind-libunwind.c            | 62 +++++++++++++-----
 9 files changed, 250 insertions(+), 98 deletions(-)


base-commit: 91b0782fc9e9d2f0a40b5256146e014802fdbb36
prerequisite-patch-id: b6fdc526887b71fb66f5fe0c0d41f7ef9493861e
prerequisite-patch-id: d47077f674c700f4f6296e9367f7ddfe004aea89
prerequisite-patch-id: ed0db23450840601762fe85ca1bef06ce6d28fe7
prerequisite-patch-id: d84e6b96d533ee6ed549e26e94216e2da60f7f74
prerequisite-patch-id: 6e8f19551d771621a5037096626cfe7e271b36f9
prerequisite-patch-id: 3768dfd588c7439deb741bce44c74763011c6be7
prerequisite-patch-id: babac99a4faa3b1525e44d3f34ab33a88103334c
prerequisite-patch-id: 5279827453fea1536abc620aa33c887c44e41ee9
prerequisite-patch-id: bdc0d648a577142b9270fb59b54496bc2bf8d5ea
-- 
2.55.0.1082.g2b9226bbc0-goog
Re: [PATCH v3 0/3] perf srcline: Fix addr2line cache and fallback bugs
Posted by Arnaldo Carvalho de Melo 6 days, 9 hours ago
On Wed, Sep 16, 2026 at 04:43:59PM -0700, Ian Rogers wrote:
> Three fixes to source line resolution, all on the addr2line paths.
> 
> 1) libdw is pointed at the wrong file. dso__libdw_dwfl() opens the Dwfl
>    with the name of the file the samples came from, but when the debug
>    information is in a separate file, symbol loading records that as the
>    dso's symsrc filename and the Dwfl is left referring to a file with
>    no DWARF in it.
> 
> 2) The dso addr2line cache is shared between implementations. The libbfd
>    reader caches a struct a2l_data and the command line fallback caches
>    a struct child_process through the same pointer, so once a dso has
>    fallen back from one to the other the cached object is read back as
>    the wrong type.
> 
> 3) libbfd only reports success when the caller asked for a file name.
>    addr2inlines() doesn't, so srcline.c treats a resolved address as a
>    failure and tries the next implementation, which appends its own
>    frames to the ones libbfd already appended. Every frame that isn't
>    inlined is then reported twice. This is the default when perf is
>    built with libbfd but without libdw:
> 
>      $ perf record --call-graph dwarf -- perf test -w inlineloop 1
>      $ perf script --fields +srcline
>      ...
>                  56051a99503a inlineloop+0x8a (perf)
>          inlineloop.c:47
>                  56051a99503a inlineloop+0x8a (perf)
>          inlineloop.c:47
>      ...
> 
>    With addr2line.style set to "libbfd,addr2line" so the fallback is
>    taken, the script output for that workload drops from 288 lines to
>    176, and each repeated frame goes from appearing 16 times to 8.

Thanks, applied to perf-tools-next, for v7.4.

- Arnaldo
[PATCH v4 0/3] perf srcline: Fix addr2line cache and fallback bugs
Posted by Ian Rogers 1 week, 1 day ago
Three fixes to source line resolution, all on the addr2line paths.

1) libdw is pointed at the wrong file. dso__libdw_dwfl() opens the Dwfl
   with the name of the file the samples came from, but when the debug
   information is in a separate file, symbol loading records that as the
   dso's symsrc filename and the Dwfl is left referring to a file with
   no DWARF in it.

2) The dso addr2line cache is shared between implementations. The libbfd
   reader caches a struct a2l_data and the command line fallback caches
   a struct child_process through the same pointer, so once a dso has
   fallen back from one to the other the cached object is read back as
   the wrong type.

3) libbfd only reports success when the caller asked for a file name.
   addr2inlines() doesn't, so srcline.c treats a resolved address as a
   failure and tries the next implementation, which appends its own
   frames to the ones libbfd already appended. Every frame that isn't
   inlined is then reported twice. This is the default when perf is
   built with libbfd but without libdw:

     $ perf record --call-graph dwarf -- perf test -w inlineloop 1
     $ perf script --fields +srcline
     ...
                 56051a99503a inlineloop+0x8a (perf)
         inlineloop.c:47
                 56051a99503a inlineloop+0x8a (perf)
         inlineloop.c:47
     ...

   With addr2line.style set to "libbfd,addr2line" so the fallback is
   taken, the script output for that workload drops from 288 lines to
   176, and each repeated frame goes from appearing 16 times to 8.

Changes in v4:
 - Patch 1: add the headers that are used, rather than relying on glibc
   including them for us, since these files are rearranging their
   includes anyway: <unistd.h> in addr2line.c for write(), <stdio.h> in
   srcline.c for asprintf(), and <unistd.h> and <limits.h> in
   unwind-libunwind.c for close() and PATH_MAX. These are all
   pre-existing omissions that musl libc builds would trip over.
 - Patch 2: drop the duplicate "dso.h" that the include reordering left
   behind in the alphabetical block of dso.c, as it is already included
   at the top of the file.

Changes in v3:
 - Patch 1: re-read dso__symsrc_filename() under dso__lock() in
   libbfd__addr2line() and cmd__addr2line() before initializing the
   addr2line cache. In v2, __get_srcline() fetched dso_name before the
   lock was acquired, leaving a TOCTOU window where another thread
   calling dso__set_symsrc_filename() could invalidate the cache and
   cause the new cache to be initialized with the stale dso_name.

Changes in v2:
 - Add patch 3, so that a resolved address isn't retried by the next
   addr2line implementation and reported twice.
 - Patch 1: rewrite the commit message to describe the wrong file the
   Dwfl is opened with, which is the actual bug, rather than the cache
   teardown that follows from it, and add the Fixes tag.
 - Rebase onto perf-tools-next.

This series is independent of "perf build: Fix builds with clang and
BUILD_NONDISTRO" posted earlier and applies with or without it.

Testing:

 - "perf test addr2line" and the hists tests pass.
 - Builds in 13 configurations, including BUILD_NONDISTRO=1 where the
   libbfd reader is actually compiled, and with clang and gcc.
 - Every patch builds on its own, so the series stays bisectable.

Ian Rogers (3):
  perf libdw: Fix Dwfl discovery with split files
  perf dso: Separate libbfd and cmd addr2line caches to fix confusion
  perf libbfd: Report success when an address is found

 .../arch/powerpc/util/skip-callchain-idx.c    |  4 +-
 tools/perf/util/addr2line.c                   | 42 +++++++++---
 tools/perf/util/dso.c                         | 57 +++++++++++------
 tools/perf/util/dso.h                         | 26 ++++++--
 tools/perf/util/libbfd.c                      | 53 ++++++++++-----
 tools/perf/util/libdw.c                       | 39 +++++++----
 tools/perf/util/srcline.c                     | 64 +++++++++++++------
 tools/perf/util/unwind-libdw.c                |  2 +
 tools/perf/util/unwind-libunwind.c            | 64 ++++++++++++++-----
 9 files changed, 253 insertions(+), 98 deletions(-)


base-commit: 91b0782fc9e9d2f0a40b5256146e014802fdbb36
prerequisite-patch-id: b6fdc526887b71fb66f5fe0c0d41f7ef9493861e
prerequisite-patch-id: d47077f674c700f4f6296e9367f7ddfe004aea89
prerequisite-patch-id: ed0db23450840601762fe85ca1bef06ce6d28fe7
prerequisite-patch-id: d84e6b96d533ee6ed549e26e94216e2da60f7f74
prerequisite-patch-id: 6e8f19551d771621a5037096626cfe7e271b36f9
prerequisite-patch-id: 3768dfd588c7439deb741bce44c74763011c6be7
prerequisite-patch-id: babac99a4faa3b1525e44d3f34ab33a88103334c
prerequisite-patch-id: 5279827453fea1536abc620aa33c887c44e41ee9
prerequisite-patch-id: bdc0d648a577142b9270fb59b54496bc2bf8d5ea
-- 
2.55.0.1082.g2b9226bbc0-goog
[PATCH v4 1/3] perf libdw: Fix Dwfl discovery with split files
Posted by Ian Rogers 1 week, 1 day ago
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: b7a2b011e962 ("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
---
 .../arch/powerpc/util/skip-callchain-idx.c    |  4 +-
 tools/perf/util/addr2line.c                   | 42 +++++++++---
 tools/perf/util/dso.c                         |  9 +++
 tools/perf/util/dso.h                         |  5 +-
 tools/perf/util/libbfd.c                      | 29 ++++++---
 tools/perf/util/libdw.c                       | 39 +++++++----
 tools/perf/util/srcline.c                     | 62 ++++++++++++------
 tools/perf/util/unwind-libdw.c                |  2 +
 tools/perf/util/unwind-libunwind.c            | 64 ++++++++++++++-----
 9 files changed, 187 insertions(+), 69 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..6f64f6b42442 100644
--- a/tools/perf/util/addr2line.c
+++ b/tools/perf/util/addr2line.c
@@ -15,6 +15,7 @@
 #include <signal.h>
 #include <stdlib.h>
 #include <string.h>
+#include <unistd.h>
 
 #define MAX_INLINE_NEST 1024
 
@@ -284,7 +285,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,24 +297,38 @@ 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));
+	current_dso_name = dso__symsrc_filename(dso) ?: dso_name;
+	a2l = dso__a2l(dso);
 
 	if (!a2l) {
-		if (!filename__has_section(dso_name, ".debug_line"))
-			goto out;
+		if (!filename__has_section(current_dso_name, ".debug_line"))
+			goto out_unlock;
 
 		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);
 	}
 
 	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 +429,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 1b23a261a9ce..807944cd978c 100644
--- a/tools/perf/util/libbfd.c
+++ b/tools/perf/util/libbfd.c
@@ -255,7 +255,11 @@ 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));
+	dso_name = dso__symsrc_filename(dso) ?: dso_name;
+	a2l = dso__a2l(dso);
 
 	if (!a2l) {
 		a2l = addr2line_init(dso_name);
@@ -265,7 +269,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;
@@ -273,14 +278,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) &&
@@ -290,8 +299,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;
 			}
@@ -306,6 +317,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..5ed89f97ff11 100644
--- a/tools/perf/util/srcline.c
+++ b/tools/perf/util/srcline.c
@@ -1,40 +1,49 @@
 // SPDX-License-Identifier: GPL-2.0
 #include "srcline.h"
+
+#include <inttypes.h>
+#include <stdio.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 +267,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 +277,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 +289,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 +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 *file = NULL;
-	const char *dso_name;
+	char *dso_name;
 
 	if (!dso__has_srcline(dso))
 		return NULL;
@@ -315,18 +331,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 +443,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 ebea101e1001..4ccfcc7c2dfc 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..6052383e862b 100644
--- a/tools/perf/util/unwind-libunwind.c
+++ b/tools/perf/util/unwind-libunwind.c
@@ -1,20 +1,28 @@
 // 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 "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 +301,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 +338,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 +348,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 +448,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.1082.g2b9226bbc0-goog
[PATCH v4 2/3] perf dso: Separate libbfd and cmd addr2line caches to fix confusion
Posted by Ian Rogers 1 week, 1 day ago
When a dso executes addr2line via libbfd it instantiates an a2l_data
pointer. If the DSO later executes via the command-line fallback due
to an inlined bug, the a2l pointer is unconditionally populated with a
child_process process wrapper. If libbfd is queried again, the offline
reader attempts to cast struct a2l_data into child_process, coercing
random arbitrary instruction addresses and causing silent aborts. This
cleanly splits the caches.

Fixes: 257046a36750 ("perf srcline: Fallback between addr2line implementations")
Signed-off-by: Ian Rogers <irogers@google.com>
Assisted-by: Antigravity:gemini-3.1-pro
---
 tools/perf/util/dso.c     | 48 +++++++++++++++++++++++----------------
 tools/perf/util/dso.h     | 21 +++++++++++++++--
 tools/perf/util/libbfd.c  | 10 ++++----
 tools/perf/util/srcline.c |  2 ++
 4 files changed, 54 insertions(+), 27 deletions(-)

diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c
index df39e6ca88e6..9e90de92fcfa 100644
--- a/tools/perf/util/dso.c
+++ b/tools/perf/util/dso.c
@@ -1,38 +1,44 @@
 // SPDX-License-Identifier: GPL-2.0
+#include "dso.h"
+
+#include <errno.h>
+#include <stdlib.h>
+
 #include <asm/bug.h>
+#include <fcntl.h>
 #include <linux/kernel.h>
 #include <linux/string.h>
 #include <linux/zalloc.h>
-#include <sys/time.h>
 #include <sys/resource.h>
-#include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
 #include <unistd.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#ifdef HAVE_LIBBPF_SUPPORT
-#include <bpf/libbpf.h>
-#include "bpf-event.h"
-#include "bpf-utils.h"
-#endif
+
+#include "annotate-data.h"
+#include "auxtrace.h"
 #include "compress.h"
+#include "debug.h"
+#include "dsos.h"
 #include "env.h"
+#include "libbfd.h"
+#include "libdw.h"
+#include "machine.h"
+#include "map.h"
 #include "namespaces.h"
 #include "path.h"
-#include "map.h"
-#include "symbol.h"
 #include "srcline.h"
-#include "dso.h"
-#include "dsos.h"
-#include "machine.h"
-#include "auxtrace.h"
-#include "util.h" /* O_CLOEXEC for older systems */
-#include "debug.h"
 #include "string2.h"
+#include "symbol.h"
+#include "util.h" /* O_CLOEXEC for older systems */
 #include "vdso.h"
-#include "annotate-data.h"
-#include "libdw.h"
+
+#ifdef HAVE_LIBBPF_SUPPORT
+#include <bpf/libbpf.h>
+
+#include "bpf-event.h"
+#include "bpf-utils.h"
+#endif
 
 static const char * const debuglink_paths[] = {
 	"%.0s%s",
@@ -1757,6 +1763,7 @@ void dso__delete(struct dso *dso)
 	auxtrace_cache__free(RC_CHK_ACCESS(dso)->auxtrace_cache);
 	dso_cache__free(dso);
 	dso__free_a2l(dso);
+	dso__free_a2l_libbfd(dso);
 	dso__free_libdw(dso);
 	dso__free_symsrc_filename(dso);
 	nsinfo__zput(RC_CHK_ACCESS(dso)->nsinfo);
@@ -2081,6 +2088,7 @@ 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__free_a2l_libbfd(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 7966c7048c85..e7d5f4bbf894 100644
--- a/tools/perf/util/dso.h
+++ b/tools/perf/util/dso.h
@@ -304,7 +304,12 @@ DECLARE_RC_STRUCT(dso) {
 	const char	 *short_name;
 	const char	 *long_name;
 	void		 *a2l;
+#ifdef HAVE_LIBBFD_SUPPORT
+	void		 *a2l_libbfd;
+#endif
+#ifdef HAVE_LIBDW_SUPPORT
 	void		 *libdw;
+#endif
 	char		 *symsrc_filename;
 	struct nsinfo	*nsinfo;
 	struct auxtrace_cache *auxtrace_cache;
@@ -368,6 +373,20 @@ static inline void dso__set_a2l(struct dso *dso, void *val)
 	RC_CHK_ACCESS(dso)->a2l = val;
 }
 
+#ifdef HAVE_LIBBFD_SUPPORT
+static inline void *dso__a2l_libbfd(const struct dso *dso)
+{
+	return RC_CHK_ACCESS(dso)->a2l_libbfd;
+}
+
+static inline void dso__set_a2l_libbfd(struct dso *dso, void *val)
+{
+	RC_CHK_ACCESS(dso)->a2l_libbfd = val;
+}
+#endif
+
+struct Dwfl;
+#ifdef HAVE_LIBDW_SUPPORT
 static inline void *dso__libdw(const struct dso *dso)
 {
 	return RC_CHK_ACCESS(dso)->libdw;
@@ -378,8 +397,6 @@ static inline void dso__set_libdw(struct dso *dso, void *val)
 	RC_CHK_ACCESS(dso)->libdw = val;
 }
 
-struct Dwfl;
-#ifdef HAVE_LIBDW_SUPPORT
 struct Dwfl *dso__libdw_dwfl(struct dso *dso);
 #else
 static inline struct Dwfl *dso__libdw_dwfl(struct dso *dso __maybe_unused)
diff --git a/tools/perf/util/libbfd.c b/tools/perf/util/libbfd.c
index 807944cd978c..d87242d88525 100644
--- a/tools/perf/util/libbfd.c
+++ b/tools/perf/util/libbfd.c
@@ -239,7 +239,7 @@ static int inline_list__append_dso_a2l(struct dso *dso,
 				       struct inline_node *node,
 				       struct symbol *sym)
 {
-	struct a2l_data *a2l = dso__a2l(dso);
+	struct a2l_data *a2l = dso__a2l_libbfd(dso);
 	struct symbol *inline_sym = new_inline_sym(dso, sym, a2l->funcname);
 	char *srcline = NULL;
 
@@ -259,11 +259,11 @@ int libbfd__addr2line(const char *dso_name, u64 addr,
 
 	mutex_lock(dso__lock(dso));
 	dso_name = dso__symsrc_filename(dso) ?: dso_name;
-	a2l = dso__a2l(dso);
+	a2l = dso__a2l_libbfd(dso);
 
 	if (!a2l) {
 		a2l = addr2line_init(dso_name);
-		dso__set_a2l(dso, a2l);
+		dso__set_a2l_libbfd(dso, a2l);
 	}
 
 	if (a2l == NULL) {
@@ -324,14 +324,14 @@ int libbfd__addr2line(const char *dso_name, u64 addr,
 
 void dso__free_a2l_libbfd(struct dso *dso)
 {
-	struct a2l_data *a2l = dso__a2l(dso);
+	struct a2l_data *a2l = dso__a2l_libbfd(dso);
 
 	if (!a2l)
 		return;
 
 	addr2line_cleanup(a2l);
 
-	dso__set_a2l(dso, NULL);
+	dso__set_a2l_libbfd(dso, NULL);
 }
 
 static int bfd_symbols__cmpvalue(const void *a, const void *b)
diff --git a/tools/perf/util/srcline.c b/tools/perf/util/srcline.c
index 5ed89f97ff11..a8d16106f619 100644
--- a/tools/perf/util/srcline.c
+++ b/tools/perf/util/srcline.c
@@ -301,6 +301,7 @@ char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym,
 	if (dso__a2l_fails(dso) > A2L_FAIL_LIMIT) {
 		dso__set_has_srcline(dso, false);
 		dso__free_a2l(dso);
+		dso__free_a2l_libbfd(dso);
 	}
 	mutex_unlock(dso__lock(dso));
 out:
@@ -348,6 +349,7 @@ char *get_srcline_split(struct dso *dso, u64 addr, unsigned *line)
 	if (dso__a2l_fails(dso) > A2L_FAIL_LIMIT) {
 		dso__set_has_srcline(dso, false);
 		dso__free_a2l(dso);
+		dso__free_a2l_libbfd(dso);
 	}
 	mutex_unlock(dso__lock(dso));
 
-- 
2.55.0.1082.g2b9226bbc0-goog
[PATCH v4 3/3] perf libbfd: Report success when an address is found
Posted by Ian Rogers 1 week, 1 day ago
libbfd__addr2line() only reports success when the caller asked for a file
name. addr2inlines() passes a NULL file as it just wants the inline_node
populating, so libbfd__addr2line() returns 0 for it unless
bfd_find_inliner_info() happened to find an inline frame. addr2line() in
srcline.c treats 0 as a failure and tries the next addr2line
implementation, which appends its own frames to the inline_node libbfd
already appended to. Every frame that isn't inlined is then reported
twice, which happens by default when perf is built with libbfd but
without libdw as the fallback order is then libbfd followed by the
addr2line command:

  $ perf record --call-graph dwarf -- perf test -w inlineloop 1
  $ perf script --fields +srcline
  ...
	    56051a994f8e parent+0x2e (perf)
    inlineloop.c:32
	    56051a99503a inlineloop+0x8a (perf)
    inlineloop.c:47
	    56051a99503a inlineloop+0x8a (perf)
    inlineloop.c:47
	    56051a95841a cmd_test+0xb7a (perf)
    ??:0
	    56051a95841a cmd_test+0xb7a (perf)
    ??:0
  ...

Report success whenever the address is found, like libdw__addr2line()
does, and clear the frames appended so far when appending fails so that a
following implementation starts from an empty node.

Fixes: 257046a36750 ("perf srcline: Fallback between addr2line implementations")
Signed-off-by: Ian Rogers <irogers@google.com>
Assisted-by: Antigravity:gemini-3.1-pro
---
 tools/perf/util/libbfd.c | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/tools/perf/util/libbfd.c b/tools/perf/util/libbfd.c
index d87242d88525..a907d7c1ac94 100644
--- a/tools/perf/util/libbfd.c
+++ b/tools/perf/util/libbfd.c
@@ -287,6 +287,7 @@ int libbfd__addr2line(const char *dso_name, u64 addr,
 		int cnt = 0;
 
 		if (node && inline_list__append_dso_a2l(dso, node, sym)) {
+			inline_node__clear_frames(node);
 			ret = 0;
 			goto out;
 		}
@@ -300,23 +301,32 @@ 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;
 					goto out;
 				}
-				// found at least one inline frame
-				ret = 1;
 			}
 		}
 	}
 
 	if (file) {
 		*file = a2l->filename ? strdup(a2l->filename) : NULL;
-		ret = *file ? 1 : 0;
+		if (!*file) {
+			/* Leave ret as 0 so that another addr2line is tried. */
+			goto out;
+		}
 	}
 
 	if (line)
 		*line = a2l->line;
 
+	/*
+	 * The address was found, report success so that the caller doesn't try
+	 * another addr2line implementation that would append the inline frames
+	 * above a second time.
+	 */
+	ret = 1;
+
 out:
 	mutex_unlock(dso__lock(dso));
 	return ret;
-- 
2.55.0.1082.g2b9226bbc0-goog
[PATCH v3 1/3] perf libdw: Fix Dwfl discovery with split files
Posted by Ian Rogers 1 week, 1 day ago
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: b7a2b011e962 ("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
---
 .../arch/powerpc/util/skip-callchain-idx.c    |  4 +-
 tools/perf/util/addr2line.c                   | 41 +++++++++---
 tools/perf/util/dso.c                         |  9 +++
 tools/perf/util/dso.h                         |  5 +-
 tools/perf/util/libbfd.c                      | 29 ++++++---
 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, 183 insertions(+), 69 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..0abe8dd07670 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,24 +296,38 @@ 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));
+	current_dso_name = dso__symsrc_filename(dso) ?: dso_name;
+	a2l = dso__a2l(dso);
 
 	if (!a2l) {
-		if (!filename__has_section(dso_name, ".debug_line"))
-			goto out;
+		if (!filename__has_section(current_dso_name, ".debug_line"))
+			goto out_unlock;
 
 		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);
 	}
 
 	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 +428,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 1b23a261a9ce..807944cd978c 100644
--- a/tools/perf/util/libbfd.c
+++ b/tools/perf/util/libbfd.c
@@ -255,7 +255,11 @@ 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));
+	dso_name = dso__symsrc_filename(dso) ?: dso_name;
+	a2l = dso__a2l(dso);
 
 	if (!a2l) {
 		a2l = addr2line_init(dso_name);
@@ -265,7 +269,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;
@@ -273,14 +278,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) &&
@@ -290,8 +299,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;
 			}
@@ -306,6 +317,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 ebea101e1001..4ccfcc7c2dfc 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.1082.g2b9226bbc0-goog
[PATCH v3 2/3] perf dso: Separate libbfd and cmd addr2line caches to fix confusion
Posted by Ian Rogers 1 week, 1 day ago
When a dso executes addr2line via libbfd it instantiates an a2l_data
pointer. If the DSO later executes via the command-line fallback due
to an inlined bug, the a2l pointer is unconditionally populated with a
child_process process wrapper. If libbfd is queried again, the offline
reader attempts to cast struct a2l_data into child_process, coercing
random arbitrary instruction addresses and causing silent aborts. This
cleanly splits the caches.

Fixes: 257046a36750 ("perf srcline: Fallback between addr2line implementations")
Signed-off-by: Ian Rogers <irogers@google.com>
Assisted-by: Antigravity:gemini-3.1-pro
---
 tools/perf/util/dso.c     | 49 +++++++++++++++++++++++----------------
 tools/perf/util/dso.h     | 21 +++++++++++++++--
 tools/perf/util/libbfd.c  | 10 ++++----
 tools/perf/util/srcline.c |  2 ++
 4 files changed, 55 insertions(+), 27 deletions(-)

diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c
index df39e6ca88e6..6510767177be 100644
--- a/tools/perf/util/dso.c
+++ b/tools/perf/util/dso.c
@@ -1,38 +1,45 @@
 // SPDX-License-Identifier: GPL-2.0
+#include "dso.h"
+
+#include <errno.h>
+#include <stdlib.h>
+
 #include <asm/bug.h>
+#include <fcntl.h>
 #include <linux/kernel.h>
 #include <linux/string.h>
 #include <linux/zalloc.h>
-#include <sys/time.h>
 #include <sys/resource.h>
-#include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
 #include <unistd.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#ifdef HAVE_LIBBPF_SUPPORT
-#include <bpf/libbpf.h>
-#include "bpf-event.h"
-#include "bpf-utils.h"
-#endif
+
+#include "annotate-data.h"
+#include "auxtrace.h"
 #include "compress.h"
+#include "debug.h"
+#include "dso.h"
+#include "dsos.h"
 #include "env.h"
+#include "libbfd.h"
+#include "libdw.h"
+#include "machine.h"
+#include "map.h"
 #include "namespaces.h"
 #include "path.h"
-#include "map.h"
-#include "symbol.h"
 #include "srcline.h"
-#include "dso.h"
-#include "dsos.h"
-#include "machine.h"
-#include "auxtrace.h"
-#include "util.h" /* O_CLOEXEC for older systems */
-#include "debug.h"
 #include "string2.h"
+#include "symbol.h"
+#include "util.h" /* O_CLOEXEC for older systems */
 #include "vdso.h"
-#include "annotate-data.h"
-#include "libdw.h"
+
+#ifdef HAVE_LIBBPF_SUPPORT
+#include <bpf/libbpf.h>
+
+#include "bpf-event.h"
+#include "bpf-utils.h"
+#endif
 
 static const char * const debuglink_paths[] = {
 	"%.0s%s",
@@ -1757,6 +1764,7 @@ void dso__delete(struct dso *dso)
 	auxtrace_cache__free(RC_CHK_ACCESS(dso)->auxtrace_cache);
 	dso_cache__free(dso);
 	dso__free_a2l(dso);
+	dso__free_a2l_libbfd(dso);
 	dso__free_libdw(dso);
 	dso__free_symsrc_filename(dso);
 	nsinfo__zput(RC_CHK_ACCESS(dso)->nsinfo);
@@ -2081,6 +2089,7 @@ 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__free_a2l_libbfd(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 7966c7048c85..e7d5f4bbf894 100644
--- a/tools/perf/util/dso.h
+++ b/tools/perf/util/dso.h
@@ -304,7 +304,12 @@ DECLARE_RC_STRUCT(dso) {
 	const char	 *short_name;
 	const char	 *long_name;
 	void		 *a2l;
+#ifdef HAVE_LIBBFD_SUPPORT
+	void		 *a2l_libbfd;
+#endif
+#ifdef HAVE_LIBDW_SUPPORT
 	void		 *libdw;
+#endif
 	char		 *symsrc_filename;
 	struct nsinfo	*nsinfo;
 	struct auxtrace_cache *auxtrace_cache;
@@ -368,6 +373,20 @@ static inline void dso__set_a2l(struct dso *dso, void *val)
 	RC_CHK_ACCESS(dso)->a2l = val;
 }
 
+#ifdef HAVE_LIBBFD_SUPPORT
+static inline void *dso__a2l_libbfd(const struct dso *dso)
+{
+	return RC_CHK_ACCESS(dso)->a2l_libbfd;
+}
+
+static inline void dso__set_a2l_libbfd(struct dso *dso, void *val)
+{
+	RC_CHK_ACCESS(dso)->a2l_libbfd = val;
+}
+#endif
+
+struct Dwfl;
+#ifdef HAVE_LIBDW_SUPPORT
 static inline void *dso__libdw(const struct dso *dso)
 {
 	return RC_CHK_ACCESS(dso)->libdw;
@@ -378,8 +397,6 @@ static inline void dso__set_libdw(struct dso *dso, void *val)
 	RC_CHK_ACCESS(dso)->libdw = val;
 }
 
-struct Dwfl;
-#ifdef HAVE_LIBDW_SUPPORT
 struct Dwfl *dso__libdw_dwfl(struct dso *dso);
 #else
 static inline struct Dwfl *dso__libdw_dwfl(struct dso *dso __maybe_unused)
diff --git a/tools/perf/util/libbfd.c b/tools/perf/util/libbfd.c
index 807944cd978c..d87242d88525 100644
--- a/tools/perf/util/libbfd.c
+++ b/tools/perf/util/libbfd.c
@@ -239,7 +239,7 @@ static int inline_list__append_dso_a2l(struct dso *dso,
 				       struct inline_node *node,
 				       struct symbol *sym)
 {
-	struct a2l_data *a2l = dso__a2l(dso);
+	struct a2l_data *a2l = dso__a2l_libbfd(dso);
 	struct symbol *inline_sym = new_inline_sym(dso, sym, a2l->funcname);
 	char *srcline = NULL;
 
@@ -259,11 +259,11 @@ int libbfd__addr2line(const char *dso_name, u64 addr,
 
 	mutex_lock(dso__lock(dso));
 	dso_name = dso__symsrc_filename(dso) ?: dso_name;
-	a2l = dso__a2l(dso);
+	a2l = dso__a2l_libbfd(dso);
 
 	if (!a2l) {
 		a2l = addr2line_init(dso_name);
-		dso__set_a2l(dso, a2l);
+		dso__set_a2l_libbfd(dso, a2l);
 	}
 
 	if (a2l == NULL) {
@@ -324,14 +324,14 @@ int libbfd__addr2line(const char *dso_name, u64 addr,
 
 void dso__free_a2l_libbfd(struct dso *dso)
 {
-	struct a2l_data *a2l = dso__a2l(dso);
+	struct a2l_data *a2l = dso__a2l_libbfd(dso);
 
 	if (!a2l)
 		return;
 
 	addr2line_cleanup(a2l);
 
-	dso__set_a2l(dso, NULL);
+	dso__set_a2l_libbfd(dso, NULL);
 }
 
 static int bfd_symbols__cmpvalue(const void *a, const void *b)
diff --git a/tools/perf/util/srcline.c b/tools/perf/util/srcline.c
index e60dea472507..68798a4de887 100644
--- a/tools/perf/util/srcline.c
+++ b/tools/perf/util/srcline.c
@@ -300,6 +300,7 @@ char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym,
 	if (dso__a2l_fails(dso) > A2L_FAIL_LIMIT) {
 		dso__set_has_srcline(dso, false);
 		dso__free_a2l(dso);
+		dso__free_a2l_libbfd(dso);
 	}
 	mutex_unlock(dso__lock(dso));
 out:
@@ -347,6 +348,7 @@ char *get_srcline_split(struct dso *dso, u64 addr, unsigned *line)
 	if (dso__a2l_fails(dso) > A2L_FAIL_LIMIT) {
 		dso__set_has_srcline(dso, false);
 		dso__free_a2l(dso);
+		dso__free_a2l_libbfd(dso);
 	}
 	mutex_unlock(dso__lock(dso));
 
-- 
2.55.0.1082.g2b9226bbc0-goog
[PATCH v3 3/3] perf libbfd: Report success when an address is found
Posted by Ian Rogers 1 week, 1 day ago
libbfd__addr2line() only reports success when the caller asked for a file
name. addr2inlines() passes a NULL file as it just wants the inline_node
populating, so libbfd__addr2line() returns 0 for it unless
bfd_find_inliner_info() happened to find an inline frame. addr2line() in
srcline.c treats 0 as a failure and tries the next addr2line
implementation, which appends its own frames to the inline_node libbfd
already appended to. Every frame that isn't inlined is then reported
twice, which happens by default when perf is built with libbfd but
without libdw as the fallback order is then libbfd followed by the
addr2line command:

  $ perf record --call-graph dwarf -- perf test -w inlineloop 1
  $ perf script --fields +srcline
  ...
	    56051a994f8e parent+0x2e (perf)
    inlineloop.c:32
	    56051a99503a inlineloop+0x8a (perf)
    inlineloop.c:47
	    56051a99503a inlineloop+0x8a (perf)
    inlineloop.c:47
	    56051a95841a cmd_test+0xb7a (perf)
    ??:0
	    56051a95841a cmd_test+0xb7a (perf)
    ??:0
  ...

Report success whenever the address is found, like libdw__addr2line()
does, and clear the frames appended so far when appending fails so that a
following implementation starts from an empty node.

Fixes: 257046a36750 ("perf srcline: Fallback between addr2line implementations")
Signed-off-by: Ian Rogers <irogers@google.com>
Assisted-by: Antigravity:gemini-3.1-pro
---
 tools/perf/util/libbfd.c | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/tools/perf/util/libbfd.c b/tools/perf/util/libbfd.c
index d87242d88525..a907d7c1ac94 100644
--- a/tools/perf/util/libbfd.c
+++ b/tools/perf/util/libbfd.c
@@ -287,6 +287,7 @@ int libbfd__addr2line(const char *dso_name, u64 addr,
 		int cnt = 0;
 
 		if (node && inline_list__append_dso_a2l(dso, node, sym)) {
+			inline_node__clear_frames(node);
 			ret = 0;
 			goto out;
 		}
@@ -300,23 +301,32 @@ 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;
 					goto out;
 				}
-				// found at least one inline frame
-				ret = 1;
 			}
 		}
 	}
 
 	if (file) {
 		*file = a2l->filename ? strdup(a2l->filename) : NULL;
-		ret = *file ? 1 : 0;
+		if (!*file) {
+			/* Leave ret as 0 so that another addr2line is tried. */
+			goto out;
+		}
 	}
 
 	if (line)
 		*line = a2l->line;
 
+	/*
+	 * The address was found, report success so that the caller doesn't try
+	 * another addr2line implementation that would append the inline frames
+	 * above a second time.
+	 */
+	ret = 1;
+
 out:
 	mutex_unlock(dso__lock(dso));
 	return ret;
-- 
2.55.0.1082.g2b9226bbc0-goog