Skip to content

Handle x64 linux signal trampoline without relying on DWARF unwind info #32

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 1 commit into from
Aug 10, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 95 additions & 1 deletion src/UnwindCursor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

#if defined(_LIBUNWIND_TARGET_LINUX) && \
(defined(_LIBUNWIND_TARGET_AARCH64) || defined(_LIBUNWIND_TARGET_RISCV) || \
defined(_LIBUNWIND_TARGET_S390X))
defined(_LIBUNWIND_TARGET_S390X) || defined(_LIBUNWIND_TARGET_X86_64))
#include <errno.h>
#include <signal.h>
#include <sys/syscall.h>
Expand Down Expand Up @@ -1004,6 +1004,10 @@ class UnwindCursor : public AbstractUnwindCursor{
#if defined(_LIBUNWIND_TARGET_S390X)
bool setInfoForSigReturn(Registers_s390x &);
int stepThroughSigReturn(Registers_s390x &);
#endif
#if defined(_LIBUNWIND_TARGET_X86_64)
bool setInfoForSigReturn(Registers_x86_64 &);
int stepThroughSigReturn(Registers_x86_64 &);
#endif
template <typename Registers> bool setInfoForSigReturn(Registers &) {
return false;
Expand Down Expand Up @@ -2932,6 +2936,96 @@ int UnwindCursor<A, R>::stepThroughSigReturn(Registers_s390x &) {
#endif // defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) &&
// defined(_LIBUNWIND_TARGET_S390X)

#if defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) && \
defined(_LIBUNWIND_TARGET_X86_64)
template <typename A, typename R>
bool UnwindCursor<A, R>::setInfoForSigReturn(Registers_x86_64 &) {
// Look for the sigreturn trampoline. The trampoline's body is two
// specific instructions (see below). Typically the trampoline comes from the
// vDSO or from libc.
//
// This special code path is a fallback that is only used if the trampoline
// lacks proper (e.g. DWARF) unwind info.
const uint8_t amd64_linux_sigtramp_code[9] = {
0x48, 0xc7, 0xc0, 0x0f, 0x00, 0x00, 0x00, // mov rax, 15
0x0f, 0x05 // syscall
};
const size_t code_size = sizeof(amd64_linux_sigtramp_code);

// The PC might contain an invalid address if the unwind info is bad, so
// directly accessing it could cause a SIGSEGV.
unw_word_t pc = static_cast<pint_t>(this->getReg(UNW_REG_IP));
if (!isReadableAddr(pc))
return false;
// If near page boundary, check the next page too.
if (((pc + code_size - 1) & 4095) != (pc & 4095) && !isReadableAddr(pc + code_size - 1))
return false;

const uint8_t *pc_ptr = reinterpret_cast<const uint8_t *>(pc);
if (memcmp(pc_ptr, amd64_linux_sigtramp_code, code_size))
return false;

_info = {};
_info.start_ip = pc;
_info.end_ip = pc + code_size;
_isSigReturn = true;
return true;
}

template <typename A, typename R>
int UnwindCursor<A, R>::stepThroughSigReturn(Registers_x86_64 &) {
// In the signal trampoline frame, sp points to ucontext:
// struct ucontext {
// unsigned long uc_flags;
// struct ucontext *uc_link;
// stack_t uc_stack; // 24 bytes
// struct sigcontext uc_mcontext;
// ...
// };
const pint_t kOffsetSpToSigcontext = (8 + 8 + 24);
pint_t sigctx = _registers.getSP() + kOffsetSpToSigcontext;

// UNW_X86_64_* -> field in struct sigcontext_64.
// struct sigcontext_64 {
// __u64 r8; // 0
// __u64 r9; // 1
// __u64 r10; // 2
// __u64 r11; // 3
// __u64 r12; // 4
// __u64 r13; // 5
// __u64 r14; // 6
// __u64 r15; // 7
// __u64 di; // 8
// __u64 si; // 9
// __u64 bp; // 10
// __u64 bx; // 11
// __u64 dx; // 12
// __u64 ax; // 13
// __u64 cx; // 14
// __u64 sp; // 15
// __u64 ip; // 16
// ...
// };
const size_t idx_map[17] = {13, 12, 14, 11, 9, 8, 10, 15, 0, 1, 2, 3, 4, 5, 6, 7, 16};

for (int i = 0; i < 17; ++i) {
uint64_t value = _addressSpace.get64(sigctx + idx_map[i] * 8);
_registers.setRegister(i, value);
}

// The +1 story is the same as in DwarfInstructions::stepWithDwarf()
// (search for "returnAddress + cieInfo.isSignalFrame" or "Return address points to the next instruction").
// This is probably not the right place for this because this function is not necessarily used
// with DWARF. Need to research whether the other unwind methods have the same +-1 situation or
// are off by one.
_registers.setIP(_registers.getIP() + 1);

_isSignalFrame = true;
return UNW_STEP_SUCCESS;
}
#endif // defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) &&
// defined(_LIBUNWIND_TARGET_X86_64)

template <typename A, typename R> int UnwindCursor<A, R>::step(bool stage2) {
(void)stage2;
// Bottom of stack is defined is when unwind info cannot be found.
Expand Down