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 @@ -2358,6 +2358,14 @@ // 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\n", + 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,7 +2388,26 @@ } } else { // Step up a normal frame. - returnAddress = reinterpret_cast(lastStack)[2]; + + bool usedLRFromSigContext; + if (!lastStack) { + returnAddress = NULL; + usedLRFromSigContext = false; + } else if (!TBTable->tb.saves_lr && registers.getLR()) { + // Use the LR value saved from sigcontext if LR is not saved in the + // stack frame. + returnAddress = registers.getLR(); + usedLRFromSigContext = true; + _LIBUNWIND_TRACE_UNWINDING("Use saved LR=%p\n", + reinterpret_cast(returnAddress)); + } else { + // Otherwise, use the LR value in the stack link area. + returnAddress = reinterpret_cast(lastStack)[2]; + usedLRFromSigContext = false; + } + + // Reset LR in the current context. + newRegisters.setLR(NULL); _LIBUNWIND_TRACE_UNWINDING("Extract info from lastStack=%p, " "returnAddress=%p\n", @@ -2472,8 +2499,10 @@ *(reinterpret_cast(lastStack + sizeof(uintptr_t)))); } - // Restore the SP. - newRegisters.setSP(lastStack); + // Restore the SP and move one frame up except when the LR saved from + // sigcontext is used as the return address. + if (!usedLRFromSigContext) + newRegisters.setSP(lastStack); // The first instruction after return. uint32_t firstInstruction = *(reinterpret_cast(returnAddress)); @@ -2516,7 +2545,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.cpp b/libunwind/test/aix_signal_unwind.pass.cpp new file mode 100644 --- /dev/null +++ b/libunwind/test/aix_signal_unwind.pass.cpp @@ -0,0 +1,123 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// 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 from a signal handler and produces +// a correct traceback when the function raising the signal is a leaf function +// in the middle of the call chain. + +// REQUIRES: target=powerpc{{(64)?}}-ibm-aix + +#undef NDEBUG +#include +#include +#include +#include +#include +#include +#include + +#define FUNC_ARRAY_SIZE 10 + +struct func_t { + const char *name1; // Function name obtained in the function. + char *name2; // Function name obtained during unwind. +} func[FUNC_ARRAY_SIZE]; + +int funcIndex = 0; +int lastFuncIndex = 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; + if (funcIndex >= 0) { + uint16_t nameLen; + uintptr_t ip = _Unwind_GetIP(uc); + func[funcIndex--].name2 = strndup(getFuncName(ip, &nameLen), nameLen); + } + return _URC_NO_REASON; +} + +extern "C" void handler(int signum) { + (void)signum; + func[funcIndex].name1 = __func__; + lastFuncIndex = funcIndex; + + // Walk stack frames for traceback. + _Unwind_Backtrace(callBack, NULL); + + // Verify the traceback. + for (int i = 0; i <= lastFuncIndex; ++i) { + assert(!strcmp(func[i].name1, func[i].name2) && + "Function names do not match"); + free(func[i].name2); + } + exit(0); +} + +volatile int *null = 0; + +// abc() is a leaf function that raises signal SIGSEGV. +extern "C" __attribute__((noinline)) void abc() { + func[funcIndex++].name1 = __func__; + // Produce a SIGSEGV. + *null = 0; +} + +extern "C" __attribute__((noinline)) void bar() { + func[funcIndex++].name1 = __func__; + abc(); +} + +extern "C" __attribute__((noinline)) void foo() { + func[funcIndex++].name1 = __func__; + bar(); +} + +int main() { + // Set signal handler for SIGSEGV. + signal(SIGSEGV, handler); + + func[funcIndex++].name1 = __func__; + foo(); +}