https://github.com/mchoo7 created 
https://github.com/llvm/llvm-project/pull/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 without increasing the cursor size.

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

Assisted-by: GPT

>From 6413e580a84bf68980ba1ca65b74816158fe2e2f Mon Sep 17 00:00:00 2001
From: Minsoo Choo <[email protected]>
Date: Fri, 23 Oct 2026 11:41:58 -0400
Subject: [PATCH] [libunwind] Unwind through AArch64 FreeBSD signal trampolines

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 without increasing the cursor size.

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

Based on the support proposed in https://reviews.llvm.org/D155066.
---
 libunwind/include/__libunwind_config.h        |   3 +
 libunwind/src/UnwindCursor.hpp                |  77 ++++++++++++-
 .../aarch64_freebsd_signal_unwind.pass.cpp    | 103 ++++++++++++++++++
 libunwind/test/aarch64_za_unwind.pass.cpp     |   2 +
 4 files changed, 184 insertions(+), 1 deletion(-)
 create mode 100644 libunwind/test/aarch64_freebsd_signal_unwind.pass.cpp

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>

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to