diff --git a/libunwind/include/__libunwind_config.h b/libunwind/include/__libunwind_config.h --- a/libunwind/include/__libunwind_config.h +++ b/libunwind/include/__libunwind_config.h @@ -27,6 +27,9 @@ #define _LIBUNWIND_HIGHEST_DWARF_REGISTER_RISCV 64 #if defined(_LIBUNWIND_IS_NATIVE_ONLY) +# if defined(__linux__) +# define _LIBUNWIND_TARGET_LINUX 1 +# endif # if defined(__i386__) # define _LIBUNWIND_TARGET_I386 # define _LIBUNWIND_CONTEXT_SIZE 8 diff --git a/libunwind/src/UnwindCursor.hpp b/libunwind/src/UnwindCursor.hpp --- a/libunwind/src/UnwindCursor.hpp +++ b/libunwind/src/UnwindCursor.hpp @@ -925,6 +925,21 @@ } #endif +#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) + bool setInfoForSigReturn() { + R dummy; + return setInfoForSigReturn(dummy); + } + int stepThroughSigReturn() { + R dummy; + return stepThroughSigReturn(dummy); + } + bool setInfoForSigReturn(Registers_arm64 &); + int stepThroughSigReturn(Registers_arm64 &); + template bool setInfoForSigReturn(R2 &) { return false; } + template int stepThroughSigReturn(R2 &) { return UNW_STEP_END; } +#endif + #if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) bool getInfoFromFdeCie(const typename CFI_Parser::FDE_Info &fdeInfo, const typename CFI_Parser::CIE_Info &cieInfo, @@ -1179,6 +1194,9 @@ unw_proc_info_t _info; bool _unwindInfoMissing; bool _isSignalFrame; +#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) + bool _isSigReturn = false; +#endif }; @@ -1873,7 +1891,11 @@ template void UnwindCursor::setInfoBasedOnIPRegister(bool isReturnAddress) { - pint_t pc = (pint_t)this->getReg(UNW_REG_IP); +#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) + _isSigReturn = false; +#endif + + pint_t pc = static_cast(this->getReg(UNW_REG_IP)); #if defined(_LIBUNWIND_ARM_EHABI) // Remove the thumb bit so the IP represents the actual instruction address. // This matches the behaviour of _Unwind_GetIP on arm. @@ -1971,10 +1993,67 @@ } #endif // #if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) +#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) + if (setInfoForSigReturn()) + return; +#endif + // no unwind info, flag that we can't reliably unwind _unwindInfoMissing = true; } +#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) +template +bool UnwindCursor::setInfoForSigReturn(Registers_arm64 &) { + // Look for the __kernel_rt_sigreturn trampoline. This special code path is a + // fallback that is only used if the trampoline lacks proper (e.g. DWARF) + // unwind info. On AArch64, a new DWARF register constant for the PC needs to + // be defined before DWARF can handle a signal trampoline. This code may + // segfault if the target PC is unreadable (e.g. because of + // execute-only-memory or an invalid PC). + const pint_t pc = static_cast(this->getReg(UNW_REG_IP)); + // Look for instructions: mov x8, #0x8b; svc #0x0 + if (_addressSpace.get32(pc) == 0xd2801168 && + _addressSpace.get32(pc + 4) == 0xd4000001) { + _info = {}; + _isSigReturn = true; + return true; + } + return false; +} + +template +int UnwindCursor::stepThroughSigReturn(Registers_arm64 &) { + // In the signal trampoline frame, sp points to an rt_sigframe, which is: + // - 128-byte siginfo_t + // - ucontext_t: + // - 8-byte long + // - 8-byte pointer + // - 24-byte stack_t + // - 128-byte signal set + // - 8 bytes of padding because sigcontext has 16-byte alignment + // - mcontext_t/sigcontext + const pint_t kOffsetSpToSigcontext = (128 + 8 + 8 + 24 + 128 + 8); // 304 + + // Offsets from sigcontext to each register. + const pint_t kOffsetGprs = 8; // offset to "__u64 regs[31]" field + const pint_t kOffsetSp = 256; // offset to "__u64 sp" field + const pint_t kOffsetPc = 264; // offset to "__u64 pc" field + + pint_t sigctx = _registers.getSP() + kOffsetSpToSigcontext; + + for (int i = 0; i <= 30; ++i) { + uint64_t value = _addressSpace.get64(sigctx + kOffsetGprs + + static_cast(i * 8)); + _registers.setRegister(UNW_ARM64_X0 + i, value); + } + _registers.setSP(_addressSpace.get64(sigctx + kOffsetSp)); + _registers.setIP(_addressSpace.get64(sigctx + kOffsetPc)); + _isSignalFrame = true; + return UNW_STEP_SUCCESS; +} +#endif // defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) + template int UnwindCursor::step() { // Bottom of stack is defined is when unwind info cannot be found. @@ -1983,20 +2062,27 @@ // Use unwinding info to modify register set as if function returned. int result; +#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) + if (_isSigReturn) { + result = this->stepThroughSigReturn(); + } else +#endif + { #if defined(_LIBUNWIND_SUPPORT_COMPACT_UNWIND) - result = this->stepWithCompactEncoding(); + result = this->stepWithCompactEncoding(); #elif defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) - result = this->stepWithSEHData(); + result = this->stepWithSEHData(); #elif defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) - result = this->stepWithDwarfFDE(); + result = this->stepWithDwarfFDE(); #elif defined(_LIBUNWIND_ARM_EHABI) - result = this->stepWithEHABI(); + result = this->stepWithEHABI(); #else #error Need _LIBUNWIND_SUPPORT_COMPACT_UNWIND or \ _LIBUNWIND_SUPPORT_SEH_UNWIND or \ _LIBUNWIND_SUPPORT_DWARF_UNWIND or \ _LIBUNWIND_ARM_EHABI #endif + } // update info based on new PC if (result == UNW_STEP_SUCCESS) { diff --git a/libunwind/test/signal_unwind.pass.cpp b/libunwind/test/signal_unwind.pass.cpp --- a/libunwind/test/signal_unwind.pass.cpp +++ b/libunwind/test/signal_unwind.pass.cpp @@ -8,7 +8,7 @@ //===----------------------------------------------------------------------===// // Ensure that the unwinder can cope with the signal handler. -// REQUIRES: x86_64-linux +// REQUIRES: linux && (target-aarch64 || target-x86_64) #include #include @@ -23,10 +23,11 @@ _Unwind_Reason_Code frame_handler(struct _Unwind_Context* ctx, void* arg) { (void)arg; Dl_info info = { 0, 0, 0, 0 }; - assert(dladdr((void*)_Unwind_GetIP(ctx), &info)); - // Unwind util the main is reached, above frames deeped on the platfrom and architecture. - if(info.dli_sname && !strcmp("main", info.dli_sname)) { + // Unwind util the main is reached, above frames depend on the platform and + // architecture. + if (dladdr((void *)_Unwind_GetIP(ctx), &info) && info.dli_sname && + !strcmp("main", info.dli_sname)) { _Exit(0); } return _URC_NO_REASON;