Linux Perf Users
 help / color / mirror / Atom feed
From: Stian Halseth <stian@itx.no>
To: Arnaldo Carvalho de Melo <acme@kernel.org>,
	Namhyung Kim <namhyung@kernel.org>
Cc: Ian Rogers <irogers@google.com>,
	Peter Zijlstra <peterz@infradead.org>,
	Ingo Molnar <mingo@redhat.com>,
	Mark Rutland <mark.rutland@arm.com>,
	Alexander Shishkin <alexander.shishkin@linux.intel.com>,
	Jiri Olsa <jolsa@kernel.org>,
	Adrian Hunter <adrian.hunter@intel.com>,
	James Clark <james.clark@linaro.org>,
	linux-perf-users@vger.kernel.org, sparclinux@vger.kernel.org,
	linux-kernel@vger.kernel.org, Stian Halseth <stian@itx.no>
Subject: [PATCH] perf unwind-libdw: Fix reading the stack of a 32-bit task
Date: Wed, 23 Sep 2026 21:34:32 +0200	[thread overview]
Message-ID: <20260923193432.2489729-1-stian@itx.no> (raw)

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


             reply	other threads:[~2026-09-23 19:34 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-23 19:34 Stian Halseth [this message]
2026-09-23 19:45 ` [PATCH] perf unwind-libdw: Fix reading the stack of a 32-bit task 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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260923193432.2489729-1-stian@itx.no \
    --to=stian@itx.no \
    --cc=acme@kernel.org \
    --cc=adrian.hunter@intel.com \
    --cc=alexander.shishkin@linux.intel.com \
    --cc=irogers@google.com \
    --cc=james.clark@linaro.org \
    --cc=jolsa@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-perf-users@vger.kernel.org \
    --cc=mark.rutland@arm.com \
    --cc=mingo@redhat.com \
    --cc=namhyung@kernel.org \
    --cc=peterz@infradead.org \
    --cc=sparclinux@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox