mirror of
https://github.com/torvalds/linux.git
synced 2026-11-10 17:11:02 +01:00
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>
This commit is contained in:
parent
edd8a9fe2e
commit
d8fd56321d
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user