Author: Ryan Prichard Date: 2021-01-13T16:38:36-08:00 New Revision: c82deed6764cbc63966374baf9721331901ca958
URL: https://github.com/llvm/llvm-project/commit/c82deed6764cbc63966374baf9721331901ca958 DIFF: https://github.com/llvm/llvm-project/commit/c82deed6764cbc63966374baf9721331901ca958.diff LOG: [libunwind] Unwind through aarch64/Linux sigreturn frame An AArch64 sigreturn trampoline frame can't currently be described in a DWARF .eh_frame section, because the AArch64 DWARF spec currently doesn't define a constant for the PC register. (PC and LR may need to be restored to different values.) Instead, use the same technique as libgcc or github.com/libunwind and detect the sigreturn frame by looking for the sigreturn instructions: mov x8, #0x8b svc #0x0 If a sigreturn frame is detected, libunwind restores all the GPRs by assuming that sp points at an rt_sigframe Linux kernel struct. This behavior is a fallback mode that is only used if there is no ordinary unwind info for sigreturn. If libunwind can't find unwind info for a PC, it assumes that the PC is readable, and would crash if it isn't. This could happen if: - The PC points at a function compiled without unwind info, and which is part of an execute-only mapping (e.g. using -Wl,--execute-only). - The PC is invalid and happens to point to unreadable or unmapped memory. In the tests, ignore a failed dladdr call so that the tests can run on user-mode qemu for AArch64, which uses a stack-allocated trampoline instead of a vDSO. Reviewed By: danielkiss, compnerd, #libunwind Differential Revision: https://reviews.llvm.org/D90898 Added: Modified: libunwind/include/__libunwind_config.h libunwind/src/UnwindCursor.hpp libunwind/test/signal_unwind.pass.cpp libunwind/test/unwind_leaffunction.pass.cpp Removed: ################################################################################ diff --git a/libunwind/include/__libunwind_config.h b/libunwind/include/__libunwind_config.h index 71d77ca65118..80be357496c4 100644 --- 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 index 9f8fa65107b4..e537ed84dd93 100644 --- a/libunwind/src/UnwindCursor.hpp +++ b/libunwind/src/UnwindCursor.hpp @@ -925,6 +925,25 @@ class UnwindCursor : public AbstractUnwindCursor{ } #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 <typename Registers> bool setInfoForSigReturn(Registers &) { + return false; + } + template <typename Registers> int stepThroughSigReturn(Registers &) { + return UNW_STEP_END; + } +#endif + #if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) bool getInfoFromFdeCie(const typename CFI_Parser<A>::FDE_Info &fdeInfo, const typename CFI_Parser<A>::CIE_Info &cieInfo, @@ -1179,6 +1198,9 @@ class UnwindCursor : public AbstractUnwindCursor{ unw_proc_info_t _info; bool _unwindInfoMissing; bool _isSignalFrame; +#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) + bool _isSigReturn = false; +#endif }; @@ -1873,7 +1895,11 @@ bool UnwindCursor<A, R>::getInfoFromSEH(pint_t pc) { template <typename A, typename R> void UnwindCursor<A, R>::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<pint_t>(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 +1997,77 @@ void UnwindCursor<A, R>::setInfoBasedOnIPRegister(bool isReturnAddress) { } #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 <typename A, typename R> +bool UnwindCursor<A, R>::setInfoForSigReturn(Registers_arm64 &) { + // Look for the sigreturn trampoline. The trampoline's body is two + // specific instructions (see below). Typically the trampoline comes from the + // vDSO[1] (i.e. the __kernel_rt_sigreturn function). A libc might provide its + // own restorer function, though, or user-mode QEMU might write a trampoline + // onto the stack. + // + // 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.: + // - The PC points at a function compiled without unwind info, and which is + // part of an execute-only mapping (e.g. using -Wl,--execute-only). + // - The PC is invalid and happens to point to unreadable or unmapped memory. + // + // [1] https://github.com/torvalds/linux/blob/master/arch/arm64/kernel/vdso/sigreturn.S + const pint_t pc = static_cast<pint_t>(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 <typename A, typename R> +int UnwindCursor<A, R>::stepThroughSigReturn(Registers_arm64 &) { + // In the signal trampoline frame, sp points to an rt_sigframe[1], which is: + // - 128-byte siginfo struct + // - ucontext struct: + // - 8-byte long (uc_flags) + // - 8-byte pointer (uc_link) + // - 24-byte stack_t + // - 128-byte signal set + // - 8 bytes of padding because sigcontext has 16-byte alignment + // - sigcontext/mcontext_t + // [1] https://github.com/torvalds/linux/blob/master/arch/arm64/kernel/signal.c + 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<pint_t>(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 <typename A, typename R> int UnwindCursor<A, R>::step() { // Bottom of stack is defined is when unwind info cannot be found. @@ -1983,20 +2076,27 @@ int UnwindCursor<A, R>::step() { // 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 index 25ff0bbfc509..3acd77209a1c 100644 --- 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 <assert.h> #include <dlfcn.h> @@ -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(reinterpret_cast<void *>(_Unwind_GetIP(ctx)), &info) && + info.dli_sname && !strcmp("main", info.dli_sname)) { _Exit(0); } return _URC_NO_REASON; diff --git a/libunwind/test/unwind_leaffunction.pass.cpp b/libunwind/test/unwind_leaffunction.pass.cpp index 2b08a16e5fef..725a29163e50 100644 --- a/libunwind/test/unwind_leaffunction.pass.cpp +++ b/libunwind/test/unwind_leaffunction.pass.cpp @@ -8,7 +8,7 @@ //===----------------------------------------------------------------------===// // Ensure that leaf function can be unwund. -// REQUIRES: x86_64-linux +// REQUIRES: linux && (target-aarch64 || target-x86_64) #include <assert.h> #include <dlfcn.h> @@ -23,10 +23,10 @@ _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)) { + if (dladdr(reinterpret_cast<void *>(_Unwind_GetIP(ctx)), &info) && + info.dli_sname && !strcmp("main", info.dli_sname)) { _Exit(0); } return _URC_NO_REASON; _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits