diff --git a/libunwind/src/Registers.hpp b/libunwind/src/Registers.hpp --- a/libunwind/src/Registers.hpp +++ b/libunwind/src/Registers.hpp @@ -619,6 +619,8 @@ 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 @@ 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 --- a/libunwind/src/UnwindCursor.hpp +++ b/libunwind/src/UnwindCursor.hpp @@ -2301,27 +2301,39 @@ 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(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(TBTable), + reinterpret_cast(pc), + reinterpret_cast(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(registers.getSP()); + pint_t lastStack = *reinterpret_cast(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(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 @@ reinterpret_cast(lastStack)); sigcontext *sigContext = reinterpret_cast( - reinterpret_cast(lastStack) + STKMIN); + reinterpret_cast(lastStack) + STKMINALIGN); returnAddress = sigContext->sc_jmpbuf.jmp_context.iar; - _LIBUNWIND_TRACE_UNWINDING("From sigContext=%p, returnAddress=%p\n", - reinterpret_cast(sigContext), - reinterpret_cast(returnAddress)); - + bool useSTKMIN = false; if (returnAddress < 0x10000000) { - // Try again using STKMINALIGN + // Try again using STKMIN. sigContext = reinterpret_cast( - reinterpret_cast(lastStack) + STKMINALIGN); + reinterpret_cast(lastStack) + STKMIN); returnAddress = sigContext->sc_jmpbuf.jmp_context.iar; if (returnAddress < 0x10000000) { - _LIBUNWIND_TRACE_UNWINDING("Bad returnAddress=%p\n", - reinterpret_cast(returnAddress)); + _LIBUNWIND_TRACE_UNWINDING("Bad returnAddress=%p from sigcontext=%p", + reinterpret_cast(returnAddress), + reinterpret_cast(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(sigContext), - reinterpret_cast(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(sigContext), + reinterpret_cast(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(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 @@ } } else { // Step up a normal frame. - returnAddress = reinterpret_cast(lastStack)[2]; - _LIBUNWIND_TRACE_UNWINDING("Extract info from lastStack=%p, " - "returnAddress=%p\n", - reinterpret_cast(lastStack), - reinterpret_cast(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(returnAddress)); + } else { + // Otherwise, use the LR value in the stack link area. + returnAddress = reinterpret_cast(lastStack)[2]; + } + + // Reset LR in the current context. + newRegisters.setLR(NULL); + + _LIBUNWIND_TRACE_UNWINDING( + "Extract info from lastStack=%p, returnAddress=%p", + reinterpret_cast(lastStack), + reinterpret_cast(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 @@ struct vec_ext *vec_ext = reinterpret_cast(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 @@ // Do we need to set the TOC register? _LIBUNWIND_TRACE_UNWINDING( - "Current gpr2=%p\n", + "Current gpr2=%p", reinterpret_cast(newRegisters.getRegister(2))); if (firstInstruction == loadTOCRegInst) { _LIBUNWIND_TRACE_UNWINDING( - "Set gpr2=%p from frame\n", + "Set gpr2=%p from frame", reinterpret_cast(reinterpret_cast(lastStack)[5])); newRegisters.setRegister(2, reinterpret_cast(lastStack)[5]); } @@ -2516,7 +2549,6 @@ } 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 --- a/libunwind/src/UnwindRegistersSave.S +++ b/libunwind/src/UnwindRegistersSave.S @@ -340,7 +340,12 @@ 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) @@ -586,6 +591,11 @@ // 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 --- /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 +#include +#include +#include +#include +#include +#include + +#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(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(p + 1); + + if (!TBTable->tb.name_present) + return NULL; + + // Get to the optional portion of the traceback table. + p = reinterpret_cast(&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(p); + return reinterpret_cast(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 "inline.c","IBM Open XL C/C++ for AIX 17.1.2 (5725-C72, 5765-J18), version 17.1.2.0, LLVM version 18.0.0git" + .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 "inline.c","IBM Open XL C/C++ for AIX 17.1.2 (5725-C72, 5765-J18), version 17.1.2.0, LLVM version 18.0.0git" + .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