* [PATCH v2 0/6] Add a libdw addr2line implementation
@ 2026-01-10 8:26 Ian Rogers
2026-01-10 8:26 ` [PATCH v2 1/6] perf addr2line: Add a libdw implementation Ian Rogers
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: Ian Rogers @ 2026-01-10 8:26 UTC (permalink / raw)
To: Tony Jones, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Alexander Shishkin,
Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark, Howard Chu,
Stephen Brennan, linux-kernel, linux-perf-users
addr2line is a performance bottleneck in perf, add a libdw based
implementation that avoids forking addr2line and caches the decoded
debug information.
Allow the addr2line implementation to be picked via the configuration
file or --addr2line-style with `perf report`.
Test/fix that inline callchains are properly displayed by perf script.
v2: Fix bias issue with libdwfl functions. Use cu_walk_functions_at
from perf's dwarf-aux to fully walk inline functions. Add testing
that inlined functions are shown in the perf script srcline
callchain information. Add configurability as to which addr2line
style to use.
v1: https://lore.kernel.org/lkml/20251122093934.94971-1-irogers@google.com/
Ian Rogers (6):
perf addr2line: Add a libdw implementation
perf addr2line.c: Rename a2l_style to cmd_a2l_style
perf srcline: Add configuration support for the addr2line style
perf callchain: Fix srcline printing with inlines
perf test workload: Add inlineloop test workload
perf test: Test addr2line unwinding works with inline functions
tools/perf/builtin-report.c | 10 ++
tools/perf/tests/builtin-test.c | 1 +
tools/perf/tests/shell/addr2line_inlines.sh | 47 +++++++
tools/perf/tests/tests.h | 1 +
tools/perf/tests/workloads/Build | 2 +
tools/perf/tests/workloads/inlineloop.c | 52 +++++++
tools/perf/util/Build | 1 +
tools/perf/util/addr2line.c | 20 +--
tools/perf/util/config.c | 4 +
tools/perf/util/dso.c | 2 +
tools/perf/util/dso.h | 11 ++
tools/perf/util/evsel_fprintf.c | 8 +-
tools/perf/util/libdw.c | 148 ++++++++++++++++++++
tools/perf/util/libdw.h | 60 ++++++++
tools/perf/util/srcline.c | 116 +++++++++++++--
tools/perf/util/srcline.h | 3 +
tools/perf/util/symbol_conf.h | 10 ++
17 files changed, 476 insertions(+), 20 deletions(-)
create mode 100755 tools/perf/tests/shell/addr2line_inlines.sh
create mode 100644 tools/perf/tests/workloads/inlineloop.c
create mode 100644 tools/perf/util/libdw.c
create mode 100644 tools/perf/util/libdw.h
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2 1/6] perf addr2line: Add a libdw implementation
2026-01-10 8:26 [PATCH v2 0/6] Add a libdw addr2line implementation Ian Rogers
@ 2026-01-10 8:26 ` Ian Rogers
2026-01-10 8:26 ` [PATCH v2 2/6] perf addr2line.c: Rename a2l_style to cmd_a2l_style Ian Rogers
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Ian Rogers @ 2026-01-10 8:26 UTC (permalink / raw)
To: Tony Jones, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Alexander Shishkin,
Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark, Howard Chu,
Stephen Brennan, linux-kernel, linux-perf-users
Add an implementation of addr2line that uses libdw. Other addr2line
implementations are slow, particularly in the case of forking
addr2line. Add an implementation that caches the libdw information in
the dso and uses it to find the file and line number
information. Inline information is supported but because
cu_walk_functions_at visits the leaf function last add a
inline_list__append_tail to reverse the lists order.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/Build | 1 +
tools/perf/util/dso.c | 2 +
tools/perf/util/dso.h | 11 +++
tools/perf/util/libdw.c | 148 ++++++++++++++++++++++++++++++++++++++
tools/perf/util/libdw.h | 60 ++++++++++++++++
tools/perf/util/srcline.c | 24 +++++++
tools/perf/util/srcline.h | 1 +
7 files changed, 247 insertions(+)
create mode 100644 tools/perf/util/libdw.c
create mode 100644 tools/perf/util/libdw.h
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index 1c2a43e1dc68..2bed6274e248 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -224,6 +224,7 @@ perf-util-$(CONFIG_LIBDW) += dwarf-regs-powerpc.o
perf-util-$(CONFIG_LIBDW) += dwarf-regs-x86.o
perf-util-$(CONFIG_LIBDW) += debuginfo.o
perf-util-$(CONFIG_LIBDW) += annotate-data.o
+perf-util-$(CONFIG_LIBDW) += libdw.o
perf-util-$(CONFIG_LIBDW_DWARF_UNWIND) += unwind-libdw.o
perf-util-$(CONFIG_LOCAL_LIBUNWIND) += unwind-libunwind-local.o
diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c
index 344e689567ee..06980844c014 100644
--- a/tools/perf/util/dso.c
+++ b/tools/perf/util/dso.c
@@ -32,6 +32,7 @@
#include "string2.h"
#include "vdso.h"
#include "annotate-data.h"
+#include "libdw.h"
static const char * const debuglink_paths[] = {
"%.0s%s",
@@ -1605,6 +1606,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_libdw(dso);
dso__free_symsrc_filename(dso);
nsinfo__zput(RC_CHK_ACCESS(dso)->nsinfo);
mutex_destroy(dso__lock(dso));
diff --git a/tools/perf/util/dso.h b/tools/perf/util/dso.h
index f8ccb9816b89..4aee23775054 100644
--- a/tools/perf/util/dso.h
+++ b/tools/perf/util/dso.h
@@ -268,6 +268,7 @@ DECLARE_RC_STRUCT(dso) {
const char *short_name;
const char *long_name;
void *a2l;
+ void *a2l_libdw;
char *symsrc_filename;
#if defined(__powerpc__)
void *dwfl; /* DWARF debug info */
@@ -334,6 +335,16 @@ static inline void dso__set_a2l(struct dso *dso, void *val)
RC_CHK_ACCESS(dso)->a2l = val;
}
+static inline void *dso__a2l_libdw(const struct dso *dso)
+{
+ return RC_CHK_ACCESS(dso)->a2l_libdw;
+}
+
+static inline void dso__set_a2l_libdw(struct dso *dso, void *val)
+{
+ RC_CHK_ACCESS(dso)->a2l_libdw = val;
+}
+
static inline unsigned int dso__a2l_fails(const struct dso *dso)
{
return RC_CHK_ACCESS(dso)->a2l_fails;
diff --git a/tools/perf/util/libdw.c b/tools/perf/util/libdw.c
new file mode 100644
index 000000000000..91e8c161a437
--- /dev/null
+++ b/tools/perf/util/libdw.c
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "dso.h"
+#include "libdw.h"
+#include "srcline.h"
+#include "symbol.h"
+#include "dwarf-aux.h"
+#include <fcntl.h>
+#include <unistd.h>
+#include <elfutils/libdwfl.h>
+
+void dso__free_a2l_libdw(struct dso *dso)
+{
+ Dwfl *dwfl = dso__a2l_libdw(dso);
+
+ if (dwfl) {
+ dwfl_end(dwfl);
+ dso__set_a2l_libdw(dso, NULL);
+ }
+}
+
+struct libdw_a2l_cb_args {
+ struct dso *dso;
+ struct symbol *sym;
+ struct inline_node *node;
+};
+
+static int libdw_a2l_cb(Dwarf_Die *die, void *_args)
+{
+ struct libdw_a2l_cb_args *args = _args;
+ struct symbol *inline_sym = new_inline_sym(args->dso, args->sym, dwarf_diename(die));
+ char *srcline = NULL;
+ const char *fname = die_get_decl_file(die);
+
+ if (fname) {
+ int lineno;
+
+ dwarf_decl_line(die, &lineno);
+ srcline = srcline_from_fileline(fname, lineno);
+ }
+ inline_list__append_tail(inline_sym, srcline, args->node);
+ return 0;
+}
+
+int libdw__addr2line(const char *dso_name, u64 addr,
+ char **file, unsigned int *line_nr,
+ struct dso *dso, bool unwind_inlines,
+ struct inline_node *node, struct symbol *sym)
+{
+ static const Dwfl_Callbacks offline_callbacks = {
+ .find_debuginfo = dwfl_standard_find_debuginfo,
+ .section_address = dwfl_offline_section_address,
+ .find_elf = dwfl_build_id_find_elf,
+ };
+ Dwfl *dwfl = dso__a2l_libdw(dso);
+ Dwfl_Module *mod;
+ Dwfl_Line *dwline;
+ Dwarf_Addr bias;
+ const char *src;
+ int lineno;
+
+ if (!dwfl) {
+ /*
+ * Initialize Dwfl session.
+ * We need to open the DSO file to report it to libdw.
+ */
+ int fd;
+
+ fd = open(dso_name, O_RDONLY);
+ if (fd < 0)
+ return 0;
+
+ dwfl = dwfl_begin(&offline_callbacks);
+ if (!dwfl) {
+ close(fd);
+ return 0;
+ }
+
+ /*
+ * If the report is successful, the file descriptor fd is consumed
+ * and closed by the Dwfl. If not, it is not closed.
+ */
+ mod = dwfl_report_offline(dwfl, dso_name, dso_name, fd);
+ if (!mod) {
+ dwfl_end(dwfl);
+ close(fd);
+ return 0;
+ }
+
+ dwfl_report_end(dwfl, /*removed=*/NULL, /*arg=*/NULL);
+ dso__set_a2l_libdw(dso, dwfl);
+ } else {
+ /* Dwfl session already initialized, get module for address. */
+ mod = dwfl_addrmodule(dwfl, addr);
+ }
+
+ if (!mod)
+ return 0;
+
+ /*
+ * 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;
+
+ /* Find source line information for the address. */
+ dwline = dwfl_module_getsrc(mod, addr + bias);
+ if (!dwline)
+ return 0;
+
+ /* Get line information. */
+ src = dwfl_lineinfo(dwline, /*addr=*/NULL, &lineno, /*col=*/NULL, /*mtime=*/NULL,
+ /*length=*/NULL);
+
+ if (file)
+ *file = src ? strdup(src) : NULL;
+ if (line_nr)
+ *line_nr = lineno;
+
+ /* Optionally unwind inline function call chain. */
+ if (unwind_inlines && node) {
+ Dwarf_Addr unused_bias;
+ Dwarf_Die *cudie = dwfl_module_addrdie(mod, addr + bias, &unused_bias);
+ struct libdw_a2l_cb_args args = {
+ .dso = dso,
+ .sym = sym,
+ .node = node,
+ };
+
+ /* Walk from the parent down to the leaf. */
+ cu_walk_functions_at(cudie, addr, libdw_a2l_cb, &args);
+ /*
+ * The srcline information is for die, fix up the leaf one to be
+ * that of the particular addr.
+ */
+ if (src) {
+ struct inline_list *ilist;
+
+ list_for_each_entry(ilist, &node->val, list) {
+ free(ilist->srcline);
+ ilist->srcline = srcline_from_fileline(src, lineno);
+ break;
+ }
+ }
+ }
+ return 1;
+}
diff --git a/tools/perf/util/libdw.h b/tools/perf/util/libdw.h
new file mode 100644
index 000000000000..0f8d7b4a11a5
--- /dev/null
+++ b/tools/perf/util/libdw.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef PERF_LIBDW_H
+#define PERF_LIBDW_H
+
+#include <linux/types.h>
+
+struct dso;
+struct inline_node;
+struct symbol;
+
+#ifdef HAVE_LIBDW_SUPPORT
+/*
+ * libdw__addr2line - Convert address to source location using libdw
+ * @dso_name: Name of the DSO
+ * @addr: Address to resolve
+ * @file: Pointer to return filename (caller must free)
+ * @line_nr: Pointer to return line number
+ * @dso: The dso struct
+ * @unwind_inlines: Whether to unwind inline function calls
+ * @node: Inline node list to append to
+ * @sym: The symbol associated with the address
+ *
+ * This function initializes a Dwfl context for the DSO if not already present,
+ * finds the source line information for the given address, and optionally
+ * resolves inline function call chains.
+ *
+ * Returns 1 on success (found), 0 on failure (not found).
+ */
+int libdw__addr2line(const char *dso_name, u64 addr, char **file,
+ unsigned int *line_nr, struct dso *dso,
+ bool unwind_inlines, struct inline_node *node,
+ struct symbol *sym);
+
+/*
+ * dso__free_a2l_libdw - Free libdw resources associated with the DSO
+ * @dso: The dso to free resources for
+ *
+ * This function cleans up the Dwfl context used for addr2line lookups.
+ */
+void dso__free_a2l_libdw(struct dso *dso);
+
+#else /* HAVE_LIBDW_SUPPORT */
+
+static inline int libdw__addr2line(const char *dso_name __maybe_unused,
+ u64 addr __maybe_unused, char **file __maybe_unused,
+ unsigned int *line_nr __maybe_unused,
+ struct dso *dso __maybe_unused,
+ bool unwind_inlines __maybe_unused,
+ struct inline_node *node __maybe_unused,
+ struct symbol *sym __maybe_unused)
+{
+ return 0;
+}
+
+static inline void dso__free_a2l_libdw(struct dso *dso __maybe_unused)
+{
+}
+#endif /* HAVE_LIBDW_SUPPORT */
+
+#endif /* PERF_LIBDW_H */
diff --git a/tools/perf/util/srcline.c b/tools/perf/util/srcline.c
index 27c0966611ab..e2d280678b02 100644
--- a/tools/perf/util/srcline.c
+++ b/tools/perf/util/srcline.c
@@ -6,6 +6,7 @@
#include "libbfd.h"
#include "llvm.h"
#include "symbol.h"
+#include "libdw.h"
#include <inttypes.h>
#include <string.h>
@@ -51,6 +52,25 @@ int inline_list__append(struct symbol *symbol, char *srcline, struct inline_node
return 0;
}
+int inline_list__append_tail(struct symbol *symbol, char *srcline, struct inline_node *node)
+{
+ struct inline_list *ilist;
+
+ ilist = zalloc(sizeof(*ilist));
+ if (ilist == NULL)
+ return -1;
+
+ ilist->symbol = symbol;
+ ilist->srcline = srcline;
+
+ if (callchain_param.order == ORDER_CALLEE)
+ list_add(&ilist->list, &node->val);
+ else
+ list_add_tail(&ilist->list, &node->val);
+
+ return 0;
+}
+
/* basename version that takes a const input string */
static const char *gnu_basename(const char *path)
{
@@ -120,6 +140,10 @@ static int addr2line(const char *dso_name, u64 addr, char **file, unsigned int *
{
int ret;
+ ret = libdw__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines, node, sym);
+ if (ret > 0)
+ return ret;
+
ret = llvm__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines, node, sym);
if (ret > 0)
return ret;
diff --git a/tools/perf/util/srcline.h b/tools/perf/util/srcline.h
index c36f573cd339..be9f002bf234 100644
--- a/tools/perf/util/srcline.h
+++ b/tools/perf/util/srcline.h
@@ -57,6 +57,7 @@ struct inline_node *inlines__tree_find(struct rb_root_cached *tree, u64 addr);
void inlines__tree_delete(struct rb_root_cached *tree);
int inline_list__append(struct symbol *symbol, char *srcline, struct inline_node *node);
+int inline_list__append_tail(struct symbol *symbol, char *srcline, struct inline_node *node);
char *srcline_from_fileline(const char *file, unsigned int line);
struct symbol *new_inline_sym(struct dso *dso,
struct symbol *base_sym,
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2 2/6] perf addr2line.c: Rename a2l_style to cmd_a2l_style
2026-01-10 8:26 [PATCH v2 0/6] Add a libdw addr2line implementation Ian Rogers
2026-01-10 8:26 ` [PATCH v2 1/6] perf addr2line: Add a libdw implementation Ian Rogers
@ 2026-01-10 8:26 ` Ian Rogers
2026-01-10 8:26 ` [PATCH v2 3/6] perf srcline: Add configuration support for the addr2line style Ian Rogers
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Ian Rogers @ 2026-01-10 8:26 UTC (permalink / raw)
To: Tony Jones, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Alexander Shishkin,
Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark, Howard Chu,
Stephen Brennan, linux-kernel, linux-perf-users
The a2l_style is only relevant to the command line version, so rename
to make this clearer.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/addr2line.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/tools/perf/util/addr2line.c b/tools/perf/util/addr2line.c
index f2d94a3272d7..0f1499350d47 100644
--- a/tools/perf/util/addr2line.c
+++ b/tools/perf/util/addr2line.c
@@ -90,16 +90,16 @@ static struct child_process *addr2line_subprocess_init(const char *addr2line_pat
return a2l;
}
-enum a2l_style {
+enum cmd_a2l_style {
BROKEN,
GNU_BINUTILS,
LLVM,
};
-static enum a2l_style addr2line_configure(struct child_process *a2l, const char *dso_name)
+static enum cmd_a2l_style cmd_addr2line_configure(struct child_process *a2l, const char *dso_name)
{
static bool cached;
- static enum a2l_style style;
+ static enum cmd_a2l_style style;
if (!cached) {
char buf[128];
@@ -149,7 +149,7 @@ static enum a2l_style addr2line_configure(struct child_process *a2l, const char
}
static int read_addr2line_record(struct io *io,
- enum a2l_style style,
+ enum cmd_a2l_style style,
const char *dso_name,
u64 addr,
bool first,
@@ -298,7 +298,7 @@ int cmd__addr2line(const char *dso_name, u64 addr,
char buf[128];
ssize_t written;
struct io io = { .eof = false };
- enum a2l_style a2l_style;
+ enum cmd_a2l_style cmd_a2l_style;
if (!a2l) {
if (!filename__has_section(dso_name, ".debug_line"))
@@ -314,8 +314,8 @@ int cmd__addr2line(const char *dso_name, u64 addr,
pr_warning("%s %s: addr2line_subprocess_init failed\n", __func__, dso_name);
goto out;
}
- a2l_style = addr2line_configure(a2l, dso_name);
- if (a2l_style == BROKEN)
+ cmd_a2l_style = cmd_addr2line_configure(a2l, dso_name);
+ if (cmd_a2l_style == BROKEN)
goto out;
/*
@@ -336,7 +336,7 @@ int cmd__addr2line(const char *dso_name, u64 addr,
}
io__init(&io, a2l->out, buf, sizeof(buf));
io.timeout_ms = addr2line_timeout_ms;
- switch (read_addr2line_record(&io, a2l_style, dso_name, addr, /*first=*/true,
+ switch (read_addr2line_record(&io, cmd_a2l_style, dso_name, addr, /*first=*/true,
&record_function, &record_filename, &record_line_nr)) {
case -1:
if (!symbol_conf.disable_add2line_warn)
@@ -351,7 +351,7 @@ int cmd__addr2line(const char *dso_name, u64 addr,
* binutils, also force a non-zero address as we're no longer
* reading that record.
*/
- switch (read_addr2line_record(&io, a2l_style, dso_name,
+ switch (read_addr2line_record(&io, cmd_a2l_style, dso_name,
/*addr=*/1, /*first=*/true,
NULL, NULL, NULL)) {
case -1:
@@ -397,7 +397,7 @@ int cmd__addr2line(const char *dso_name, u64 addr,
* as we're reading records beyond the first.
*/
while ((record_status = read_addr2line_record(&io,
- a2l_style,
+ cmd_a2l_style,
dso_name,
/*addr=*/1,
/*first=*/false,
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2 3/6] perf srcline: Add configuration support for the addr2line style
2026-01-10 8:26 [PATCH v2 0/6] Add a libdw addr2line implementation Ian Rogers
2026-01-10 8:26 ` [PATCH v2 1/6] perf addr2line: Add a libdw implementation Ian Rogers
2026-01-10 8:26 ` [PATCH v2 2/6] perf addr2line.c: Rename a2l_style to cmd_a2l_style Ian Rogers
@ 2026-01-10 8:26 ` Ian Rogers
2026-01-10 8:26 ` [PATCH v2 4/6] perf callchain: Fix srcline printing with inlines Ian Rogers
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Ian Rogers @ 2026-01-10 8:26 UTC (permalink / raw)
To: Tony Jones, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Alexander Shishkin,
Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark, Howard Chu,
Stephen Brennan, linux-kernel, linux-perf-users
Allow the addr2line style to be specified on the `perf report` command
line or in the .perfconfig file.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/builtin-report.c | 10 ++++
tools/perf/util/config.c | 4 ++
tools/perf/util/srcline.c | 98 +++++++++++++++++++++++++++++++----
tools/perf/util/srcline.h | 2 +
tools/perf/util/symbol_conf.h | 10 ++++
5 files changed, 113 insertions(+), 11 deletions(-)
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c
index 6c2b4f93ec78..2e936928e8c0 100644
--- a/tools/perf/builtin-report.c
+++ b/tools/perf/builtin-report.c
@@ -1271,6 +1271,13 @@ parse_percent_limit(const struct option *opt, const char *str,
return 0;
}
+static int
+report_parse_addr2line_config(const struct option *opt __maybe_unused,
+ const char *arg, int unset __maybe_unused)
+{
+ return addr2line_configure("addr2line.style", arg, NULL);
+}
+
static int process_attr(const struct perf_tool *tool __maybe_unused,
union perf_event *event,
struct evlist **pevlist)
@@ -1447,6 +1454,9 @@ int cmd_report(int argc, const char **argv)
"objdump binary to use for disassembly and annotations"),
OPT_STRING(0, "addr2line", &addr2line_path, "path",
"addr2line binary to use for line numbers"),
+ OPT_CALLBACK(0, "addr2line-style", NULL, "addr2line style",
+ "addr2line styles (libdw,llvm,libbfd,addr2line)",
+ report_parse_addr2line_config),
OPT_BOOLEAN(0, "demangle", &symbol_conf.demangle,
"Symbol demangling. Enabled by default, use --no-demangle to disable."),
OPT_BOOLEAN(0, "demangle-kernel", &symbol_conf.demangle_kernel,
diff --git a/tools/perf/util/config.c b/tools/perf/util/config.c
index e0219bc6330a..0452fbc6c085 100644
--- a/tools/perf/util/config.c
+++ b/tools/perf/util/config.c
@@ -20,6 +20,7 @@
#include "util/stat.h" /* perf_stat__set_big_num */
#include "util/evsel.h" /* evsel__hw_names, evsel__use_bpf_counters */
#include "util/addr2line.h" /* addr2line_timeout_ms */
+#include "srcline.h"
#include "build-id.h"
#include "debug.h"
#include "config.h"
@@ -519,6 +520,9 @@ int perf_default_config(const char *var, const char *value,
if (strstarts(var, "stat."))
return perf_stat_config(var, value);
+ if (strstarts(var, "addr2line."))
+ return addr2line_configure(var, value, dummy);
+
/* Add other config variables here. */
return 0;
}
diff --git a/tools/perf/util/srcline.c b/tools/perf/util/srcline.c
index e2d280678b02..28fa1abd1fd3 100644
--- a/tools/perf/util/srcline.c
+++ b/tools/perf/util/srcline.c
@@ -7,9 +7,11 @@
#include "llvm.h"
#include "symbol.h"
#include "libdw.h"
+#include "debug.h"
#include <inttypes.h>
#include <string.h>
+#include <linux/string.h>
bool srcline_full_filename;
@@ -138,21 +140,95 @@ static int addr2line(const char *dso_name, u64 addr, char **file, unsigned int *
struct dso *dso, bool unwind_inlines, struct inline_node *node,
struct symbol *sym)
{
- int ret;
+ int ret = 0;
+
+ if (symbol_conf.addr2line_style[0] == A2L_STYLE_UNKNOWN) {
+ int i = 0;
+
+ /* Default addr2line fallback order. */
+#ifdef HAVE_LIBDW_SUPPORT
+ symbol_conf.addr2line_style[i++] = A2L_STYLE_LIBDW;
+#endif
+#ifdef HAVE_LIBLLVM_SUPPORT
+ symbol_conf.addr2line_style[i++] = A2L_STYLE_LLVM;
+#endif
+#ifdef HAVE_LIBBFD_SUPPORT
+ symbol_conf.addr2line_style[i++] = A2L_STYLE_LIBBFD;
+#endif
+ symbol_conf.addr2line_style[i++] = A2L_STYLE_CMD;
+ }
+
+ for (size_t i = 0; i < ARRAY_SIZE(symbol_conf.addr2line_style); i++) {
+ switch (symbol_conf.addr2line_style[i]) {
+ case A2L_STYLE_LIBDW:
+ ret = libdw__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines,
+ node, sym);
+ break;
+ case A2L_STYLE_LLVM:
+ ret = llvm__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines,
+ node, sym);
+ break;
+ case A2L_STYLE_LIBBFD:
+ ret = libbfd__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines,
+ node, sym);
+ break;
+ case A2L_STYLE_CMD:
+ ret = cmd__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines,
+ node, sym);
+ break;
+ case A2L_STYLE_UNKNOWN:
+ default:
+ break;
+ }
+ if (ret > 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+int addr2line_configure(const char *var, const char *value, void *cb __maybe_unused)
+{
+ static const char * const a2l_style_names[] = {
+ [A2L_STYLE_LIBDW] = "libdw",
+ [A2L_STYLE_LLVM] = "llvm",
+ [A2L_STYLE_LIBBFD] = "libbfd",
+ [A2L_STYLE_CMD] = "addr2line",
+ NULL
+ };
+
+ char *s, *p, *saveptr;
+ size_t i = 0;
- ret = libdw__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines, node, sym);
- if (ret > 0)
- return ret;
+ if (strcmp(var, "addr2line.style"))
+ return 0;
+
+ if (!value)
+ return -1;
- ret = llvm__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines, node, sym);
- if (ret > 0)
- return ret;
+ s = strdup(value);
+ if (!s)
+ return -1;
- ret = libbfd__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines, node, sym);
- if (ret > 0)
- return ret;
+ p = strtok_r(s, ",", &saveptr);
+ while (p && i < ARRAY_SIZE(symbol_conf.addr2line_style)) {
+ bool found = false;
+ char *q = strim(p);
+
+ for (size_t j = A2L_STYLE_LIBDW; j < MAX_A2L_STYLE; j++) {
+ if (!strcasecmp(q, a2l_style_names[j])) {
+ symbol_conf.addr2line_style[i++] = j;
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ pr_warning("Unknown addr2line style: %s\n", q);
+ p = strtok_r(NULL, ",", &saveptr);
+ }
- return cmd__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines, node, sym);
+ free(s);
+ return 0;
}
static struct inline_node *addr2inlines(const char *dso_name, u64 addr,
diff --git a/tools/perf/util/srcline.h b/tools/perf/util/srcline.h
index be9f002bf234..7c37b3bf9ce7 100644
--- a/tools/perf/util/srcline.h
+++ b/tools/perf/util/srcline.h
@@ -63,4 +63,6 @@ struct symbol *new_inline_sym(struct dso *dso,
struct symbol *base_sym,
const char *funcname);
+int addr2line_configure(const char *var, const char *value, void *cb);
+
#endif /* PERF_SRCLINE_H */
diff --git a/tools/perf/util/symbol_conf.h b/tools/perf/util/symbol_conf.h
index 7a80d2c14d9b..71bb17372a6c 100644
--- a/tools/perf/util/symbol_conf.h
+++ b/tools/perf/util/symbol_conf.h
@@ -9,6 +9,15 @@
struct strlist;
struct intlist;
+enum a2l_style {
+ A2L_STYLE_UNKNOWN = 0,
+ A2L_STYLE_LIBDW,
+ A2L_STYLE_LLVM,
+ A2L_STYLE_LIBBFD,
+ A2L_STYLE_CMD,
+};
+#define MAX_A2L_STYLE (A2L_STYLE_CMD + 1)
+
struct symbol_conf {
bool nanosecs;
unsigned short priv_size;
@@ -70,6 +79,7 @@ struct symbol_conf {
*col_width_list_str,
*bt_stop_list_str;
const char *addr2line_path;
+ enum a2l_style addr2line_style[MAX_A2L_STYLE];
unsigned long time_quantum;
struct strlist *dso_list,
*comm_list,
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2 4/6] perf callchain: Fix srcline printing with inlines
2026-01-10 8:26 [PATCH v2 0/6] Add a libdw addr2line implementation Ian Rogers
` (2 preceding siblings ...)
2026-01-10 8:26 ` [PATCH v2 3/6] perf srcline: Add configuration support for the addr2line style Ian Rogers
@ 2026-01-10 8:26 ` Ian Rogers
2026-01-10 8:26 ` [PATCH v2 5/6] perf test workload: Add inlineloop test workload Ian Rogers
2026-01-10 8:26 ` [PATCH v2 6/6] perf test: Test addr2line unwinding works with inline functions Ian Rogers
5 siblings, 0 replies; 7+ messages in thread
From: Ian Rogers @ 2026-01-10 8:26 UTC (permalink / raw)
To: Tony Jones, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Alexander Shishkin,
Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark, Howard Chu,
Stephen Brennan, linux-kernel, linux-perf-users
sample__fprintf_callchain was using map__fprintf_srcline which won't
report inline line numbers. Fix by using the srcline from the
callchain and falling back to the map variant.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/evsel_fprintf.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/tools/perf/util/evsel_fprintf.c b/tools/perf/util/evsel_fprintf.c
index 10f1a03c2860..5521d00bff2c 100644
--- a/tools/perf/util/evsel_fprintf.c
+++ b/tools/perf/util/evsel_fprintf.c
@@ -185,8 +185,12 @@ int sample__fprintf_callchain(struct perf_sample *sample, int left_alignment,
if (print_dso && (!sym || !sym->inlined))
printed += map__fprintf_dsoname_dsoff(map, print_dsoff, addr, fp);
- if (print_srcline)
- printed += map__fprintf_srcline(map, addr, "\n ", fp);
+ if (print_srcline) {
+ if (node->srcline)
+ printed += fprintf(fp, "\n %s", node->srcline);
+ else
+ printed += map__fprintf_srcline(map, addr, "\n ", fp);
+ }
if (sym && sym->inlined)
printed += fprintf(fp, " (inlined)");
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2 5/6] perf test workload: Add inlineloop test workload
2026-01-10 8:26 [PATCH v2 0/6] Add a libdw addr2line implementation Ian Rogers
` (3 preceding siblings ...)
2026-01-10 8:26 ` [PATCH v2 4/6] perf callchain: Fix srcline printing with inlines Ian Rogers
@ 2026-01-10 8:26 ` Ian Rogers
2026-01-10 8:26 ` [PATCH v2 6/6] perf test: Test addr2line unwinding works with inline functions Ian Rogers
5 siblings, 0 replies; 7+ messages in thread
From: Ian Rogers @ 2026-01-10 8:26 UTC (permalink / raw)
To: Tony Jones, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Alexander Shishkin,
Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark, Howard Chu,
Stephen Brennan, linux-kernel, linux-perf-users
The purpose of this workload is to gather samples in an inlined
function. This can be used to test whether inlined addr2line works
correctly.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/tests/builtin-test.c | 1 +
tools/perf/tests/tests.h | 1 +
tools/perf/tests/workloads/Build | 2 +
tools/perf/tests/workloads/inlineloop.c | 52 +++++++++++++++++++++++++
4 files changed, 56 insertions(+)
create mode 100644 tools/perf/tests/workloads/inlineloop.c
diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c
index bd6ffa8e4578..e2490652f030 100644
--- a/tools/perf/tests/builtin-test.c
+++ b/tools/perf/tests/builtin-test.c
@@ -153,6 +153,7 @@ static struct test_workload *workloads[] = {
&workload__datasym,
&workload__landlock,
&workload__traploop,
+ &workload__inlineloop,
};
#define workloads__for_each(workload) \
diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h
index cb67ddbd0375..1f0f8b267fb1 100644
--- a/tools/perf/tests/tests.h
+++ b/tools/perf/tests/tests.h
@@ -240,6 +240,7 @@ DECLARE_WORKLOAD(brstack);
DECLARE_WORKLOAD(datasym);
DECLARE_WORKLOAD(landlock);
DECLARE_WORKLOAD(traploop);
+DECLARE_WORKLOAD(inlineloop);
extern const char *dso_to_test;
extern const char *test_objdump_path;
diff --git a/tools/perf/tests/workloads/Build b/tools/perf/tests/workloads/Build
index fb1012cc4fc3..866a00bd14a0 100644
--- a/tools/perf/tests/workloads/Build
+++ b/tools/perf/tests/workloads/Build
@@ -8,9 +8,11 @@ perf-test-y += brstack.o
perf-test-y += datasym.o
perf-test-y += landlock.o
perf-test-y += traploop.o
+perf-test-y += inlineloop.o
CFLAGS_sqrtloop.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE
CFLAGS_leafloop.o = -g -O0 -fno-inline -fno-omit-frame-pointer -U_FORTIFY_SOURCE
CFLAGS_brstack.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE
CFLAGS_datasym.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE
CFLAGS_traploop.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE
+CFLAGS_inlineloop.o = -g -O2
diff --git a/tools/perf/tests/workloads/inlineloop.c b/tools/perf/tests/workloads/inlineloop.c
new file mode 100644
index 000000000000..bc82dfc7c410
--- /dev/null
+++ b/tools/perf/tests/workloads/inlineloop.c
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <pthread.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>
+#include <linux/compiler.h>
+#include "../tests.h"
+
+static volatile int a;
+static volatile sig_atomic_t done;
+
+static void sighandler(int sig __maybe_unused)
+{
+ done = 1;
+}
+
+static inline void __attribute__((always_inline)) leaf(int b)
+{
+again:
+ a += b;
+ if (!done)
+ goto again;
+}
+
+static inline void __attribute__((always_inline)) middle(int b)
+{
+ leaf(b);
+}
+
+static noinline void parent(int b)
+{
+ middle(b);
+}
+
+static int inlineloop(int argc, const char **argv)
+{
+ int sec = 1;
+
+ pthread_setname_np(pthread_self(), "perf-inlineloop");
+ if (argc > 0)
+ sec = atoi(argv[0]);
+
+ signal(SIGINT, sighandler);
+ signal(SIGALRM, sighandler);
+ alarm(sec);
+
+ parent(sec);
+
+ return 0;
+}
+
+DEFINE_WORKLOAD(inlineloop);
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2 6/6] perf test: Test addr2line unwinding works with inline functions
2026-01-10 8:26 [PATCH v2 0/6] Add a libdw addr2line implementation Ian Rogers
` (4 preceding siblings ...)
2026-01-10 8:26 ` [PATCH v2 5/6] perf test workload: Add inlineloop test workload Ian Rogers
@ 2026-01-10 8:26 ` Ian Rogers
5 siblings, 0 replies; 7+ messages in thread
From: Ian Rogers @ 2026-01-10 8:26 UTC (permalink / raw)
To: Tony Jones, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Alexander Shishkin,
Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark, Howard Chu,
Stephen Brennan, linux-kernel, linux-perf-users
Add a test that seeks to see inline functions correctly displayed in
perf script from the inlineloop workload.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/tests/shell/addr2line_inlines.sh | 47 +++++++++++++++++++++
1 file changed, 47 insertions(+)
create mode 100755 tools/perf/tests/shell/addr2line_inlines.sh
diff --git a/tools/perf/tests/shell/addr2line_inlines.sh b/tools/perf/tests/shell/addr2line_inlines.sh
new file mode 100755
index 000000000000..4a5b6f5be23d
--- /dev/null
+++ b/tools/perf/tests/shell/addr2line_inlines.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+# test addr2line inline unwinding
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+err=0
+test_dir=$(mktemp -d /tmp/perf-test-inline-addr2line.XXXXXXXXXX)
+perf_data="${test_dir}/perf.data"
+perf_script_txt="${test_dir}/perf_script.txt"
+
+cleanup() {
+ rm -rf "${test_dir}"
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+test_inlinedloop() {
+ echo "Inline unwinding verification test"
+ # Record data. Currently only dwarf callchains support inlined functions.
+ perf record --call-graph dwarf -e task-clock:u -o "${perf_data}" -- perf test -w inlineloop 1
+
+ # Check output with inline (default) and srcline
+ perf script -i "${perf_data}" --fields +srcline > "${perf_script_txt}"
+
+ # Expect the leaf and middle functions to occur on lines in the 20s, with
+ # the non-inlined parent function on a line in the 30s.
+ if grep -q "inlineloop.c:2. (inlined)" "${perf_script_txt}" &&
+ grep -q "inlineloop.c:3.$" "${perf_script_txt}"
+ then
+ echo "Inline unwinding verification test [Success]"
+ else
+ echo "Inline unwinding verification test [Failed missing inlined functions]"
+ err=1
+ fi
+}
+
+test_inlinedloop
+
+cleanup
+exit $err
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-01-10 8:27 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-01-10 8:26 [PATCH v2 0/6] Add a libdw addr2line implementation Ian Rogers
2026-01-10 8:26 ` [PATCH v2 1/6] perf addr2line: Add a libdw implementation Ian Rogers
2026-01-10 8:26 ` [PATCH v2 2/6] perf addr2line.c: Rename a2l_style to cmd_a2l_style Ian Rogers
2026-01-10 8:26 ` [PATCH v2 3/6] perf srcline: Add configuration support for the addr2line style Ian Rogers
2026-01-10 8:26 ` [PATCH v2 4/6] perf callchain: Fix srcline printing with inlines Ian Rogers
2026-01-10 8:26 ` [PATCH v2 5/6] perf test workload: Add inlineloop test workload Ian Rogers
2026-01-10 8:26 ` [PATCH v2 6/6] perf test: Test addr2line unwinding works with inline functions 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®