mirror of https://lore.kernel.org/lkml/
 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 20:33 ` Ian Rogers
  0 siblings, 1 reply; 3+ 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	[flat|nested] 3+ messages in thread

* Re: [PATCH] perf unwind-libdw: Fix reading the stack of a 32-bit task
  2026-09-23 19:34 [PATCH] perf unwind-libdw: Fix reading the stack of a 32-bit task Stian Halseth
@ 2026-09-23 20:33 ` Ian Rogers
  2026-09-24 17:08   ` Arnaldo Carvalho de Melo
  0 siblings, 1 reply; 3+ messages in thread
From: Ian Rogers @ 2026-09-23 20:33 UTC (permalink / raw)
  To: Stian Halseth
  Cc: Arnaldo Carvalho de Melo, Namhyung Kim, Peter Zijlstra,
	Ingo Molnar, Mark Rutland, Alexander Shishkin, Jiri Olsa,
	Adrian Hunter, James Clark, linux-perf-users, sparclinux,
	linux-kernel

On Wed, Sep 23, 2026 at 12:34 PM Stian Halseth <stian@itx.no> wrote:
>
> 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>

Reviewed-by: Ian Rogers <irogers@google.com>

Thanks!
Ian

> ---
> 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	[flat|nested] 3+ messages in thread

* Re: [PATCH] perf unwind-libdw: Fix reading the stack of a 32-bit task
  2026-09-23 20:33 ` Ian Rogers
@ 2026-09-24 17:08   ` Arnaldo Carvalho de Melo
  0 siblings, 0 replies; 3+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-09-24 17:08 UTC (permalink / raw)
  To: Ian Rogers
  Cc: Stian Halseth, Namhyung Kim, Peter Zijlstra, Ingo Molnar,
	Mark Rutland, Alexander Shishkin, Jiri Olsa, Adrian Hunter,
	James Clark, linux-perf-users, sparclinux, linux-kernel

On Wed, Sep 23, 2026 at 01:33:48PM -0700, Ian Rogers wrote:
> On Wed, Sep 23, 2026 at 12:34 PM Stian Halseth <stian@itx.no> wrote:
> >
> > 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>
> 
> Reviewed-by: Ian Rogers <irogers@google.com>

Thanks, applied to perf-tools-next, for v7.4.

- Arnaldo

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

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

Thread overview: 3+ 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 20:33 ` Ian Rogers
2026-09-24 17:08   ` Arnaldo Carvalho de Melo

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®