https://github.com/pkubaj updated 
https://github.com/llvm/llvm-project/pull/212142

>From 257ac77237f2894d2793483fe45410262e898808 Mon Sep 17 00:00:00 2001
From: Piotr Kubaj <[email protected]>
Date: Sun, 26 Jul 2026 21:17:21 +0200
Subject: [PATCH] [libunwind][PPC64] Fix _Unwind_Backtrace SIGSEGV from TOC
 restore at end of stack (ELFv2)

On ppc64 ELFv2, stepWithDwarf applies a heuristic to restore the TOC
pointer (r2) when the instruction at the return address is `ld r2, 24(r1)`:
it reads the saved value from sp+24. This is correct during normal
exception unwinding, where sp+24 lies within the caller's stack frame.

During _Unwind_Backtrace the walk continues to the outermost frame, where
sp+24 is outside any mapped stack region. Reading from it faults with a
SIGSEGV inside _Unwind_Backtrace itself. Because _Unwind_Backtrace is a
read-only stack walk that never executes landing pads, a valid r2 is not
needed there.

Fix: introduce a thread-local flag _unw_ppc64_in_backtrace, set to 1 at
the start of _Unwind_Backtrace and cleared at each of its exit points. On
ppc64 ELFv2, stepWithDwarf skips the TOC restore heuristic while the flag
is set. The flag is thread-local so a backtrace on one thread cannot
suppress the heuristic for exception unwinding on another.

The fault is a property of the ELFv2 sp+24 TOC-save location, not of byte
order: the instruction match reads in target endianness, and whether sp+24
is mapped is endianness-independent. The change is therefore gated on
`defined(_CALL_ELF) && _CALL_ELF == 2` and applies to both little- and
big-endian ELFv2. It was reproduced on ppc64le; big-endian ELFv2 was not
separately reproduced, but suppressing an unused heuristic during a
read-only walk is safe there and cannot affect exception unwinding.

Discovered while debugging lang/rust build failures with RUST_BACKTRACE=1
on FreeBSD/powerpc64le (IBM POWER9). With this and the companion
unw_getcontext VSX corruption fix applied to FreeBSD's libunwind, the
rust 1.95.0 build completes.
---
 libunwind/src/DwarfInstructions.hpp  | 19 ++++++++++++++++++-
 libunwind/src/UnwindLevel1-gcc-ext.c | 18 ++++++++++++++++++
 2 files changed, 36 insertions(+), 1 deletion(-)

diff --git a/libunwind/src/DwarfInstructions.hpp 
b/libunwind/src/DwarfInstructions.hpp
index 8868932d6821f..528a7b67b0589 100644
--- a/libunwind/src/DwarfInstructions.hpp
+++ b/libunwind/src/DwarfInstructions.hpp
@@ -22,6 +22,14 @@
 #include "dwarf2.h"
 #include "libunwind_ext.h"
 
+#if defined(_LIBUNWIND_TARGET_PPC64) && defined(_CALL_ELF) && _CALL_ELF == 2
+// Thread-local flag set by _Unwind_Backtrace to suppress the PPC64 ELFv2 TOC
+// restoration heuristic during read-only stack walks (see
+// UnwindLevel1-gcc-ext.c). Declared extern "C" so the C definition in
+// UnwindLevel1-gcc-ext.c is visible here.
+extern "C" _Thread_local int _unw_ppc64_in_backtrace;
+#endif
+
 namespace libunwind {
 
 
@@ -405,7 +413,16 @@ int DwarfInstructions<A, R>::stepWithDwarf(
       // then r2 was saved and needs to be restored.
       // ELFv2 ABI specifies that the TOC Pointer must be saved at SP + 24,
       // while in ELFv1 ABI it is saved at SP + 40.
-      if (R::getArch() == REGISTERS_PPC64 && returnAddress != 0) {
+      //
+      // On ppc64 ELFv2, skip this heuristic during _Unwind_Backtrace. A
+      // read-only stack walk does not execute landing pads and does not need
+      // a correct r2; suppressing the heuristic avoids reading from invalid
+      // stack slots at the bottom of the call stack.
+      if (R::getArch() == REGISTERS_PPC64 && returnAddress != 0
+#if defined(_CALL_ELF) && _CALL_ELF == 2
+          && !_unw_ppc64_in_backtrace
+#endif
+      ) {
         pint_t sp = newRegisters.getRegister(UNW_REG_SP);
         pint_t r2 = 0;
         switch (addressSpace.get32(returnAddress)) {
diff --git a/libunwind/src/UnwindLevel1-gcc-ext.c 
b/libunwind/src/UnwindLevel1-gcc-ext.c
index 1764499b304f1..56319912f8273 100644
--- a/libunwind/src/UnwindLevel1-gcc-ext.c
+++ b/libunwind/src/UnwindLevel1-gcc-ext.c
@@ -28,6 +28,14 @@
 
 #if defined(_LIBUNWIND_BUILD_ZERO_COST_APIS)
 
+#if defined(_LIBUNWIND_TARGET_PPC64) && defined(_CALL_ELF) && _CALL_ELF == 2
+// Set to 1 during _Unwind_Backtrace to suppress the PPC64 ELFv2 TOC
+// restoration heuristic (see DwarfInstructions.hpp). A read-only stack walk
+// does not need correct r2, and the heuristic can fault on invalid stack
+// data at the bottom of the call stack.
+_Thread_local int _unw_ppc64_in_backtrace = 0;
+#endif
+
 #if defined(_LIBUNWIND_SUPPORT_SEH_UNWIND)
 #define PRIVATE_1 private_[0]
 #elif defined(_LIBUNWIND_ARM_EHABI)
@@ -139,6 +147,10 @@ _Unwind_Backtrace(_Unwind_Trace_Fn callback, void *ref) {
   _LIBUNWIND_TRACE_API("_Unwind_Backtrace(callback=%p)",
                        (void *)(uintptr_t)callback);
 
+#if defined(_LIBUNWIND_TARGET_PPC64) && defined(_CALL_ELF) && _CALL_ELF == 2
+  _unw_ppc64_in_backtrace = 1;
+#endif
+
 #if defined(_LIBUNWIND_ARM_EHABI)
   // Create a mock exception object for force unwinding.
   _Unwind_Exception ex;
@@ -157,6 +169,9 @@ _Unwind_Backtrace(_Unwind_Trace_Fn callback, void *ref) {
       _LIBUNWIND_TRACE_UNWINDING(" _backtrace: ended because cursor reached "
                                  "bottom of stack, returning %d",
                                  _URC_END_OF_STACK);
+#if defined(_LIBUNWIND_TARGET_PPC64) && defined(_CALL_ELF) && _CALL_ELF == 2
+      _unw_ppc64_in_backtrace = 0;
+#endif
       return _URC_END_OF_STACK;
     }
 #else
@@ -202,6 +217,9 @@ _Unwind_Backtrace(_Unwind_Trace_Fn callback, void *ref) {
     if (result != _URC_NO_REASON) {
       _LIBUNWIND_TRACE_UNWINDING(
           " _backtrace: ended because callback returned %d", result);
+#if defined(_LIBUNWIND_TARGET_PPC64) && defined(_CALL_ELF) && _CALL_ELF == 2
+      _unw_ppc64_in_backtrace = 0;
+#endif
       return result;
     }
   }

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to