mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 01:32:21 +02:00
perf unwind: Refactor get_entries to allow dynamic libdw/libunwind selection
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: 2e9191573a ("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>
This commit is contained in:
parent
e356a67cc4
commit
4248ae6e79
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
104
tools/perf/util/unwind.c
Normal file
104
tools/perf/util/unwind.c
Normal file
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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,6 +73,18 @@ 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
|
||||
#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 int unwind__prepare_access(struct maps *maps __maybe_unused,
|
||||
struct map *map __maybe_unused,
|
||||
bool *initialized __maybe_unused)
|
||||
|
|
@ -57,26 +95,5 @@ 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
|
||||
#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)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 /* HAVE_DWARF_UNWIND_SUPPORT */
|
||||
#endif /* __UNWIND_H */
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user