[libunwind] 4d7292e - [libunwind] Unwind through AArch64 FreeBSD signal trampolines (#235065)

via cfe-commits cfe-commits at lists.llvm.org
Sat Oct 24 18:02:55 PDT 2026


Author: Minsoo Choo
Date: 2026-10-24T21:02:50-04:00
New Revision: 4d7292e58078f0d6acad8eddc8f7c4b80ae40f74

URL: https://github.com/llvm/llvm-project/commit/4d7292e58078f0d6acad8eddc8f7c4b80ae40f74
DIFF: https://github.com/llvm/llvm-project/commit/4d7292e58078f0d6acad8eddc8f7c4b80ae40f74.diff

LOG: [libunwind] Unwind through AArch64 FreeBSD signal trampolines (#235065)

Retried from https://reviews.llvm.org/D155066.

Recognize signal trampolines using KERN_PROC_SIGTRAMP when unwind
metadata is unavailable. Restore the interrupted PC, LR, SP, general
registers, and valid floating-point registers.

Add coverage for unwinding out of an interrupted leaf function on both
regular and alternate signal stacks.

Assisted-by: GPT

Added: 
    libunwind/test/aarch64_freebsd_signal_unwind.pass.cpp

Modified: 
    libunwind/include/__libunwind_config.h
    libunwind/src/UnwindCursor.hpp
    libunwind/test/aarch64_za_unwind.pass.cpp

Removed: 
    


################################################################################
diff  --git a/libunwind/include/__libunwind_config.h b/libunwind/include/__libunwind_config.h
index 0c7aed13375a14..60ab1bdc2cf757 100644
--- a/libunwind/include/__libunwind_config.h
+++ b/libunwind/include/__libunwind_config.h
@@ -36,6 +36,9 @@
 # if defined(__linux__)
 #  define _LIBUNWIND_TARGET_LINUX 1
 # endif
+# if defined(__FreeBSD__)
+#  define _LIBUNWIND_TARGET_FREEBSD 1
+# endif
 # if defined(__HAIKU__)
 #  define _LIBUNWIND_TARGET_HAIKU 1
 # endif

diff  --git a/libunwind/src/UnwindCursor.hpp b/libunwind/src/UnwindCursor.hpp
index f307190addadac..dfd5b2b19e1b48 100644
--- a/libunwind/src/UnwindCursor.hpp
+++ b/libunwind/src/UnwindCursor.hpp
@@ -41,6 +41,17 @@
 #define _LIBUNWIND_CHECK_LINUX_SIGRETURN 1
 #endif
 
+#if defined(_LIBUNWIND_TARGET_FREEBSD) && defined(_LIBUNWIND_TARGET_AARCH64)
+#include <errno.h>
+#include <machine/frame.h>
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/ucontext.h>
+#include <sys/user.h>
+#include <unistd.h>
+#define _LIBUNWIND_CHECK_FREEBSD_SIGRETURN 1
+#endif
+
 #if defined(_LIBUNWIND_TARGET_HAIKU) &&                                        \
     (defined(_LIBUNWIND_TARGET_I386) || defined(_LIBUNWIND_TARGET_X86_64))
 #include <OS.h>
@@ -1055,7 +1066,8 @@ class UnwindCursor : public AbstractUnwindCursor {
   template <typename Registers> int stepThroughSigReturn(Registers &) {
     return UNW_STEP_END;
   }
-#elif defined(_LIBUNWIND_CHECK_HAIKU_SIGRETURN)
+#elif defined(_LIBUNWIND_CHECK_FREEBSD_SIGRETURN) ||                           \
+    defined(_LIBUNWIND_CHECK_HAIKU_SIGRETURN)
   bool setInfoForSigReturn();
   int stepThroughSigReturn();
 #endif
@@ -1377,6 +1389,7 @@ class UnwindCursor : public AbstractUnwindCursor {
   bool             _unwindInfoMissing;
   bool             _isSignalFrame;
 #if defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) ||                               \
+    defined(_LIBUNWIND_CHECK_FREEBSD_SIGRETURN) ||                             \
     defined(_LIBUNWIND_CHECK_HAIKU_SIGRETURN)
   bool             _isSigReturn = false;
 #endif
@@ -2890,6 +2903,7 @@ int UnwindCursor<A, R>::stepWithTBTable(pint_t pc, tbtable *TBTable,
 template <typename A, typename R>
 void UnwindCursor<A, R>::setInfoBasedOnIPRegister(bool isReturnAddress) {
 #if defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) ||                               \
+    defined(_LIBUNWIND_CHECK_FREEBSD_SIGRETURN) ||                             \
     defined(_LIBUNWIND_CHECK_HAIKU_SIGRETURN)
   _isSigReturn = false;
 #endif
@@ -3023,6 +3037,7 @@ void UnwindCursor<A, R>::setInfoBasedOnIPRegister(bool isReturnAddress) {
 #endif // #if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND)
 
 #if defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) ||                               \
+    defined(_LIBUNWIND_CHECK_FREEBSD_SIGRETURN) ||                             \
     defined(_LIBUNWIND_CHECK_HAIKU_SIGRETURN)
   if (setInfoForSigReturn())
     return;
@@ -3329,6 +3344,65 @@ int UnwindCursor<A, R>::stepThroughSigReturn(Registers_s390x &) {
 #endif // defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) &&
        // defined(_LIBUNWIND_TARGET_S390X)
 
+#if defined(_LIBUNWIND_CHECK_FREEBSD_SIGRETURN)
+template <typename A, typename R>
+bool UnwindCursor<A, R>::setInfoForSigReturn() {
+  // Ask the kernel rather than reading instructions at a possibly invalid PC.
+  // This is only used when no unwind information was found. Keep the result
+  // local to avoid shared mutable state when unwinding from a signal handler.
+  struct kinfo_sigtramp kst = {};
+  size_t len = sizeof(kst);
+  int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_SIGTRAMP, getpid()};
+  const int savedErrno = errno;
+  const int result = sysctl(mib, 4, &kst, &len, nullptr, 0);
+  errno = savedErrno;
+  if (result != 0)
+    return false;
+
+  const pint_t pc = static_cast<pint_t>(this->getReg(UNW_REG_IP));
+  const pint_t start = reinterpret_cast<pint_t>(kst.ksigtramp_start);
+  const pint_t end = reinterpret_cast<pint_t>(kst.ksigtramp_end);
+  if (pc < start || pc >= end)
+    return false;
+
+  _info = {};
+  _info.start_ip = start;
+  _info.end_ip = end;
+  _isSigReturn = true;
+  return true;
+}
+
+#if defined(_LIBUNWIND_TARGET_AARCH64)
+template <typename A, typename R>
+int UnwindCursor<A, R>::stepThroughSigReturn() {
+  // The trampoline leaves SP pointing at the sigframe installed by sendsig().
+  const pint_t sigctx = _registers.getSP() + offsetof(struct sigframe, sf_uc) +
+                        offsetof(ucontext_t, uc_mcontext);
+  const pint_t gprs = sigctx + offsetof(mcontext_t, mc_gpregs);
+  for (int i = 0; i < 30; ++i)
+    _registers.setRegister(
+        UNW_AARCH64_X0 + i,
+        _addressSpace.get64(gprs + offsetof(struct gpregs, gp_x) + i * 8));
+  _registers.setRegister(
+      UNW_AARCH64_LR,
+      _addressSpace.get64(gprs + offsetof(struct gpregs, gp_lr)));
+  _registers.setSP(_addressSpace.get64(gprs + offsetof(struct gpregs, gp_sp)));
+  _registers.setIP(_addressSpace.get64(gprs + offsetof(struct gpregs, gp_elr)));
+
+  if (_addressSpace.get32(sigctx + offsetof(mcontext_t, mc_flags)) &
+      _MC_FP_VALID) {
+    const pint_t fprs = sigctx + offsetof(mcontext_t, mc_fpregs) +
+                        offsetof(struct fpregs, fp_q);
+    for (int i = 0; i < 32; ++i)
+      _registers.setFloatRegister(UNW_AARCH64_V0 + i,
+                                  _addressSpace.getDouble(fprs + i * 16));
+  }
+  _isSignalFrame = true;
+  return UNW_STEP_SUCCESS;
+}
+#endif // defined(_LIBUNWIND_TARGET_AARCH64)
+#endif // defined(_LIBUNWIND_CHECK_FREEBSD_SIGRETURN)
+
 #if defined(_LIBUNWIND_CHECK_HAIKU_SIGRETURN)
 template <typename A, typename R>
 bool UnwindCursor<A, R>::setInfoForSigReturn() {
@@ -3428,6 +3502,7 @@ template <typename A, typename R> int UnwindCursor<A, R>::step(bool stage2) {
   // Use unwinding info to modify register set as if function returned.
   int result;
 #if defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) ||                               \
+    defined(_LIBUNWIND_CHECK_FREEBSD_SIGRETURN) ||                             \
     defined(_LIBUNWIND_CHECK_HAIKU_SIGRETURN)
   if (_isSigReturn) {
     result = this->stepThroughSigReturn();

diff  --git a/libunwind/test/aarch64_freebsd_signal_unwind.pass.cpp b/libunwind/test/aarch64_freebsd_signal_unwind.pass.cpp
new file mode 100644
index 00000000000000..44be6c507dedc1
--- /dev/null
+++ b/libunwind/test/aarch64_freebsd_signal_unwind.pass.cpp
@@ -0,0 +1,103 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: target={{aarch64-.*-freebsd.*}}
+// UNSUPPORTED: msan
+
+// Unwind through the kernel's signal trampoline, including on an alternate
+// stack. In particular, restore the interrupted PC rather than the saved LR:
+// the latter would skip the interrupted leaf function altogether.
+
+#undef NDEBUG
+#include "support/func_bounds.h"
+#include <assert.h>
+#include <libunwind.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ucontext.h>
+
+FUNC_BOUNDS_DECL(main_func);
+static volatile sig_atomic_t handled;
+
+static void check_reg(unw_cursor_t *cursor, int reg, unw_word_t expected) {
+  unw_word_t value;
+  assert(unw_get_reg(cursor, reg, &value) == 0);
+  assert(value == expected);
+}
+
+static void handler(int, siginfo_t *, void *context) {
+  auto *uc = static_cast<ucontext_t *>(context);
+  const mcontext_t &mc = uc->uc_mcontext;
+  unw_context_t unwind_context;
+  unw_cursor_t cursor;
+  assert(unw_getcontext(&unwind_context) == 0);
+  assert(unw_init_local(&cursor, &unwind_context) == 0);
+
+  bool found = false;
+  for (int i = 0; i < 64 && unw_step(&cursor) > 0; ++i) {
+    if (unw_is_signal_frame(&cursor)) {
+      found = true;
+      break;
+    }
+  }
+  assert(found);
+  check_reg(&cursor, UNW_REG_IP, mc.mc_gpregs.gp_elr);
+  check_reg(&cursor, UNW_REG_SP, mc.mc_gpregs.gp_sp);
+  check_reg(&cursor, UNW_AARCH64_LR, mc.mc_gpregs.gp_lr);
+  for (int i = 0; i < 30; ++i)
+    check_reg(&cursor, UNW_AARCH64_X0 + i, mc.mc_gpregs.gp_x[i]);
+
+  if (mc.mc_flags & _MC_FP_VALID) {
+    for (int i = 0; i < 32; ++i) {
+      unw_fpreg_t value;
+      assert(unw_get_fpreg(&cursor, UNW_AARCH64_V0 + i, &value) == 0);
+      assert(memcmp(&value, &mc.mc_fpregs.fp_q[i], sizeof(value)) == 0);
+    }
+  }
+
+  assert(unw_step(&cursor) > 0);
+  assert(!unw_is_signal_frame(&cursor));
+  unw_word_t pc;
+  assert(unw_get_reg(&cursor, UNW_REG_IP, &pc) == 0);
+  assert(pc >= reinterpret_cast<uintptr_t>(FUNC_START(main_func)));
+  assert(pc < reinterpret_cast<uintptr_t>(FUNC_END(main_func)));
+
+  // Skip the BRK instruction when returning from the signal handler.
+  uc->uc_mcontext.mc_gpregs.gp_elr += 4;
+  handled = handled + 1;
+}
+
+__attribute__((noinline)) static void leaf() { asm volatile("brk #0"); }
+
+FUNC_ATTR(main_func) int main(int, char **) {
+  stack_t stack = {};
+  stack.ss_size = SIGSTKSZ;
+  stack.ss_sp = malloc(stack.ss_size);
+  assert(stack.ss_sp != nullptr);
+  assert(sigaltstack(&stack, nullptr) == 0);
+
+  struct sigaction action = {};
+  action.sa_sigaction = handler;
+  action.sa_flags = SA_SIGINFO;
+  assert(sigemptyset(&action.sa_mask) == 0);
+  assert(sigaction(SIGTRAP, &action, nullptr) == 0);
+  leaf();
+  assert(handled == 1);
+
+  action.sa_flags |= SA_ONSTACK;
+  assert(sigaction(SIGTRAP, &action, nullptr) == 0);
+  leaf();
+  assert(handled == 2);
+
+  stack.ss_flags = SS_DISABLE;
+  assert(sigaltstack(&stack, nullptr) == 0);
+  free(stack.ss_sp);
+  return 0;
+}

diff  --git a/libunwind/test/aarch64_za_unwind.pass.cpp b/libunwind/test/aarch64_za_unwind.pass.cpp
index 408351662b5217..3835e695220812 100644
--- a/libunwind/test/aarch64_za_unwind.pass.cpp
+++ b/libunwind/test/aarch64_za_unwind.pass.cpp
@@ -13,7 +13,9 @@
 // to disable ZA before resuming from unwinding.
 // UNSUPPORTED: stdlib=system && target={{.+}}-apple-{{.*}}{{(11|12|13|14|15|26)(\.\d+)*}}
 
+#if !defined(__FreeBSD__)
 #include <alloca.h>
+#endif
 #include <libunwind.h>
 #include <stdint.h>
 #include <stdio.h>


        


More information about the cfe-commits mailing list