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

2024-08-13 Thread Michael Kolupaev via cfe-commits

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

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

>From f3e4787973daf1cd278a9615960bbb4b31d3c0c0 Mon Sep 17 00:00:00 2001
From: Michael Kolupaev 
Date: Tue, 13 Aug 2024 21:16:08 +
Subject: [PATCH] [libunwind][X86-64] Handle Linux sigreturn trampoline when
 DWARF info is missing

---
 libunwind/src/UnwindCursor.hpp | 96 +-
 1 file changed, 95 insertions(+), 1 deletion(-)

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 
 #include 
 #include 
@@ -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  bool setInfoForSigReturn(Registers &) {
 return false;
@@ -2917,6 +2921,96 @@ int UnwindCursor::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 
+bool UnwindCursor::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(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(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 
+int UnwindCursor::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.setI

[libunwind] [libunwind] Detect cycles of length 1 (PR #103476)

2024-08-13 Thread Michael Kolupaev via cfe-commits

https://github.com/al13n321 created 
https://github.com/llvm/llvm-project/pull/103476

If an unwind step leaves both the instruction pointer and the stack pointer 
unchanged, stop unwinding. Otherwise we'd be stuck in a loop producing the same 
stack frame over and over.

This happens with musl libc in __clone(), which has incorrect DWARF unwind 
information.

GDB also handles it using cycle detection, but it checks for cycles of any 
length rather than just length 1. That seems unnecessary here, at least until 
anyone encounters such cycles in practice.

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

>From cdac646034cf9f7bf6326bcd28a3626db8650ed7 Mon Sep 17 00:00:00 2001
From: Michael Kolupaev 
Date: Tue, 13 Aug 2024 21:31:30 +
Subject: [PATCH] [libunwind] Detect cycles of length 1

---
 libunwind/src/UnwindCursor.hpp | 11 +++
 1 file changed, 11 insertions(+)

diff --git a/libunwind/src/UnwindCursor.hpp b/libunwind/src/UnwindCursor.hpp
index ce6dced535e781..b6c51446cb7d47 100644
--- a/libunwind/src/UnwindCursor.hpp
+++ b/libunwind/src/UnwindCursor.hpp
@@ -2923,6 +2923,9 @@ template  int UnwindCursor::step(bool stage2) {
   if (_unwindInfoMissing)
 return UNW_STEP_END;
 
+  unw_word_t previousIP = getReg(UNW_REG_IP);
+  unw_word_t previousSP = getReg(UNW_REG_SP);
+
   // Use unwinding info to modify register set as if function returned.
   int result;
 #if defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN)
@@ -2951,6 +2954,14 @@ template  int UnwindCursor::step(bool stage2) {
 
   // update info based on new PC
   if (result == UNW_STEP_SUCCESS) {
+// Detect cycles of length 1. In particular this happens in musl's
+// __clone(), which has incorrect DWARF unwind information.
+// We don't check all registers, so it's not strictly guaranteed that
+// unwinding would be stuck in a cycle, but seems like a reasonable
+// heuristic.
+if (getReg(UNW_REG_SP) == previousSP && getReg(UNW_REG_IP) == previousIP)
+  return UNW_EBADFRAME;
+
 this->setInfoBasedOnIPRegister(true);
 if (_unwindInfoMissing)
   return UNW_STEP_END;

___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[libunwind] [libunwind] Detect cycles of length 1 (PR #103476)

2024-08-19 Thread Michael Kolupaev via cfe-commits

al13n321 wrote:

The `clone` syscall, in newly created thread, adjusts stack pointer and stack 
contents, and there's logically no "caller" stack frame and no return address 
on the stack. `tools/add-cfi.x86_64.awk` doesn't know about any of that and 
autogenerates cfi as if it's just normal linear code and the syscall does 
nothing special. Below the syscall the cfi is just all incorrect, at least from 
child thread's point of view. It just happens to have the property that ip and 
sp are left unchanged by the unwind step. (I don't remember the details, but 
it's something like: the `clone` syscall puts the entry function pointer 
somewhere near the top of the stack, and the cfi happens to point to that 
offset from sp despite having incorrect cfa.)

A principled fix would be to add manual cfi annotations in 
`musl/src/thread/x86_64/clone.s`. But I figured that since (1) gdb does cycle 
detection, and (2) it's still not fixed in musl; then this is considered 
acceptable, and maybe even intentional. (And, IIUC, musl people generally don't 
want programs to be able to unwind their own stack for debugging purposes. If I 
send a fix to musl, I imagine the answer may be "gdb already works, libunwind 
already doesn't work, and that's exactly how we want it", but may be worth a 
try.)

https://github.com/llvm/llvm-project/pull/103476
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[libunwind] [libunwind] Detect cycles of length 1 (PR #103476)

2024-08-19 Thread Michael Kolupaev via cfe-commits

al13n321 wrote:

Feel free to close this PR if such change is not going to be accepted.

https://github.com/llvm/llvm-project/pull/103476
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2025-02-21 Thread Michael Kolupaev via cfe-commits

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

>From 3beb6fc40ccc0b5726b360156063ef35e2b1db3f Mon Sep 17 00:00:00 2001
From: Michael Kolupaev 
Date: Tue, 13 Aug 2024 21:16:08 +
Subject: [PATCH] [libunwind][X86-64] Handle Linux sigreturn trampoline when
 DWARF info is missing

---
 libunwind/src/UnwindCursor.hpp | 100 -
 1 file changed, 99 insertions(+), 1 deletion(-)

diff --git a/libunwind/src/UnwindCursor.hpp b/libunwind/src/UnwindCursor.hpp
index ca9927edc9990..b4ddcd2792bf2 100644
--- a/libunwind/src/UnwindCursor.hpp
+++ b/libunwind/src/UnwindCursor.hpp
@@ -33,7 +33,8 @@
 #if defined(_LIBUNWIND_TARGET_LINUX) &&
\
 (defined(_LIBUNWIND_TARGET_AARCH64) || 
\
  defined(_LIBUNWIND_TARGET_LOONGARCH) ||   
\
- defined(_LIBUNWIND_TARGET_RISCV) || defined(_LIBUNWIND_TARGET_S390X))
+ defined(_LIBUNWIND_TARGET_RISCV) || defined(_LIBUNWIND_TARGET_S390X) ||   
\
+ defined(_LIBUNWIND_TARGET_S390X) || defined(_LIBUNWIND_TARGET_X86_64))
 #include 
 #include 
 #include 
@@ -1008,6 +1009,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  bool setInfoForSigReturn(Registers &) {
 return false;
@@ -3032,6 +3037,99 @@ int UnwindCursor::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 
+bool UnwindCursor::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(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(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 
+int UnwindCursor::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_SUCCE