Changeset View
Standalone View
libunwind/src/UnwindCursor.hpp
Show First 20 Lines • Show All 919 Lines • ▼ Show 20 Lines | const uint32_t *ehtp = | ||||
&off, &len); | &off, &len); | ||||
if (_Unwind_VRS_Interpret((_Unwind_Context *)this, ehtp, off, len) != | if (_Unwind_VRS_Interpret((_Unwind_Context *)this, ehtp, off, len) != | ||||
_URC_CONTINUE_UNWIND) | _URC_CONTINUE_UNWIND) | ||||
return UNW_STEP_END; | return UNW_STEP_END; | ||||
return UNW_STEP_SUCCESS; | return UNW_STEP_SUCCESS; | ||||
} | } | ||||
#endif | #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; | |||||
compnerd: What do you think of `Registers` instead of `R2`? | |||||
Both R and Registers would be in scope and would be the names of Registers_* classes, but that seems OK to me. rprichard: Both `R` and `Registers` would be in scope and would be the names of `Registers_*` classes, but… | |||||
} | |||||
template <typename Registers> int stepThroughSigReturn(Registers &) { | |||||
return UNW_STEP_END; | |||||
} | |||||
#endif | |||||
#if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) | #if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) | ||||
bool getInfoFromFdeCie(const typename CFI_Parser<A>::FDE_Info &fdeInfo, | bool getInfoFromFdeCie(const typename CFI_Parser<A>::FDE_Info &fdeInfo, | ||||
const typename CFI_Parser<A>::CIE_Info &cieInfo, | const typename CFI_Parser<A>::CIE_Info &cieInfo, | ||||
pint_t pc, uintptr_t dso_base); | pint_t pc, uintptr_t dso_base); | ||||
bool getInfoFromDwarfSection(pint_t pc, const UnwindInfoSections §s, | bool getInfoFromDwarfSection(pint_t pc, const UnwindInfoSections §s, | ||||
uint32_t fdeSectionOffsetHint=0); | uint32_t fdeSectionOffsetHint=0); | ||||
int stepWithDwarfFDE() { | int stepWithDwarfFDE() { | ||||
return DwarfInstructions<A, R>::stepWithDwarf(_addressSpace, | return DwarfInstructions<A, R>::stepWithDwarf(_addressSpace, | ||||
▲ Show 20 Lines • Show All 238 Lines • ▼ Show 20 Lines | |||||
#endif // defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) | #endif // defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) | ||||
A &_addressSpace; | A &_addressSpace; | ||||
R _registers; | R _registers; | ||||
unw_proc_info_t _info; | unw_proc_info_t _info; | ||||
bool _unwindInfoMissing; | bool _unwindInfoMissing; | ||||
bool _isSignalFrame; | bool _isSignalFrame; | ||||
#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) | |||||
bool _isSigReturn = false; | |||||
#endif | |||||
}; | }; | ||||
template <typename A, typename R> | template <typename A, typename R> | ||||
UnwindCursor<A, R>::UnwindCursor(unw_context_t *context, A &as) | UnwindCursor<A, R>::UnwindCursor(unw_context_t *context, A &as) | ||||
: _addressSpace(as), _registers(context), _unwindInfoMissing(false), | : _addressSpace(as), _registers(context), _unwindInfoMissing(false), | ||||
_isSignalFrame(false) { | _isSignalFrame(false) { | ||||
static_assert((check_fit<UnwindCursor<A, R>, unw_cursor_t>::does_fit), | static_assert((check_fit<UnwindCursor<A, R>, unw_cursor_t>::does_fit), | ||||
▲ Show 20 Lines • Show All 678 Lines • ▼ Show 20 Lines | #endif | ||||
setLastPC(pc); | setLastPC(pc); | ||||
return true; | return true; | ||||
} | } | ||||
#endif | #endif | ||||
template <typename A, typename R> | template <typename A, typename R> | ||||
void UnwindCursor<A, R>::setInfoBasedOnIPRegister(bool isReturnAddress) { | 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) | #if defined(_LIBUNWIND_ARM_EHABI) | ||||
// Remove the thumb bit so the IP represents the actual instruction address. | // Remove the thumb bit so the IP represents the actual instruction address. | ||||
// This matches the behaviour of _Unwind_GetIP on arm. | // This matches the behaviour of _Unwind_GetIP on arm. | ||||
pc &= (pint_t)~0x1; | pc &= (pint_t)~0x1; | ||||
#endif | #endif | ||||
// Exit early if at the top of the stack. | // Exit early if at the top of the stack. | ||||
if (pc == 0) { | if (pc == 0) { | ||||
▲ Show 20 Lines • Show All 81 Lines • ▼ Show 20 Lines | if (!CFI_Parser<A>::decodeFDE(_addressSpace, fde, &fdeInfo, &cieInfo)) { | ||||
// Double check this FDE is for a function that includes the pc. | // Double check this FDE is for a function that includes the pc. | ||||
if ((fdeInfo.pcStart <= pc) && (pc < fdeInfo.pcEnd)) | if ((fdeInfo.pcStart <= pc) && (pc < fdeInfo.pcEnd)) | ||||
if (getInfoFromFdeCie(fdeInfo, cieInfo, pc, 0)) | if (getInfoFromFdeCie(fdeInfo, cieInfo, pc, 0)) | ||||
return; | return; | ||||
} | } | ||||
} | } | ||||
#endif // #if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) | #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 | // no unwind info, flag that we can't reliably unwind | ||||
_unwindInfoMissing = true; | _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 | |||||
Not Done ReplyInline ActionsIt really would be amazing to have a link to the VDSO code here to make it easier to understand the check - I figured it was that this is the implementation, but had to chase through the files. compnerd: It really would be amazing to have a link to the VDSO code here to make it easier to understand… | |||||
I think the vDSO is the most canonical implementation -- Bionic uses it, and it looks like glibc also does. It's not universal, though. e.g.:
FWIW, Bionic provides its own restorer function on arm/x86/x86_64 and only uses the kernel's restorer for arm64. This patch is sufficient to fix unwinding through a signal handler for Bionic, but not other targets. e.g. The situations above, but I think x86(-32) with glibc is also affected if there is no vDSO. I think I can add a link to the vDSO, probably to: rprichard: I think [the vDSO](https://github.com/torvalds/linux/blob/659caaf65dc9c7150aa3e80225ec6e66b25ab… | |||||
Not Done ReplyInline ActionsVDSO is an elf, so we could do something like this: void* handle = dlopen("linux-vdso.so.1", RTLD_LAZY); const pint_t ptr = (const pint_t)dlsym(handle, "__kernel_rt_sigreturn"); if( pc == ptr ) ... This could help with the XOM issue too. danielkiss: VDSO is an elf, so we could do something like this:
```
void* handle = dlopen("linux-vdso.so. | |||||
It looks like dlopen("linux-vdso.so.1", ...) does find the vDSO with Bionic and glibc, but not with musl. (I wondered about Bionic, because Bionic's dl_iterate_phdr reports the name of the vDSO as [vdso] rather than linux-vdso.so.1. However, when I tested, it looks like Bionic's dlopen can open linux-vdso.so but not [vdso].) This approach might have trouble with static linking? For statically-linked Bionic programs, a special limited dl_iterate_phdr is provided in libc.a, but there is no dlopen linked in by default. There is a stub dlopen in libdl.a, but the stub just returns nullptr. For glibc, dlopen also requires linking with -ldl, and then ld.bfd/ld.gold apparently print a warning for static executables: /tmp/test-f61321.o:test.c:function main: warning: Using 'dlopen' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking One approach I thought of: change findUnwindSections so that it reports a readable range of memory containing the lookup PC. I think this would break with qemu user-mode emulation but would work in most other configurations. (e.g. A static musl executable doesn't look like it shows the vDSO via dl_iterate_phdr, but that's OK if musl provides its own sigreturn function.) rprichard: It looks like dlopen("linux-vdso.so.1", ...) does find the vDSO with Bionic and glibc, but not… | |||||
// 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> | template <typename A, typename R> | ||||
int UnwindCursor<A, R>::step() { | int UnwindCursor<A, R>::step() { | ||||
// Bottom of stack is defined is when unwind info cannot be found. | // Bottom of stack is defined is when unwind info cannot be found. | ||||
if (_unwindInfoMissing) | if (_unwindInfoMissing) | ||||
return UNW_STEP_END; | return UNW_STEP_END; | ||||
// Use unwinding info to modify register set as if function returned. | // Use unwinding info to modify register set as if function returned. | ||||
int result; | int result; | ||||
#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) | |||||
if (_isSigReturn) { | |||||
result = this->stepThroughSigReturn(); | |||||
} else | |||||
#endif | |||||
{ | |||||
#if defined(_LIBUNWIND_SUPPORT_COMPACT_UNWIND) | #if defined(_LIBUNWIND_SUPPORT_COMPACT_UNWIND) | ||||
result = this->stepWithCompactEncoding(); | result = this->stepWithCompactEncoding(); | ||||
#elif defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) | #elif defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) | ||||
result = this->stepWithSEHData(); | result = this->stepWithSEHData(); | ||||
#elif defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) | #elif defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) | ||||
result = this->stepWithDwarfFDE(); | result = this->stepWithDwarfFDE(); | ||||
#elif defined(_LIBUNWIND_ARM_EHABI) | #elif defined(_LIBUNWIND_ARM_EHABI) | ||||
result = this->stepWithEHABI(); | result = this->stepWithEHABI(); | ||||
#else | #else | ||||
#error Need _LIBUNWIND_SUPPORT_COMPACT_UNWIND or \ | #error Need _LIBUNWIND_SUPPORT_COMPACT_UNWIND or \ | ||||
_LIBUNWIND_SUPPORT_SEH_UNWIND or \ | _LIBUNWIND_SUPPORT_SEH_UNWIND or \ | ||||
_LIBUNWIND_SUPPORT_DWARF_UNWIND or \ | _LIBUNWIND_SUPPORT_DWARF_UNWIND or \ | ||||
_LIBUNWIND_ARM_EHABI | _LIBUNWIND_ARM_EHABI | ||||
#endif | #endif | ||||
} | |||||
// update info based on new PC | // update info based on new PC | ||||
if (result == UNW_STEP_SUCCESS) { | if (result == UNW_STEP_SUCCESS) { | ||||
this->setInfoBasedOnIPRegister(true); | this->setInfoBasedOnIPRegister(true); | ||||
if (_unwindInfoMissing) | if (_unwindInfoMissing) | ||||
return UNW_STEP_END; | return UNW_STEP_END; | ||||
} | } | ||||
Show All 21 Lines |
What do you think of Registers instead of R2?