mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 09:36:22 +02:00
Local unwinding only works on the machine libunwind is built for, rather than cross platform, the APIs for remote and local unwinding are similar but types like unw_word_t depend on the included header. Place the architecture specific code into the appropriate libunwind-<arch>.c file. Put generic code in unwind-libunwind.c and use libunwind-arch to choose the correct implementation based on the thread's e_machine. Structuring the code this way avoids including the unwind-libunwind-local.c for each architecture of remote unwinding. Data is moved into the struct unwind_info to simplify the architecture and generic code, trying to keep as much code as possible generic. 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: 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> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
313 lines
8.0 KiB
C
313 lines
8.0 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include "libunwind-arch.h"
|
|
#include "../debug.h"
|
|
#include "../maps.h"
|
|
#include "../thread.h"
|
|
#include "../../../arch/x86/include/uapi/asm/perf_regs.h"
|
|
#include <linux/compiler.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/zalloc.h>
|
|
#include <elf.h>
|
|
#include <errno.h>
|
|
|
|
#ifdef HAVE_LIBUNWIND_X86_SUPPORT
|
|
#include <libunwind-x86.h>
|
|
#endif
|
|
|
|
int __get_perf_regnum_for_unw_regnum_i386(int unw_regnum __maybe_unused)
|
|
{
|
|
#ifndef HAVE_LIBUNWIND_X86_SUPPORT
|
|
return -EINVAL;
|
|
#else
|
|
static const int perf_i386_regnums[] = {
|
|
#define REGNUM(reg) [UNW_X86_E ## reg] = PERF_REG_X86_ ## reg
|
|
REGNUM(AX),
|
|
REGNUM(DX),
|
|
REGNUM(CX),
|
|
REGNUM(BX),
|
|
REGNUM(SI),
|
|
REGNUM(DI),
|
|
REGNUM(BP),
|
|
REGNUM(SP),
|
|
REGNUM(IP),
|
|
#undef REGNUM
|
|
};
|
|
|
|
if (unw_regnum == UNW_X86_EAX)
|
|
return PERF_REG_X86_AX;
|
|
|
|
if (unw_regnum < 0 || unw_regnum >= (int)ARRAY_SIZE(perf_i386_regnums) ||
|
|
perf_i386_regnums[unw_regnum] == 0) {
|
|
pr_err("unwind: invalid reg id %d\n", unw_regnum);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return perf_i386_regnums[unw_regnum];
|
|
#endif // HAVE_LIBUNWIND_X86_SUPPORT
|
|
}
|
|
|
|
void __libunwind_arch__flush_access_i386(struct maps *maps __maybe_unused)
|
|
{
|
|
#ifdef HAVE_LIBUNWIND_X86_SUPPORT
|
|
unw_flush_cache(maps__addr_space(maps), 0, 0);
|
|
#endif
|
|
}
|
|
|
|
void __libunwind_arch__finish_access_i386(struct maps *maps __maybe_unused)
|
|
{
|
|
#ifdef HAVE_LIBUNWIND_X86_SUPPORT
|
|
unw_destroy_addr_space(maps__addr_space(maps));
|
|
#endif
|
|
}
|
|
|
|
#ifdef HAVE_LIBUNWIND_X86_SUPPORT
|
|
static int find_proc_info(unw_addr_space_t as, unw_word_t ip, unw_proc_info_t *pi,
|
|
int need_unwind_info, void *arg)
|
|
{
|
|
return __libunwind__find_proc_info(as, ip, pi, need_unwind_info, arg);
|
|
}
|
|
|
|
static void put_unwind_info(unw_addr_space_t __maybe_unused as,
|
|
unw_proc_info_t *pi __maybe_unused,
|
|
void *arg __maybe_unused)
|
|
{
|
|
pr_debug("unwind: put_unwind_info called\n");
|
|
}
|
|
|
|
static int get_dyn_info_list_addr(unw_addr_space_t __maybe_unused as,
|
|
unw_word_t __maybe_unused *dil_addr,
|
|
void __maybe_unused *arg)
|
|
{
|
|
return -UNW_ENOINFO;
|
|
}
|
|
|
|
static int access_mem(unw_addr_space_t as, unw_word_t addr, unw_word_t *valp,
|
|
int __write, void *arg)
|
|
{
|
|
return __libunwind__access_mem(as, addr, valp, __write, arg);
|
|
}
|
|
|
|
static int access_reg(unw_addr_space_t as, unw_regnum_t regnum, unw_word_t *valp,
|
|
int __write, void *arg)
|
|
{
|
|
return __libunwind__access_reg(as, regnum, valp, __write, arg);
|
|
}
|
|
|
|
static int access_fpreg(unw_addr_space_t __maybe_unused as,
|
|
unw_regnum_t __maybe_unused num,
|
|
unw_fpreg_t __maybe_unused *val,
|
|
int __maybe_unused __write,
|
|
void __maybe_unused *arg)
|
|
{
|
|
pr_err("unwind: access_fpreg unsupported\n");
|
|
return -UNW_EINVAL;
|
|
}
|
|
|
|
static int resume(unw_addr_space_t __maybe_unused as,
|
|
unw_cursor_t __maybe_unused *cu,
|
|
void __maybe_unused *arg)
|
|
{
|
|
pr_err("unwind: resume unsupported\n");
|
|
return -UNW_EINVAL;
|
|
}
|
|
|
|
static int get_proc_name(unw_addr_space_t __maybe_unused as,
|
|
unw_word_t __maybe_unused addr,
|
|
char __maybe_unused *bufp, size_t __maybe_unused buf_len,
|
|
unw_word_t __maybe_unused *offp, void __maybe_unused *arg)
|
|
{
|
|
pr_err("unwind: get_proc_name unsupported\n");
|
|
return -UNW_EINVAL;
|
|
}
|
|
#endif
|
|
|
|
void *__libunwind_arch__create_addr_space_i386(void)
|
|
{
|
|
#ifdef HAVE_LIBUNWIND_X86_SUPPORT
|
|
static unw_accessors_t accessors = {
|
|
.find_proc_info = find_proc_info,
|
|
.put_unwind_info = put_unwind_info,
|
|
.get_dyn_info_list_addr = get_dyn_info_list_addr,
|
|
.access_mem = access_mem,
|
|
.access_reg = access_reg,
|
|
.access_fpreg = access_fpreg,
|
|
.resume = resume,
|
|
.get_proc_name = get_proc_name,
|
|
};
|
|
unw_addr_space_t addr_space;
|
|
|
|
addr_space = unw_create_addr_space(&accessors, /*byte_order=*/0);
|
|
unw_set_caching_policy(addr_space, UNW_CACHE_GLOBAL);
|
|
return addr_space;
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
#ifdef HAVE_LIBUNWIND_X86_SUPPORT
|
|
extern int UNW_OBJ(dwarf_search_unwind_table) (unw_addr_space_t as,
|
|
unw_word_t ip,
|
|
unw_dyn_info_t *di,
|
|
unw_proc_info_t *pi,
|
|
int need_unwind_info, void *arg);
|
|
#define dwarf_search_unwind_table UNW_OBJ(dwarf_search_unwind_table)
|
|
#endif
|
|
|
|
int __libunwind_arch__dwarf_search_unwind_table_i386(void *as __maybe_unused,
|
|
uint64_t ip __maybe_unused,
|
|
struct libarch_unwind__dyn_info *_di __maybe_unused,
|
|
void *pi __maybe_unused,
|
|
int need_unwind_info __maybe_unused,
|
|
void *arg __maybe_unused)
|
|
{
|
|
#ifdef HAVE_LIBUNWIND_X86_SUPPORT
|
|
unw_dyn_info_t di = {
|
|
.format = UNW_INFO_FORMAT_REMOTE_TABLE,
|
|
.start_ip = _di->start_ip,
|
|
.end_ip = _di->end_ip,
|
|
.u = {
|
|
.rti = {
|
|
.segbase = _di->segbase,
|
|
.table_data = _di->table_data,
|
|
.table_len = _di->table_len,
|
|
},
|
|
},
|
|
};
|
|
int ret = dwarf_search_unwind_table(as, ip, &di, pi, need_unwind_info, arg);
|
|
|
|
_di->start_ip = di.start_ip;
|
|
_di->end_ip = di.end_ip;
|
|
_di->segbase = di.u.rti.segbase;
|
|
_di->table_data = di.u.rti.table_data;
|
|
_di->table_len = di.u.rti.table_len;
|
|
return ret;
|
|
#else
|
|
return -EINVAL;
|
|
#endif
|
|
}
|
|
|
|
#if defined(HAVE_LIBUNWIND_X86_SUPPORT) && !defined(NO_LIBUNWIND_DEBUG_FRAME_X86)
|
|
extern int UNW_OBJ(dwarf_find_debug_frame) (int found, unw_dyn_info_t *di_debug,
|
|
unw_word_t ip,
|
|
unw_word_t segbase,
|
|
const char *obj_name, unw_word_t start,
|
|
unw_word_t end);
|
|
#define dwarf_find_debug_frame UNW_OBJ(dwarf_find_debug_frame)
|
|
#endif
|
|
|
|
int __libunwind_arch__dwarf_find_debug_frame_i386(int found __maybe_unused,
|
|
struct libarch_unwind__dyn_info *_di __maybe_unused,
|
|
uint64_t ip __maybe_unused,
|
|
uint64_t segbase __maybe_unused,
|
|
const char *obj_name __maybe_unused,
|
|
uint64_t start __maybe_unused,
|
|
uint64_t end __maybe_unused)
|
|
{
|
|
#if defined(HAVE_LIBUNWIND_X86_SUPPORT) && !defined(NO_LIBUNWIND_DEBUG_FRAME_X86)
|
|
unw_dyn_info_t di = {
|
|
.format = UNW_INFO_FORMAT_REMOTE_TABLE,
|
|
.start_ip = _di->start_ip,
|
|
.end_ip = _di->end_ip,
|
|
.u = {
|
|
.rti = {
|
|
.segbase = _di->segbase,
|
|
.table_data = _di->table_data,
|
|
.table_len = _di->table_len,
|
|
},
|
|
},
|
|
};
|
|
int ret = dwarf_find_debug_frame(found, &di, ip, segbase, obj_name, start, end);
|
|
|
|
_di->start_ip = di.start_ip;
|
|
_di->end_ip = di.end_ip;
|
|
_di->segbase = di.u.ti.segbase;
|
|
_di->table_data = di.u.ti.table_data;
|
|
_di->table_len = di.u.ti.table_len;
|
|
return ret;
|
|
#else
|
|
return -EINVAL;
|
|
#endif
|
|
}
|
|
|
|
struct unwind_info *__libunwind_arch_unwind_info__new_i386(struct thread *thread __maybe_unused,
|
|
struct perf_sample *sample __maybe_unused,
|
|
int max_stack __maybe_unused,
|
|
bool best_effort __maybe_unused,
|
|
uint64_t first_ip __maybe_unused)
|
|
{
|
|
#ifdef HAVE_LIBUNWIND_X86_SUPPORT
|
|
struct arch_unwind_info {
|
|
struct unwind_info ui;
|
|
unw_cursor_t _cursor;
|
|
uint64_t _ips[];
|
|
};
|
|
|
|
struct maps *maps = thread__maps(thread);
|
|
void *addr_space = maps__addr_space(maps);
|
|
struct arch_unwind_info *ui;
|
|
int ret;
|
|
|
|
if (addr_space == NULL)
|
|
return NULL;
|
|
|
|
ui = zalloc(sizeof(*ui) + sizeof(ui->_ips[0]) * max_stack);
|
|
if (!ui)
|
|
return NULL;
|
|
|
|
ui->ui.machine = maps__machine(maps);
|
|
ui->ui.thread = thread;
|
|
ui->ui.sample = sample;
|
|
ui->ui.cursor = &ui->_cursor;
|
|
ui->ui.ips = &ui->_ips[0];
|
|
ui->ui.ips[0] = first_ip;
|
|
ui->ui.cur_ip = 1;
|
|
ui->ui.max_ips = max_stack;
|
|
ui->ui.unw_word_t_size = sizeof(unw_word_t);
|
|
ui->ui.e_machine = EM_I386;
|
|
ui->ui.best_effort = best_effort;
|
|
|
|
ret = unw_init_remote(&ui->_cursor, addr_space, &ui->ui);
|
|
if (ret) {
|
|
if (!best_effort)
|
|
pr_err("libunwind: %s\n", unw_strerror(ret));
|
|
free(ui);
|
|
return NULL;
|
|
}
|
|
|
|
return &ui->ui;
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
int __libunwind_arch__unwind_step_i386(struct unwind_info *ui __maybe_unused)
|
|
{
|
|
#ifdef HAVE_LIBUNWIND_X86_SUPPORT
|
|
int ret;
|
|
|
|
if (ui->cur_ip >= ui->max_ips)
|
|
return 0;
|
|
|
|
ret = unw_step(ui->cursor);
|
|
if (ret > 0) {
|
|
uint64_t ip;
|
|
|
|
unw_get_reg(ui->cursor, UNW_REG_IP, &ip);
|
|
|
|
if (unw_is_signal_frame(ui->cursor) <= 0) {
|
|
/*
|
|
* Decrement the IP for any non-activation frames. This
|
|
* is required to properly find the srcline for caller
|
|
* frames. See also the documentation for
|
|
* dwfl_frame_pc(), which this code tries to replicate.
|
|
*/
|
|
--ip;
|
|
}
|
|
ui->ips[ui->cur_ip++] = ip;
|
|
}
|
|
return ret;
|
|
#else
|
|
return -EINVAL;
|
|
#endif
|
|
}
|