diff options
| author | Stian Halseth <stian@itx.no> | 2026-09-23 21:34:32 +0200 |
|---|---|---|
| committer | Arnaldo Carvalho de Melo <acme@redhat.com> | 2026-09-24 19:22:06 +0200 |
| commit | d8fd56321d7bb5df31cdf33e9acbd62f4606b483 (patch) | |
| tree | 66dc16217e275023c82acd5192ee550bafb0a1ac | |
| parent | edd8a9fe2eca009599e013a29c421c7a6b5ad1b9 (diff) | |
perf unwind-libdw: Fix reading the stack of a 32-bit task
memory_read() hands libdw 8 bytes whatever the sampled task's ABI. For a
32-bit task libdw keeps the low 32 bits (__libdwfl_frame_reg_set()), so
on a big-endian machine each word read from the stack or a DSO is the
one at addr + 4, and unwinding stops at the first frame restored from
the stack. On sparc the 8-byte load from a stack address that is only
4-byte aligned even traps, and perf dies with SIGBUS. Read 4 bytes for a
PERF_SAMPLE_REGS_ABI_32 sample.
A recording of the other byte order has each sample swapped in 8-byte
units when it is read (perf_event__all64_swap()), which leaves the
4-byte words of a 32-bit stack dump reversed and paired up wrongly.
Undo the swap of the unit holding the word and swap the word itself, as
__evsel__parse_sample() does for PERF_SAMPLE_CPU.
Suggested-by: Ian Rogers <irogers@google.com>
Reviewed-by: Ian Rogers <irogers@google.com>
Signed-off-by: Stian Halseth <stian@itx.no>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
| -rw-r--r-- | tools/perf/util/unwind-libdw.c | 46 |
1 files changed, 39 insertions, 7 deletions
diff --git a/tools/perf/util/unwind-libdw.c b/tools/perf/util/unwind-libdw.c index 4ccfcc7c2dfc..d49901b1571e 100644 --- a/tools/perf/util/unwind-libdw.c +++ b/tools/perf/util/unwind-libdw.c @@ -4,6 +4,7 @@ #include <elfutils/libdw.h> #include <elfutils/libdwfl.h> #include <inttypes.h> +#include <byteswap.h> #include <errno.h> #include "debug.h" #include "dso.h" @@ -17,6 +18,8 @@ #include <linux/types.h> #include <linux/zalloc.h> #include "event.h" +#include "evsel.h" +#include "memswap.h" #include "perf_regs.h" #include "callchain.h" #include "util/env.h" @@ -203,9 +206,10 @@ static bool get_thread(Dwfl *dwfl __maybe_unused, pid_t tid, void *arg, } static int access_dso_mem(struct unwind_info *ui, Dwarf_Addr addr, - Dwarf_Word *data) + Dwarf_Word *data, size_t len) { struct addr_location al; + union u64_swap u; ssize_t size; struct dso *dso; @@ -218,15 +222,38 @@ static int access_dso_mem(struct unwind_info *ui, Dwarf_Addr addr, if (!dso) goto out_fail; - size = dso__data_read_addr(dso, al.map, ui->machine, addr, (u8 *) data, sizeof(*data)); + size = dso__data_read_addr(dso, al.map, ui->machine, addr, (u8 *)&u, len); addr_location__exit(&al); - return !(size == sizeof(*data)); + if (size != (ssize_t)len) + return 1; + *data = len == sizeof(u32) ? u.val32[0] : u.val64; + return 0; out_fail: addr_location__exit(&al); return -1; } +/* + * libdw expects a 32-bit task's words zero-extended. A recording of the + * other byte order was swapped in 8-byte units when it was read (see + * perf_event__all64_swap()), so a 4-byte word is picked out of its unit with + * that swap undone, as for PERF_SAMPLE_CPU in __evsel__parse_sample(). + */ +static Dwarf_Word stack_word(struct stack_dump *stack, int offset, size_t len, + bool swapped) +{ + union u64_swap u; + + if (len == sizeof(u64)) + return *(Dwarf_Word *)&stack->data[offset]; + if (!swapped) + return *(u32 *)&stack->data[offset]; + + u.val64 = bswap_64(*(u64 *)&stack->data[offset & ~7]); + return bswap_32(u.val32[(offset & 4) / 4]); +} + static bool memory_read(Dwfl *dwfl __maybe_unused, Dwarf_Addr addr, Dwarf_Word *result, void *arg) { @@ -234,11 +261,16 @@ static bool memory_read(Dwfl *dwfl __maybe_unused, Dwarf_Addr addr, Dwarf_Word * struct unwind_info *ui = dwfl_ui_ti->ui; struct stack_dump *stack = &ui->sample->user_stack; u64 start, end; + bool swapped; + size_t len; int offset; int ret; if (!ui->sample->user_regs) return false; + len = ui->sample->user_regs->abi == PERF_SAMPLE_REGS_ABI_32 ? + sizeof(u32) : sizeof(u64); + swapped = ui->sample->evsel && ui->sample->evsel->needs_swap; ret = perf_reg_value(&start, ui->sample->user_regs, perf_arch_reg_sp(ui->e_machine)); @@ -248,11 +280,11 @@ static bool memory_read(Dwfl *dwfl __maybe_unused, Dwarf_Addr addr, Dwarf_Word * end = start + stack->size; /* Check overflow. */ - if (addr + sizeof(Dwarf_Word) < addr) + if (addr + len < addr) return false; - if (addr < start || addr + sizeof(Dwarf_Word) > end) { - ret = access_dso_mem(ui, addr, result); + if (addr < start || addr + len > end) { + ret = access_dso_mem(ui, addr, result, len); if (ret) { pr_debug("unwind: access_mem 0x%" PRIx64 " not inside range" " 0x%" PRIx64 "-0x%" PRIx64 "\n", @@ -263,7 +295,7 @@ static bool memory_read(Dwfl *dwfl __maybe_unused, Dwarf_Addr addr, Dwarf_Word * } offset = addr - start; - *result = *(Dwarf_Word *)&stack->data[offset]; + *result = stack_word(stack, offset, len, swapped); pr_debug("unwind: access_mem addr 0x%" PRIx64 ", val %lx, offset %d\n", addr, (unsigned long)*result, offset); return true; |
