]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
perf unwind: Refactor get_entries to allow dynamic libdw/libunwind selection
authorIan Rogers <irogers@google.com>
Wed, 13 May 2026 23:31:45 +0000 (16:31 -0700)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Fri, 15 May 2026 11:25:02 +0000 (08:25 -0300)
Currently, both libdw and libunwind define 'unwind__get_entries'. This
causes a duplicate symbol build failure when both are compiled into
perf.

This commit refactors the DWARF unwind post-processing to be
configurable at runtime via the .perfconfig file option
'unwind.style', or using the argument '--unwind-style' in the commands
'perf report', 'perf script' and 'perf inject', in a similar manner to
the addr2line or the disassembler style.

The file 'tools/perf/util/unwind.c' adds the top-level dispatch
function 'unwind__get_entries'. The backend implementations are
renamed to 'libdw__get_entries' and 'libunwind__get_entries'. Both are
attempted as fallbacks if not configured, or if the primary backend
fails.

Fixes: 2e9191573a69ff96 ("perf build: Remove NO_LIBDW_DWARF_UNWIND option")
Signed-off-by: Ian Rogers <irogers@google.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Albert Ou <aou@eecs.berkeley.edu>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Alexandre Ghiti <alex@ghiti.fr>
Cc: Andrew Jones <andrew.jones@oss.qualcomm.com>
Cc: Athira Rajeev <atrajeev@linux.ibm.com>
Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
Cc: Dmitrii Dolgov <9erthalion6@gmail.com>
Cc: Florian Fainelli <florian.fainelli@broadcom.com>
Cc: Howard Chu <howardchu95@gmail.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Clark <james.clark@linaro.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: John Garry <john.g.garry@oracle.com>
Cc: Leo Yan <leo.yan@linux.dev>
Cc: libunwind-devel@nongnu.org
Cc: Li Guan <guanli.oerv@isrc.iscas.ac.cn>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Palmer Dabbelt <palmer@dabbelt.com>
Cc: Paul Walmsley <pjw@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Shimin Guo <shimin.guo@skydio.com>
Cc: Thomas Richter <tmricht@linux.ibm.com>
Cc: Tomas Glozar <tglozar@redhat.com>
Cc: Will Deacon <will@kernel.org>
[ Don't mix declarations and code, move 'entries' variable to the start of scope ]
[ Use pr_warning_once() instead of pr_err() in stubs for get_entries(), suggested by a local sashiko instance ]
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
tools/perf/builtin-inject.c
tools/perf/builtin-report.c
tools/perf/builtin-script.c
tools/perf/util/Build
tools/perf/util/config.c
tools/perf/util/symbol_conf.h
tools/perf/util/unwind-libdw.c
tools/perf/util/unwind-libunwind-local.c
tools/perf/util/unwind-libunwind.c
tools/perf/util/unwind.c [new file with mode: 0644]
tools/perf/util/unwind.h

index 6ab20df358c43b466e8a744f3cf2e5a51058a61b..a2493f1097df97f5bacd20962b6dbb9b1deb9182 100644 (file)
@@ -26,6 +26,7 @@
 #include "util/synthetic-events.h"
 #include "util/thread.h"
 #include "util/namespaces.h"
+#include "util/unwind.h"
 #include "util/util.h"
 #include "util/tsc.h"
 
@@ -2563,6 +2564,9 @@ int cmd_inject(int argc, const char **argv)
                OPT_STRING(0, "guestmount", &symbol_conf.guestmount, "directory",
                           "guest mount directory under which every guest os"
                           " instance has a subdir"),
+               OPT_CALLBACK(0, "unwind-style", NULL, "unwind style",
+                            "unwind styles (libdw,libunwind)",
+                            unwind__option),
                OPT_BOOLEAN(0, "convert-callchain", &inject.convert_callchain,
                            "Generate callchains using DWARF and drop register/stack data"),
                OPT_END()
index 95c0bdba6b11652cdfa95631fc82a2782dc7feef..0b0966d94128ee479ea47139ea2c64f63a9d214e 100644 (file)
@@ -48,6 +48,7 @@
 #include "util/time-utils.h"
 #include "util/auxtrace.h"
 #include "util/units.h"
+#include "util/unwind.h"
 #include "util/util.h" // perf_tip()
 #include "ui/ui.h"
 #include "ui/progress.h"
@@ -1449,6 +1450,9 @@ int cmd_report(int argc, const char **argv)
        OPT_CALLBACK(0, "addr2line-style", NULL, "addr2line style",
                     "addr2line styles (libdw,llvm,libbfd,addr2line)",
                     report_parse_addr2line_config),
+       OPT_CALLBACK(0, "unwind-style", NULL, "unwind style",
+                    "unwind styles (libdw,libunwind)",
+                    unwind__option),
        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,
index c8ac9f01a36bc51145e8ef94d1ca1af8366fe4b5..fd0b4609516b3e1a83bc582b0ec85e39096434b3 100644 (file)
@@ -63,6 +63,7 @@
 #include <linux/err.h>
 #include "util/dlfilter.h"
 #include "util/record.h"
+#include "util/unwind.h"
 #include "util/util.h"
 #include "util/cgroup.h"
 #include "util/annotate.h"
@@ -4159,6 +4160,9 @@ int cmd_script(int argc, const char **argv)
                        "Enable symbol demangling"),
        OPT_BOOLEAN(0, "demangle-kernel", &symbol_conf.demangle_kernel,
                        "Enable kernel symbol demangling"),
