[libunwind] 45d1511 - [libunwind][AIX] Fix problem with stepping up from a leaf function when unwinding started in a signal handler

Xing Xue via cfe-commits cfe-commits at lists.llvm.org
Mon Oct 16 09:25:13 PDT 2023


Author: Xing Xue
Date: 2023-10-16T12:24:05-04:00
New Revision: 45d151138008c4880c8f9b77ffc43c23e0a9f1cb

URL: https://github.com/llvm/llvm-project/commit/45d151138008c4880c8f9b77ffc43c23e0a9f1cb
DIFF: https://github.com/llvm/llvm-project/commit/45d151138008c4880c8f9b77ffc43c23e0a9f1cb.diff

LOG: [libunwind][AIX] Fix problem with stepping up from a leaf function when unwinding started in a signal handler

Summary:
The implementation of AIX unwinder gets the return address from the link area of the stack frame of a function and uses the return address to walk up functions. However, when unwinding starts from a signal handler and the function that raised the signal happens to be a leaf function and it does not have its own stack frame, the return address of the stack frame of the leaf function points to the caller of the function that calls the leaf function because the leaf function and its caller share the same stack frame. As a result, the caller of the leaf function is skipped. This patch fixes the problem by saving the LR value in sigcontext when the unwinder hits the signal handler trampoline frame and using it as the return address of the leaf function. The LR value from sigcontext is saved in the unwinding context slot for LR currently unused.

Reviewed by: stephenpeckham

Differential Revision: https://reviews.llvm.org/D158655

Added: 
    libunwind/test/aix_signal_unwind.pass.sh.S

Modified: 
    libunwind/src/Registers.hpp
    libunwind/src/UnwindCursor.hpp
    libunwind/src/UnwindRegistersSave.S

Removed: 
    


