#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"
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()
#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"
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,
#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"
"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",
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
#include "build-id.h"
#include "debug.h"
#include "config.h"
+#include "unwind.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
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;
}
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,
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,
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,
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)
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;
}
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;
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)
/*
* 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,
};
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);
}
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)
--- /dev/null
+// 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);
+}
#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;
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
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
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;
}
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 */