[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