################################################################################
diff  --git a/libunwind/src/Registers.hpp b/libunwind/src/Registers.hpp
index fb6e04e50fa1c7e..d11ddb3426d522e 100644
--- a/libunwind/src/Registers.hpp
+++ b/libunwind/src/Registers.hpp
@@ -619,6 +619,8 @@ class _LIBUNWIND_HIDDEN Registers_ppc {
   void      setIP(uint32_t value) { _registers.__srr0 = value; }
   uint64_t  getCR() const         { return _registers.__cr; }
   void      setCR(uint32_t value) { _registers.__cr = value; }
+  uint64_t  getLR() const         { return _registers.__lr; }
+  void      setLR(uint32_t value) { _registers.__lr = value; }
 
 private:
   struct ppc_thread_state_t {
@@ -1189,6 +1191,8 @@ class _LIBUNWIND_HIDDEN Registers_ppc64 {
   void      setIP(uint64_t value) { _registers.__srr0 = value; }
   uint64_t  getCR() const         { return _registers.__cr; }
   void      setCR(uint64_t value) { _registers.__cr = value; }
+  uint64_t  getLR() const         { return _registers.__lr; }
+  void      setLR(uint64_t value) { _registers.__lr = value; }
 
 private:
   struct ppc64_thread_state_t {

diff  --git a/libunwind/src/UnwindCursor.hpp b/libunwind/src/UnwindCursor.hpp
index dde94773bc34170..f89c5b2c2f73e33 100644
--- a/libunwind/src/UnwindCursor.hpp
+++ b/libunwind/src/UnwindCursor.hpp
@@ -2301,27 +2301,39 @@ int UnwindCursor<A, R>::stepWithTBTable(pint_t pc, tbtable *TBTable,
     if (!getFunctionName(functionBuf, sizeof(functionBuf), &offset)) {
       functionName = ".anonymous.";
     }
-    _LIBUNWIND_TRACE_UNWINDING("%s: Look up traceback table of func=%s at %p",
-                               __func__, functionName,
-                               reinterpret_cast<void *>(TBTable));
+    _LIBUNWIND_TRACE_UNWINDING(
+        "%s: Look up traceback table of func=%s at %p, pc=%p, "
+        "SP=%p, saves_lr=%d, stores_bc=%d",
+        __func__, functionName, reinterpret_cast<void *>(TBTable),
+        reinterpret_cast<void *>(pc),
+        reinterpret_cast<void *>(registers.getSP()), TBTable->tb.saves_lr,
+        TBTable->tb.stores_bc);
   }
 
 #if defined(__powerpc64__)
-  // Instruction to reload TOC register "l r2,40(r1)"
+  // Instruction to reload TOC register "ld r2,40(r1)"
   const uint32_t loadTOCRegInst = 0xe8410028;
   const int32_t unwPPCF0Index = UNW_PPC64_F0;
   const int32_t unwPPCV0Index = UNW_PPC64_V0;
 #else
-  // Instruction to reload TOC register "l r2,20(r1)"
+  // Instruction to reload TOC register "lwz r2,20(r1)"
   const uint32_t loadTOCRegInst = 0x80410014;
   const int32_t unwPPCF0Index = UNW_PPC_F0;
   const int32_t unwPPCV0Index = UNW_PPC_V0;
 #endif
 
+  // lastStack points to the stack frame of the next routine up.
+  pint_t curStack = static_cast<pint_t>(registers.getSP());
+  pint_t lastStack = *reinterpret_cast<pint_t *>(curStack);
+
+  if (lastStack == 0)
+    return UNW_STEP_END;
+
   R newRegisters = registers;
 
-  // lastStack points to the stack frame of the next routine up.
-  pint_t lastStack = *(reinterpret_cast<pint_t *>(registers.getSP()));
+  // If backchain is not stored, use the current stack frame.
+  if (!TBTable->tb.stores_bc)
+    lastStack = curStack;
 
   // Return address is the address after call site instruction.
   pint_t returnAddress;
@@ -2331,33 +2343,41 @@ int UnwindCursor<A, R>::stepWithTBTable(pint_t pc, tbtable *TBTable,
                                reinterpret_cast<void *>(lastStack));
 
     sigcontext *sigContext = reinterpret_cast<sigcontext *>(
-        reinterpret_cast<char *>(lastStack) + STKMIN);
+        reinterpret_cast<char *>(lastStack) + STKMINALIGN);
     returnAddress = sigContext->sc_jmpbuf.jmp_context.iar;
 
-    _LIBUNWIND_TRACE_UNWINDING("From sigContext=%p, returnAddress=%p\n",
-                               reinterpret_cast<void *>(sigContext),
-                               reinterpret_cast<void *>(returnAddress));
-
+    bool useSTKMIN = false;
     if (returnAddress < 0x10000000) {
-      // Try again using STKMINALIGN
+      // Try again using STKMIN.
       sigContext = reinterpret_cast<sigcontext *>(
-          reinterpret_cast<char *>(lastStack) + STKMINALIGN);
+          reinterpret_cast<char *>(lastStack) + STKMIN);
       returnAddress = sigContext->sc_jmpbuf.jmp_context.iar;
       if (returnAddress < 0x10000000) {
-        _LIBUNWIND_TRACE_UNWINDING("Bad returnAddress=%p\n",
-                                   reinterpret_cast<void *>(returnAddress));
+        _LIBUNWIND_TRACE_UNWINDING("Bad returnAddress=%p from sigcontext=%p",
+                                   reinterpret_cast<void *>(returnAddress),
+                                   reinterpret_cast<void *>(sigContext));
         return UNW_EBADFRAME;
-      } else {
-        _LIBUNWIND_TRACE_UNWINDING("Tried again using STKMINALIGN: "
-                                   "sigContext=%p, returnAddress=%p. "
-                                   "Seems to be a valid address\n",
-                                   reinterpret_cast<void *>(sigContext),
-                                   reinterpret_cast<void *>(returnAddress));
       }
+      useSTKMIN = true;
     }
+    _LIBUNWIND_TRACE_UNWINDING("Returning from a signal handler %s: "
+                               "sigContext=%p, returnAddress=%p. "
+                               "Seems to be a valid address",
+                               useSTKMIN ? "STKMIN" : "STKMINALIGN",
+                               reinterpret_cast<void *>(sigContext),
+                               reinterpret_cast<void *>(returnAddress));
+
     // Restore the condition register from sigcontext.
     newRegisters.setCR(sigContext->sc_jmpbuf.jmp_context.cr);
 
+    // Save the LR in sigcontext for stepping up when the function that
+    // raised the signal is a leaf function. This LR has the return address
+    // to the caller of the leaf function.
+    newRegisters.setLR(sigContext->sc_jmpbuf.jmp_context.lr);
+    _LIBUNWIND_TRACE_UNWINDING(
+        "Save LR=%p from sigcontext",
+        reinterpret_cast<void *>(sigContext->sc_jmpbuf.jmp_context.lr));
+
     // Restore GPRs from sigcontext.
     for (int i = 0; i < 32; ++i)
       newRegisters.setRegister(i, sigContext->sc_jmpbuf.jmp_context.gpr[i]);
@@ -2380,13 +2400,26 @@ int UnwindCursor<A, R>::stepWithTBTable(pint_t pc, tbtable *TBTable,
     }
   } else {
     // Step up a normal frame.
-    returnAddress = reinterpret_cast<pint_t *>(lastStack)[2];
 
-    _LIBUNWIND_TRACE_UNWINDING("Extract info from lastStack=%p, "
-                               "returnAddress=%p\n",
-                               reinterpret_cast<void *>(lastStack),
-                               reinterpret_cast<void *>(returnAddress));
-    _LIBUNWIND_TRACE_UNWINDING("fpr_regs=%d, gpr_regs=%d, saves_cr=%d\n",
+    if (!TBTable->tb.saves_lr && registers.getLR()) {
+      // This case should only occur if we were called from a signal handler
+      // and the signal occurred in a function that doesn't save the LR.
+      returnAddress = registers.getLR();
+      _LIBUNWIND_TRACE_UNWINDING("Use saved LR=%p",
+                                 reinterpret_cast<void *>(returnAddress));
+    } else {
+      // Otherwise, use the LR value in the stack link area.
+      returnAddress = reinterpret_cast<pint_t *>(lastStack)[2];
+    }
+
+    // Reset LR in the current context.
+    newRegisters.setLR(NULL);
+
+    _LIBUNWIND_TRACE_UNWINDING(
+        "Extract info from lastStack=%p, returnAddress=%p",
+        reinterpret_cast<void *>(lastStack),
+        reinterpret_cast<void *>(returnAddress));
+    _LIBUNWIND_TRACE_UNWINDING("fpr_regs=%d, gpr_regs=%d, saves_cr=%d",
                                TBTable->tb.fpr_saved, TBTable->tb.gpr_saved,
                                TBTable->tb.saves_cr);
 
@@ -2450,7 +2483,7 @@ int UnwindCursor<A, R>::stepWithTBTable(pint_t pc, tbtable *TBTable,
 
       struct vec_ext *vec_ext = reinterpret_cast<struct vec_ext *>(charPtr);
 
-      _LIBUNWIND_TRACE_UNWINDING("vr_saved=%d\n", vec_ext->vr_saved);
+      _LIBUNWIND_TRACE_UNWINDING("vr_saved=%d", vec_ext->vr_saved);
 
       // Restore vector register(s) if saved on the stack.
       if (vec_ext->vr_saved) {
@@ -2480,11 +2513,11 @@ int UnwindCursor<A, R>::stepWithTBTable(pint_t pc, tbtable *TBTable,
 
     // Do we need to set the TOC register?
     _LIBUNWIND_TRACE_UNWINDING(
-        "Current gpr2=%p\n",
+        "Current gpr2=%p",
         reinterpret_cast<void *>(newRegisters.getRegister(2)));
     if (firstInstruction == loadTOCRegInst) {
       _LIBUNWIND_TRACE_UNWINDING(
-          "Set gpr2=%p from frame\n",
+          "Set gpr2=%p from frame",
           reinterpret_cast<void *>(reinterpret_cast<pint_t *>(lastStack)[5]));
       newRegisters.setRegister(2, reinterpret_cast<pint_t *>(lastStack)[5]);
     }
@@ -2516,7 +2549,6 @@ int UnwindCursor<A, R>::stepWithTBTable(pint_t pc, tbtable *TBTable,
   } else {
     isSignalFrame = false;
   }
-
   return UNW_STEP_SUCCESS;
 }
 #endif // defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND)

diff  --git a/libunwind/src/UnwindRegistersSave.S b/libunwind/src/UnwindRegistersSave.S
index 5534d1734b6ba7b..dc0f7da31ccf802 100644
--- a/libunwind/src/UnwindRegistersSave.S
+++ b/libunwind/src/UnwindRegistersSave.S
@@ -352,7 +352,12 @@ LnoR2Fix:
   std   0,  PPC64_OFFS_CR(3)
   mfxer 0
   std   0,  PPC64_OFFS_XER(3)
+#if defined(_AIX)
+  // LR value saved from the register is not used, initialize it to 0.
+  li    0,  0
+#else
   mflr  0
+#endif
   std   0,  PPC64_OFFS_LR(3)
   mfctr 0
   std   0,  PPC64_OFFS_CTR(3)
@@ -565,8 +570,8 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext)
   // is called from a 
diff erent module. Save the original TOC register
   // in the context if this is the case.
   mflr    4
-  lwz     4,   0(4)     // Get the instruction at the return address.
-  xoris   0,  4, 0x8041 // Is it reloading the TOC register "ld 2,40(1)"?
+  lwz     4,  0(4)      // Get the instruction at the return address.
+  xoris   0,  4, 0x8041 // Is it reloading the TOC register "lwz 2,20(1)"?
   cmplwi  0,  0x14
   bne     0,  LnoR2Fix  // No need to fix up r2 if it is not.
   lwz     2,  20(1)     // Use the saved TOC register in the stack.
@@ -610,6 +615,11 @@ LnoR2Fix:
   // save CR registers
   mfcr    0
   stw     0, 136(3)
+#if defined(_AIX)
+  // LR value from the register is not used, initialize it to 0.
+  li      0, 0
+  stw     0, 144(3)
+#endif
   // save CTR register
   mfctr   0
   stw     0, 148(3)

diff  --git a/libunwind/test/aix_signal_unwind.pass.sh.S b/libunwind/test/aix_signal_unwind.pass.sh.S
new file mode 100644
index 000000000000000..9ca18e9481f4fc5
--- /dev/null
+++ b/libunwind/test/aix_signal_unwind.pass.sh.S
@@ -0,0 +1,245 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// Test that _Unwind_Backtrace() walks up from a signal handler and produces
+// a correct traceback when the function raising the signal does not save
+// the link register or does not store the stack back chain.
+
+// REQUIRES: target=powerpc{{(64)?}}-ibm-aix
+
+// Test when the function raising the signal does not save the link register
+// RUN: %{cxx} -x c++ %s -o %t.exe -DCXX_CODE %{flags} %{compile_flags}
+// RUN: %{exec} %t.exe
+
+// Test when the function raising the signal does not store stack back chain.
+// RUN: %{cxx} -x c++ -c %s -o %t1.o -DCXX_CODE -DNOBACKCHAIN %{flags} \
+// RUN:   %{compile_flags}
+// RUN: %{cxx} -c %s -o %t2.o %{flags} %{compile_flags}
+// RUN: %{cxx} -o %t1.exe %t1.o %t2.o %{flags} %{link_flags}
+// RUN: %{exec} %t1.exe
+
+#ifdef CXX_CODE
+
+#undef NDEBUG
+#include <assert.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/debug.h>
+#include <unwind.h>
+
+#define NAME_ARRAY_SIZE 10
+#define NAMES_EXPECTED   6
+
+const char* namesExpected[] = {"handler", "abc", "bar", "foo", "main",
+                               "__start"};
+char *namesObtained[NAME_ARRAY_SIZE] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+int funcIndex = 0;
+
+// Get the function name from traceback table.
+char *getFuncName(uintptr_t pc, uint16_t *nameLen) {
+  uint32_t *p = reinterpret_cast<uint32_t *>(pc);
+
+  // Keep looking forward until a word of 0 is found. The traceback
+  // table starts at the following word.
+  while (*p)
+    ++p;
+  tbtable *TBTable = reinterpret_cast<tbtable *>(p + 1);
+
+  if (!TBTable->tb.name_present)
+    return NULL;
+
+  // Get to the optional portion of the traceback table.
+  p = reinterpret_cast<uint32_t *>(&TBTable->tb_ext);
+
+  // Skip field parminfo if it exists.
+  if (TBTable->tb.fixedparms || TBTable->tb.floatparms)
+    ++p;
+
+  // Skip field tb_offset if it exists.
+  if (TBTable->tb.has_tboff)
+    ++p;
+
+  // Skip field hand_mask if it exists.
+  if (TBTable->tb.int_hndl)
+    ++p;
+
+  // Skip fields ctl_info and ctl_info_disp if they exist.
+  if (TBTable->tb.has_ctl)
+    p += 1 + *p;
+
+  *nameLen = *reinterpret_cast<uint16_t *>(p);
+  return reinterpret_cast<char *>(p) + sizeof(uint16_t);
+}
+
+_Unwind_Reason_Code callBack(struct _Unwind_Context *uc, void *arg) {
+  (void)arg;
+  uint16_t nameLen;
+  uintptr_t ip = _Unwind_GetIP(uc);
+  if (funcIndex < NAME_ARRAY_SIZE)
+    namesObtained[funcIndex++] = strndup(getFuncName(ip, &nameLen), nameLen);
+  return _URC_NO_REASON;
+}
+
+extern "C" void handler(int signum) {
+  (void)signum;
+  // Walk stack frames for traceback.
+  _Unwind_Backtrace(callBack, NULL);
+
+  // Verify the traceback.
+  assert(funcIndex <= NAMES_EXPECTED && "Obtained names more than expected");
+  for (int i = 0; i < funcIndex; ++i) {
+    assert(!strcmp(namesExpected[i], namesObtained[i]) &&
+           "Function names do not match");
+    free(namesObtained[i]);
+  }
+  exit(0);
+}
+
+#ifdef NOBACKCHAIN
+// abc() is in assembly. It raises signal SIGSEGV and does not store
+// the stack back chain.
+extern "C" void abc();
+
+#else
+volatile int *null = 0;
+
+// abc() raises signal SIGSEGV and does not save the link register.
+extern "C" __attribute__((noinline)) void abc() {
+  // Produce a SIGSEGV.
+  *null = 0;
+}
+#endif
+
+extern "C" __attribute__((noinline)) void bar() {
+  abc();
+}
+
+extern "C" __attribute__((noinline)) void foo() {
+  bar();
+}
+
+int main() {
+  // Set signal handler for SIGSEGV.
+  signal(SIGSEGV, handler);
+  foo();
+}
+
+#else // Assembly code for abc().
+// This assembly code is similar to the following C code but it saves the
+// link register.
+//
+// int *badp = 0;
+// void abc() {
+//   *badp = 0;
+// }
+
+#ifdef __64BIT__
+        .csect [PR],5
+        .file   "abc.c"
+        .globl  abc[DS]                 # -- Begin function abc
+        .globl  .abc
+        .align  4
+        .csect abc[DS],3
+        .vbyte  8, .abc                 # @abc
+        .vbyte  8, TOC[TC0]
+        .vbyte  8, 0
+        .csect [PR],5
+.abc:
+# %bb.0:                                # %entry
+        mflr 0
+        std 0, 16(1)
+        ld 3, L..C0(2)                  # @badp
+        bl $+4
+        ld 4, 0(3)
+        li 3, 0
+        stw 3, 0(4)
+        ld 0, 16(1)
+        mtlr 0
+        blr
+L..abc0:
+        .vbyte  4, 0x00000000           # Traceback table begin
+        .byte   0x00                    # Version = 0
+        .byte   0x09                    # Language = CPlusPlus
+        .byte   0x20                    # -IsGlobaLinkage, -IsOutOfLineEpilogOrPrologue
+                                        # +HasTraceBackTableOffset, -IsInternalProcedure
+                                        # -HasControlledStorage, -IsTOCless
+                                        # -IsFloatingPointPresent
+                                        # -IsFloatingPointOperationLogOrAbortEnabled
+        .byte   0x61                    # -IsInterruptHandler, +IsFunctionNamePresent, +IsAllocaUsed
+                                        # OnConditionDirective = 0, -IsCRSaved, +IsLRSaved
+        .byte   0x00                    # -IsBackChainStored, -IsFixup, NumOfFPRsSaved = 0
+        .byte   0x01                    # -HasExtensionTable, -HasVectorInfo, NumOfGPRsSaved = 1
+        .byte   0x00                    # NumberOfFixedParms = 0
+        .byte   0x01                    # NumberOfFPParms = 0, +HasParmsOnStack
+        .vbyte  4, L..abc0-.abc         # Function size
+        .vbyte  2, 0x0003               # Function name len = 3
+        .byte   "abc"                   # Function Name
+        .byte   0x1f                    # AllocaUsed
+                                        # -- End function
+        .csect badp[RW],3
+        .globl  badp[RW]                # @badp
+        .align  3
+        .vbyte  8, 0
+        .toc
+L..C0:
+        .tc badp[TC],badp[RW]
+#else
+        .csect [PR],5
+        .file   "abc.c"
+        .globl  abc[DS]                 # -- Begin function abc
+        .globl  .abc
+        .align  4
+        .csect abc[DS],2
+        .vbyte  4, .abc                 # @abc
+        .vbyte  4, TOC[TC0]
+        .vbyte  4, 0
+        .csect [PR],5
+.abc:
+# %bb.0:                                # %entry
+        mflr 0
+        stw 0, 8(1)
+        lwz 3, L..C0(2)                 # @badp
+        bl $+4
+        lwz 4, 0(3)
+        li 3, 0
+        stw 3, 0(4)
+        lwz 0, 8(1)
+        mtlr 0
+        blr
+L..abc0:
+        .vbyte  4, 0x00000000           # Traceback table begin
+        .byte   0x00                    # Version = 0
+        .byte   0x09                    # Language = CPlusPlus
+        .byte   0x20                    # -IsGlobaLinkage, -IsOutOfLineEpilogOrPrologue
+                                        # +HasTraceBackTableOffset, -IsInternalProcedure
+                                        # -HasControlledStorage, -IsTOCless
+                                        # -IsFloatingPointPresent
+                                        # -IsFloatingPointOperationLogOrAbortEnabled
+        .byte   0x61                    # -IsInterruptHandler, +IsFunctionNamePresent, +IsAllocaUsed
+                                        # OnConditionDirective = 0, -IsCRSaved, +IsLRSaved
+        .byte   0x00                    # -IsBackChainStored, -IsFixup, NumOfFPRsSaved = 0
+        .byte   0x01                    # -HasExtensionTable, -HasVectorInfo, NumOfGPRsSaved = 1
+        .byte   0x00                    # NumberOfFixedParms = 0
+        .byte   0x01                    # NumberOfFPParms = 0, +HasParmsOnStack
+        .vbyte  4, L..abc0-.abc         # Function size
+        .vbyte  2, 0x0003               # Function name len = 3
+        .byte   "abc"                   # Function Name
+        .byte   0x1f                    # AllocaUsed
+                                        # -- End function
+        .csect badp[RW],2
+        .globl  badp[RW]                # @badp
+        .align  2
+        .vbyte  4, 0
+        .toc
+L..C0:
+        .tc badp[TC],badp[RW]
+#endif // __64BIT__
+#endif // CXX_CODE


        


More information about the cfe-commits mailing list