[libunwind] [libunwind][PPC64] Fix _Unwind_Backtrace SIGSEGV from TOC restore at end of stack on LE (PR #212142)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Jul 26 11:28:58 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libunwind
Author: Piotr Kubaj (pkubaj)
<details>
<summary>Changes</summary>
On powerpc64le (ELFv2 ABI), `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_ppc64le_in_backtrace`, set to 1 at
the start of `_Unwind_Backtrace` and cleared at each of its exit points.
On powerpc64le, `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.
Discovered while debugging `lang/rust` build failures with `RUST_BACKTRACE=1`
on FreeBSD/powerpc64le (IBM POWER9). This is the companion to the
`unw_getcontext` VSX corruption fix (#<!-- -->198371, 6b0a46958c56); with both
applied to FreeBSD's libunwind (`libgcc_s.so.1`), the rust 1.95.0 build
completes.
---
Full diff: https://github.com/llvm/llvm-project/pull/212142.diff
2 Files Affected:
- (modified) libunwind/src/DwarfInstructions.hpp (+18-1)
- (modified) libunwind/src/UnwindLevel1-gcc-ext.c (+18)
``````````diff
diff --git a/libunwind/src/DwarfInstructions.hpp b/libunwind/src/DwarfInstructions.hpp
index 8868932d6821f..b5909217b6d06 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(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+// 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_ppc64le_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 powerpc64le (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(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ && !_unw_ppc64le_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..410b9f00a9882 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(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+// 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_ppc64le_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(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ _unw_ppc64le_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(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ _unw_ppc64le_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(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ _unw_ppc64le_in_backtrace = 0;
+#endif
return result;
}
}
``````````
</details>
https://github.com/llvm/llvm-project/pull/212142
More information about the cfe-commits
mailing list