+       OPT_CALLBACK(0, "unwind-style", NULL, "unwind style",
+                    "unwind styles (libdw,libunwind)",
+                    unwind__option),
        OPT_STRING(0, "addr2line", &symbol_conf.addr2line_path, "path",
                        "addr2line binary to use for line numbers"),
        OPT_STRING(0, "time", &script.time_str, "str",
index 70cc91d00804d7fac192cd3aea7584a608315727..01edfccebb88e060b878eb8c9e037887889faf16 100644 (file)
@@ -216,6 +216,7 @@ ifndef CONFIG_SETNS
 perf-util-y += setns.o
 endif
 
+perf-util-y += unwind.o
 perf-util-$(CONFIG_LIBDW) += probe-finder.o
 perf-util-$(CONFIG_LIBDW) += dwarf-aux.o
 perf-util-$(CONFIG_LIBDW) += dwarf-regs.o
index 087002fb1b9bc8a84ebe13246e244ec4c3bfe5ca..7988149dc7ed8e9f3397c01ec18e9cd93166db2c 100644 (file)
@@ -23,6 +23,7 @@
 #include "build-id.h"
 #include "debug.h"
 #include "config.h"
+#include "unwind.h"
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <stdlib.h>
@@ -525,6 +526,9 @@ int perf_default_config(const char *var, const char *value,
        if (strstarts(var, "addr2line."))
                return addr2line_configure(var, value, dummy);
 
+       if (strstarts(var, "unwind."))
+               return unwind__configure(var, value, dummy);
+
        /* Add other config variables here. */
        return 0;
 }
index 6cd454d7c98e6b094864b22047054b5b56c761da..0dee5aa6a5340d07c7195734be2af1eee6a639c2 100644 (file)
@@ -9,6 +9,15 @@
 struct strlist;
 struct intlist;
 
+enum unwind_style {
+       UNWIND_STYLE_UNKNOWN = 0,
+       UNWIND_STYLE_LIBDW,
+       UNWIND_STYLE_LIBUNWIND,
+};
+
+#define MAX_UNWIND_STYLE (UNWIND_STYLE_LIBUNWIND + 1)
+
+
 enum a2l_style {
        A2L_STYLE_UNKNOWN = 0,
        A2L_STYLE_LIBDW,
@@ -81,6 +90,7 @@ struct symbol_conf {
        const char              *addr2line_path;
        enum a2l_style  addr2line_style[MAX_A2L_STYLE];
        int             addr2line_timeout_ms;
+       enum unwind_style unwind_style[MAX_UNWIND_STYLE];
        unsigned long   time_quantum;
        struct strlist  *dso_list,
                        *comm_list,
index 05e8e68bd49c446a63090578ab2d1bfe09a370cb..7f35042be5677982e081af0187b221c194defb46 100644 (file)
@@ -339,7 +339,7 @@ frame_callback(Dwfl_Frame *state, void *arg)
               DWARF_CB_ABORT : DWARF_CB_OK;
 }
 
-int unwind__get_entries(unwind_entry_cb_t cb, void *arg,
+int libdw__get_entries(unwind_entry_cb_t cb, void *arg,
                        struct thread *thread,
                        struct perf_sample *data,
                        int max_stack,
@@ -353,10 +353,10 @@ int unwind__get_entries(unwind_entry_cb_t cb, void *arg,
        static struct unwind_info *ui;
        Dwfl *dwfl;
        Dwarf_Word ip;
-       int err = -EINVAL, i;
+       int err = -EINVAL, i, entries;
 
        if (!data->user_regs || !data->user_regs->regs)
-               return -EINVAL;
+               return 0;
 
        ui = zalloc(sizeof(*ui) + sizeof(ui->entries[0]) * max_stack);
        if (!ui)
@@ -430,6 +430,18 @@ int unwind__get_entries(unwind_entry_cb_t cb, void *arg,
                map_symbol__exit(&ui->entries[i].ms);
 
        dwfl_ui_ti->ui = NULL;
+       entries = (int)ui->idx;
        free(ui);
-       return 0;
+       /*
+        * Unwinder return contract:
+        *  > 0 : unwinding succeeded (stops fallback). If we found frames but hit an error
+        *        (e.g. truncated stack), report success to preserve existing frames.
+        *    0 : unwinding failed without yielding frames. Ignore non-fatal errors
+        *        (e.g. missing debug info, DWARF corruption) to allow fallback unwinder or
+        *        kernel callchain resolution to proceed.
+        *  < 0 : fatal error (e.g. -ENOMEM). Aborts unwinding entirely.
+        */
+       if (err)
+               return (err == -ENOMEM) ? -ENOMEM : (entries > 0 ? 1 : 0);
+       return entries;
 }
index 87d496e9dfa6669cb80c045ba036be6f39bf2038..27e2f7b317895469cc292c3ddf7376e4282eb718 100644 (file)
@@ -744,7 +744,7 @@ static int get_entries(struct unwind_info *ui, unwind_entry_cb_t cb,
        ret = perf_reg_value(&val, perf_sample__user_regs(ui->sample),
                             perf_arch_reg_ip(e_machine));
        if (ret)
-               return ret;
+               return 0;
 
        ips[i++] = (unw_word_t) val;
 
@@ -757,7 +757,7 @@ static int get_entries(struct unwind_info *ui, unwind_entry_cb_t cb,
                addr_space = maps__addr_space(thread__maps(ui->thread));
 
                if (addr_space == NULL)
-                       return -1;
+                       return 0;
 
                ret = unw_init_remote(&c, addr_space, ui);
                if (ret && !ui->best_effort)
@@ -785,15 +785,30 @@ static int get_entries(struct unwind_info *ui, unwind_entry_cb_t cb,
        /*
         * Display what we got based on the order setup.
         */
+       int entries = 0;
        for (i = 0; i < max_stack && !ret; i++) {
                int j = i;
 
                if (callchain_param.order == ORDER_CALLER)
                        j = max_stack - i - 1;
-               ret = ips[j] ? entry(ips[j], ui->thread, cb, arg) : 0;
+               if (ips[j]) {
+                       ret = entry(ips[j], ui->thread, cb, arg);
+                       if (ret)
+                               break;
+                       entries++;
+               }
        }
 
-       return ret;
+       /*
+        * Unwinder return contract:
+        *  > 0 : unwinding succeeded (stops fallback).
+        *    0 : unwinding failed without yielding frames. Ignore non-fatal errors
+        *        (e.g. stepping failure) to allow fallback unwinder or kernel callchains.
+        *  < 0 : fatal error (e.g. -ENOMEM). Aborts unwinding entirely.
+        */
+       if (ret == -ENOMEM)
+               return -ENOMEM;
+       return (entries > 0 || ret == 0) ? entries : 0;
 }
 
 static int _unwind__get_entries(unwind_entry_cb_t cb, void *arg,
@@ -809,10 +824,10 @@ static int _unwind__get_entries(unwind_entry_cb_t cb, void *arg,
        };
 
        if (!data->user_regs || !data->user_regs->regs)
-               return -EINVAL;
+               return 0;
 
        if (max_stack <= 0)
-               return -EINVAL;
+               return 0;
 
        return get_entries(&ui, cb, arg, max_stack);
 }
index cb8be6acfb6f54ab12306a1c95c9b8df0265fbbf..a0016b897dae26e8bdd38b16b815d5065a2881df 100644 (file)
@@ -79,7 +79,7 @@ void unwind__finish_access(struct maps *maps)
                ops->finish_access(maps);
 }
 
-int unwind__get_entries(unwind_entry_cb_t cb, void *arg,
+int libunwind__get_entries(unwind_entry_cb_t cb, void *arg,
                         struct thread *thread,
                         struct perf_sample *data, int max_stack,
                         bool best_effort)
diff --git a/tools/perf/util/unwind.c b/tools/perf/util/unwind.c
new file mode 100644 (file)
index 0000000..4ed4b1d
--- /dev/null
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "debug.h"
+#include "symbol_conf.h"
+#include "unwind.h"
+#include <linux/string.h>
+#include <string.h>
+#include <stdlib.h>
+
+int unwind__get_entries(unwind_entry_cb_t cb __maybe_unused, void *arg __maybe_unused,
+                       struct thread *thread __maybe_unused,
+                       struct perf_sample *data __maybe_unused,
+                       int max_stack __maybe_unused,
+                       bool best_effort __maybe_unused)
+{
+       int ret = 0;
+
+#if defined(HAVE_LIBDW_SUPPORT) || defined(HAVE_LIBUNWIND_SUPPORT)
+       if (symbol_conf.unwind_style[0] == UNWIND_STYLE_UNKNOWN) {
+               int i = 0;
+#ifdef HAVE_LIBDW_SUPPORT
+               symbol_conf.unwind_style[i++] = UNWIND_STYLE_LIBDW;
+#endif
+#ifdef HAVE_LIBUNWIND_SUPPORT
+               symbol_conf.unwind_style[i++] = UNWIND_STYLE_LIBUNWIND;
+#endif
+       }
+#endif //defined(HAVE_LIBDW_SUPPORT) || defined(HAVE_LIBUNWIND_SUPPORT)
+
+       for (size_t i = 0; i < ARRAY_SIZE(symbol_conf.unwind_style); i++) {
+               switch (symbol_conf.unwind_style[i]) {
+               case UNWIND_STYLE_LIBDW:
+                       ret = libdw__get_entries(cb, arg, thread, data, max_stack, best_effort);
+                       break;
+               case UNWIND_STYLE_LIBUNWIND:
+                       ret = libunwind__get_entries(cb, arg, thread, data, max_stack, best_effort);
+                       break;
+               case UNWIND_STYLE_UNKNOWN:
+               default:
+#if !defined(HAVE_LIBDW_SUPPORT) && !defined(HAVE_LIBUNWIND_SUPPORT)
+                       pr_warning_once(
+                               "Error: dwarf unwinding not supported, build perf with libdw or libunwind.\n");
+#endif
+                       ret = 0;
+                       break;
+               }
+               if (ret > 0) {
+                       ret = 0;
+                       break;
+               }
+               if (ret < 0)
+                       break;
+       }
+       return ret;
+}
+
+int unwind__configure(const char *var, const char *value, void *cb __maybe_unused)
+{
+       static const char * const unwind_style_names[] = {
+               [UNWIND_STYLE_LIBDW] = "libdw",
+               [UNWIND_STYLE_LIBUNWIND] = "libunwind",
+               NULL
+       };
+       char *s, *p, *saveptr;
+       size_t i = 0;
+
+       if (strcmp(var, "unwind.style"))
+               return 0;
+
+       if (!value)
+               return -1;
+
+       s = strdup(value);
+       if (!s)
+               return -1;
+
+       memset(symbol_conf.unwind_style, 0, sizeof(symbol_conf.unwind_style));
+
+       p = strtok_r(s, ",", &saveptr);
+       while (p && i < ARRAY_SIZE(symbol_conf.unwind_style)) {
+               bool found = false;
+               char *q = strim(p);
+
+               for (size_t j = UNWIND_STYLE_LIBDW; j < MAX_UNWIND_STYLE; j++) {
+                       if (!strcasecmp(q, unwind_style_names[j])) {
+                               symbol_conf.unwind_style[i++] = j;
+                               found = true;
+                               break;
+                       }
+               }
+               if (!found)
+                       pr_warning("Unknown unwind style: %s\n", q);
+               p = strtok_r(NULL, ",", &saveptr);
+       }
+
+       free(s);
+       return 0;
+}
+
+int unwind__option(const struct option *opt __maybe_unused,
+                  const char *arg,
+                  int unset __maybe_unused)
+{
+       return unwind__configure("unwind.style", arg, NULL);
+}
index 9f7164c6d9aa9eccf591e6e0b4c014a2b0eb2080..69ba08afda792d17f352314a736a6f0c326d2b6e 100644 (file)
@@ -4,9 +4,10 @@
 
 #include <linux/compiler.h>
 #include <linux/types.h>
-#include "util/map_symbol.h"
+#include "map_symbol.h"
 
 struct maps;
+struct option;
 struct perf_sample;
 struct thread;
 
@@ -26,7 +27,9 @@ struct unwind_libunwind_ops {
                           struct perf_sample *data, int max_stack, bool best_effort);
 };
 
-#ifdef HAVE_DWARF_UNWIND_SUPPORT
+int unwind__configure(const char *var, const char *value, void *cb);
+int unwind__option(const struct option *opt, const char *arg, int unset);
+
 /*
  * When best_effort is set, don't report errors and fail silently. This could
  * be expanded in the future to be more permissive about things other than
@@ -36,8 +39,31 @@ int unwind__get_entries(unwind_entry_cb_t cb, void *arg,
                        struct thread *thread,
                        struct perf_sample *data, int max_stack,
                        bool best_effort);
-/* libunwind specific */
+
+#ifdef HAVE_LIBDW_SUPPORT
+int libdw__get_entries(unwind_entry_cb_t cb, void *arg,
+                      struct thread *thread,
+                      struct perf_sample *data, int max_stack,
+                      bool best_effort);
+#else
+#include "debug.h"
+static inline int libdw__get_entries(unwind_entry_cb_t cb __maybe_unused, void *arg __maybe_unused,
+                                    struct thread *thread __maybe_unused,
+                                    struct perf_sample *data __maybe_unused,
+                                    int max_stack __maybe_unused,
+                                    bool best_effort __maybe_unused)
+{
+       pr_warning_once("Error: libdw dwarf unwinding not built into perf\n");
+       return 0;
+}
+#endif
+
 #ifdef HAVE_LIBUNWIND_SUPPORT
+/* libunwind specific */
+int libunwind__get_entries(unwind_entry_cb_t cb, void *arg,
+                          struct thread *thread,
+                          struct perf_sample *data, int max_stack,
+                          bool best_effort);
 #ifndef LIBUNWIND__ARCH_REG_ID
 #define LIBUNWIND__ARCH_REG_ID(regnum) libunwind__arch_reg_id(regnum)
 #endif
@@ -47,25 +73,15 @@ int unwind__prepare_access(struct maps *maps, struct map *map, bool *initialized
 void unwind__flush_access(struct maps *maps);
 void unwind__finish_access(struct maps *maps);
 #else
-static inline int unwind__prepare_access(struct maps *maps __maybe_unused,
-                                        struct map *map __maybe_unused,
-                                        bool *initialized __maybe_unused)
-{
-       return 0;
-}
-
-static inline void unwind__flush_access(struct maps *maps __maybe_unused) {}
-static inline void unwind__finish_access(struct maps *maps __maybe_unused) {}
-#endif
-#else
-static inline int
-unwind__get_entries(unwind_entry_cb_t cb __maybe_unused,
-                   void *arg __maybe_unused,
-                   struct thread *thread __maybe_unused,
-                   struct perf_sample *data __maybe_unused,
-                   int max_stack __maybe_unused,
-                   bool best_effort __maybe_unused)
+#include "debug.h"
+static inline int libunwind__get_entries(unwind_entry_cb_t cb __maybe_unused,
+                                        void *arg __maybe_unused,
+                                        struct thread *thread __maybe_unused,
+                                        struct perf_sample *data __maybe_unused,
+                                        int max_stack __maybe_unused,
+                                        bool best_effort __maybe_unused)
 {
+       pr_warning_once("Error: libunwind dwarf unwinding not built into perf\n");
        return 0;
 }
 
@@ -78,5 +94,6 @@ static inline int unwind__prepare_access(struct maps *maps __maybe_unused,
 
 static inline void unwind__flush_access(struct maps *maps __maybe_unused) {}
 static inline void unwind__finish_access(struct maps *maps __maybe_unused) {}
-#endif /* HAVE_DWARF_UNWIND_SUPPORT */
+#endif
+
 #endif /* __UNWIND_H */