[libunwind] [libunwind][X86-64] Handle Linux sigreturn trampoline when DWARF info is missing (PR #103473)

via cfe-commits cfe-commits at lists.llvm.org
Tue Aug 13 14:27:16 PDT 2024


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libunwind

Author: Michael Kolupaev (al13n321)

<details>
<summary>Changes</summary>

Do for X86-64 what libunwind already does for AArch64, RISC-V, and S390X. GDB does this too.

Useful for musl libc, which doesn't have DWARF unwind info for `__restore_rt` trampoline, so libunwind couldn't unwind out of signal handlers. Now it can.

Tested manually in https://github.com/ClickHouse/libunwind/pull/32

---
Full diff: https://github.com/llvm/llvm-project/pull/103473.diff


1 Files Affected:

- (modified) libunwind/src/UnwindCursor.hpp (+95-1) 


``````````diff
diff --git a/libunwind/src/UnwindCursor.hpp b/libunwind/src/UnwindCursor.hpp
index ce6dced535e781..02cbd9891e4ff5 100644
--- a/libunwind/src/UnwindCursor.hpp
+++ b/libunwind/src/UnwindCursor.hpp
@@ -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>
@@ -1003,6 +1003,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;
@@ -2917,6 +2921,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.

``````````

</details>


https://github.com/llvm/llvm-project/pull/103473


More information about the cfe-commits mailing list