All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] perf unwind-libdw: Fix reading the stack of a 32-bit task
@ 2026-09-23 19:34 Stian Halseth
  2026-09-23 19:45 ` sashiko-bot
  2026-09-23 20:33 ` Ian Rogers
  0 siblings, 2 replies; 5+ messages in thread
From: Stian Halseth @ 2026-09-23 19:34 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: Ian Rogers, Peter Zijlstra, Ingo Molnar, Mark Rutland,
	Alexander Shishkin, Jiri Olsa, Adrian Hunter, James Clark,
	linux-perf-users, sparclinux, linux-kernel, Stian Halseth

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>
Signed-off-by: Stian Halseth <stian@itx.no>
---
Ian raised the cross-endian case in review of the sparc64 perf_regs
series:
https://lore.kernel.org/all/CAP-5=fUuRm3dWra4a-6eNF3F=tafcHvvDipqmDEXN5+ti-sW0A@mail.gmail.com/

Tested with 32-bit sparc programs (static, dynamic and multithreaded) on
top of that series, with a local change so perf recognises sparc32plus
binaries: natively on sparc64 perf died with SIGBUS before and unwinds
complete chains after; cross-endian on x86 no call chain was complete
before and all are after. 64-bit unwinding is unchanged in both.

Words read from a DSO rather than the stack dump are still not swapped
for a recording of the other byte order, whatever the word size, as
before this change.

 tools/perf/util/unwind-libdw.c | 46 ++++++++++++++++++++++++++++------
 1 file 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;

base-commit: edd8a9fe2eca009599e013a29c421c7a6b5ad1b9
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-09-24 17:08 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-23 19:34 [PATCH] perf unwind-libdw: Fix reading the stack of a 32-bit task Stian Halseth
2026-09-23 19:45 ` sashiko-bot
2026-09-23 20:29   ` Stian Halseth
2026-09-23 20:33 ` Ian Rogers
2026-09-24 17:08   ` Arnaldo Carvalho de Melo

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.