]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
perf addr2line: Add a libdw implementation
authorIan Rogers <irogers@google.com>
Sun, 11 Jan 2026 04:13:33 +0000 (20:13 -0800)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Mon, 12 Jan 2026 19:23:06 +0000 (16:23 -0300)
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.

Committer testing:

  # perf probe -x ~/bin/perf libdw__addr2line
  Added new event:
    probe_perf:libdw_addr2line (on libdw__addr2line in /home/acme/bin/perf)

  You can now use it in all perf tools, such as:

   perf record -e probe_perf:libdw_addr2line -aR sleep 1

  #
  # perf stat -e probe_perf:libdw_addr2line perf report -f --dso perf --stdio -s srcfile,srcline
  # To display the perf.data header info, please use --header/--header-only options.
  #
  #
  # Total Lost Samples: 0
  #
  # Samples: 4K of event 'cpu/cycles/Pu'
  # Event count (approx.): 5535180842
  #
  # Overhead  Source File   Source:Line
  # ........  ............  ...............
  #
      99.04%  inlineloop.c  inlineloop.c:21
       0.46%  inlineloop.c  inlineloop.c:20

  #
  # (Tip: For tracepoint events, try: perf report -s trace_fields)
  #

   Performance counter stats for 'perf report -f --dso perf --stdio -s srcfile,srcline':

                  44      probe_perf:libdw_addr2line

         0.037260744 seconds time elapsed

         0.025299000 seconds user
         0.011918000 seconds sys
  #

Adding probes to the other addr2line implementations (llvm__addr2line,
libbfd__addr2line and cmd__addr2line) I noticed some fallbacks to the
llvm one:

 Performance counter stats for 'perf report -f --dso perf --stdio -s srcfile,srcline':

                44      probe_perf:libdw_addr2line
                23      probe_perf:llvm_addr2line
                 0 probe_perf:libbfd_addr2line
                 0      probe_perf:cmd_addr2line

Something to investigate further, but at least we don't fallback to the
cmd based one :-)

Reviewed-by: James Clark <james.clark@linaro.org>
Signed-off-by: Ian Rogers <irogers@google.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Howard Chu <howardchu95@gmail.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Stephen Brennan <stephen.s.brennan@oracle.com>
Cc: Tony Jones <tonyj@suse.de>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
tools/perf/util/Build
tools/perf/util/dso.c
tools/perf/util/dso.h
tools/perf/util/libdw.c [new file with mode: 0644]
tools/perf/util/libdw.h [new file with mode: 0644]
tools/perf/util/srcline.c
tools/perf/util/srcline.h

index 1c2a43e1dc68e7f751ec6082352b10de9ffeea62..2bed6274e248a521decc69eea6aac94385bd53c0 100644 (file)
@@ -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
index 344e689567ee1a2aae49f5807e68a6e5e07937c5..06980844c014ec9bce9c2f4b2eed54924805e0d7 100644 (file)
@@ -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));
index f8ccb9816b89c3878f66ca9de698570824518152..4aee23775054cf830661994a754c5529b3fd1c05 100644 (file)
@@ -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 (file)
index 0000000..e4bfd52
--- /dev/null
@@ -0,0 +1,153 @@
+// 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;
+       char *leaf_srcline;
+       bool leaf_srcline_used;
+};
+
+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));
+       const char *call_fname = die_get_call_file(die);
+       char *call_srcline = srcline__unknown;
+       struct inline_list *ilist;
+
+       if (!inline_sym)
+               return -ENOMEM;
+
+       /* Assign caller information to the parent. */
+       if (call_fname)
+               call_srcline = srcline_from_fileline(call_fname, die_get_call_lineno(die));
+
+       list_for_each_entry(ilist, &args->node->val, list) {
+               ilist->srcline =  call_srcline;
+               call_srcline = NULL;
+               break;
+       }
+       if (call_srcline && call_fname)
+               free(call_srcline);
+
+       /* Add this symbol to the chain as the leaf. */
+       inline_list__append_tail(inline_sym, args->leaf_srcline, args->node);
+       args->leaf_srcline_used = true;
+       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 = 0;
+
+       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,
+                       .leaf_srcline = srcline_from_fileline(src ?: "<unknown>", lineno),
+               };
+
+               /* Walk from the parent down to the leaf. */
+               cu_walk_functions_at(cudie, addr, libdw_a2l_cb, &args);
+
+               if (!args.leaf_srcline_used)
+                       free(args.leaf_srcline);
+       }
+       return 1;
+}
diff --git a/tools/perf/util/libdw.h b/tools/perf/util/libdw.h
new file mode 100644 (file)
index 0000000..0f8d7b4
--- /dev/null
@@ -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 */
index 27c0966611abb70baf4eed78d67316b3b3671af1..e2d280678b024a036e7de7edeb8a58b2eb23a309 100644 (file)
@@ -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;
index c36f573cd3390bf783900778d39c637e0997a45d..be9f002bf234883d52ded23f72b988e02139c63f 100644 (file)
@@ -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,