[libunwind] [libunwind][Apple] Improve test coverage on Apple platforms (PR #186423)

Jon Roelofs via cfe-commits cfe-commits at lists.llvm.org
Fri Mar 13 08:39:03 PDT 2026


https://github.com/jroelofs created https://github.com/llvm/llvm-project/pull/186423

Introduces a macro abstraction around capturing the bounds of a function, which many platforms handle subtly differently (Mach-O, and ELF, for example).

Also introduce an arm64[^-]* -> aarch64 available feature, to enable more tests that would otherwise be excluded on Apple platforms, whose target triples tend to take the form e.g. 'arm64-apple-macosx', rather than 'aarch64-apple-macosx'.

Third, we implement the has-sme check using the appropriate sysctl, as getauxval is not available on Darwin platforms.

>From af77b7ea0ade29a59f68ce3295e16a7ded1d87c7 Mon Sep 17 00:00:00 2001
From: Jon Roelofs <jonathan_roelofs at apple.com>
Date: Fri, 13 Mar 2026 08:23:44 -0700
Subject: [PATCH] [libunwind][Apple] Improve test coverage on Apple platforms

Introduces a macro abstraction around capturing the bounds of a function, which
many platforms handle subtly differently (Mach-O, and ELF, for example).

Also introduce an arm64[^-]* -> aarch64 available feature, to enable more tests
that would otherwise be excluded on Apple platforms, whose target triples tend
to take the form e.g. 'arm64-apple-macosx', rather than 'aarch64-apple-macosx'.

Third, we implement the has-sme check using the appropriate sysctl, as getauxval
is not available on Darwin platforms.
---
 libunwind/test/aarch64_za_unwind.pass.cpp   | 15 +++++++-
 libunwind/test/configs/cmake-bridge.cfg.in  |  9 +++++
 libunwind/test/floatregister.pass.cpp       | 14 +++-----
 libunwind/test/forceunwind.pass.cpp         | 15 +++-----
 libunwind/test/signal_unwind.pass.cpp       | 15 +++-----
 libunwind/test/support/func_bounds.h        | 40 +++++++++++++++++++++
 libunwind/test/unwind_leaffunction.pass.cpp | 15 +++-----
 7 files changed, 83 insertions(+), 40 deletions(-)
 create mode 100644 libunwind/test/support/func_bounds.h

diff --git a/libunwind/test/aarch64_za_unwind.pass.cpp b/libunwind/test/aarch64_za_unwind.pass.cpp
index 9f6b106a21fec..a8f616ad17677 100644
--- a/libunwind/test/aarch64_za_unwind.pass.cpp
+++ b/libunwind/test/aarch64_za_unwind.pass.cpp
@@ -14,18 +14,31 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#if defined(__APPLE__)
+#include <sys/sysctl.h>
+#else
 #include <sys/auxv.h>
+#endif
 
 // Basic test of unwinding with SME lazy saves. This tests libunwind disables ZA
 // (and commits a lazy save of ZA) before resuming from unwinding.
 
 // Note: This test requires SME (and is setup to pass on targets without SME).
-
+#if defined(__APPLE__)
+static bool checkHasSME() {
+  int has_sme = 0;
+  size_t size = sizeof(has_sme);
+  if (!sysctlbyname("hw.optional.arm.FEAT_SME", &has_sme, &size, NULL, 0))
+    return false;
+  return has_sme != 0;
+}
+#else
 static bool checkHasSME() {
   constexpr int hwcap2_sme = (1 << 23);
   unsigned long hwcap2 = getauxval(AT_HWCAP2);
   return (hwcap2 & hwcap2_sme) != 0;
 }
+#endif
 
 struct TPIDR2Block {
   void *za_save_buffer;
diff --git a/libunwind/test/configs/cmake-bridge.cfg.in b/libunwind/test/configs/cmake-bridge.cfg.in
index e40497bfa9976..cbb39873b68c3 100644
--- a/libunwind/test/configs/cmake-bridge.cfg.in
+++ b/libunwind/test/configs/cmake-bridge.cfg.in
@@ -15,6 +15,7 @@ import os, site
 site.addsitedir(os.path.join('@LIBUNWIND_LIBCXX_PATH@', 'utils'))
 import libcxx.test.format
 from lit.util import which
+import re as _re
 
 # Basic configuration of the test suite
 config.name = os.path.basename('@LIBUNWIND_TEST_CONFIG@')
@@ -29,6 +30,14 @@ if @LIBUNWIND_USES_ARM_EHABI@:
 if not @LIBUNWIND_ENABLE_THREADS@:
     config.available_features.add('libunwind-no-threads')
 
+# Apple uses "arm64" and "arm64e" in target triples where the canonical LLVM
+# name is "aarch64".  REQUIRES directives like `REQUIRES: target={{aarch64-.+}}`
+# would otherwise never match on any Apple AArch64 target.  Add a normalised
+# alias so those directives work correctly.
+_norm_triple = _re.sub(r'^arm64([^-]*)', 'aarch64', config.target_triple)
+if _norm_triple != config.target_triple:
+    config.available_features.add('target={}'.format(_norm_triple))
+
 # Add substitutions for bootstrapping the test suite configuration
 config.substitutions.append(('%{install-prefix}', '@LIBUNWIND_TESTING_INSTALL_PREFIX@'))
 config.substitutions.append(('%{include}', '@LIBUNWIND_TESTING_INSTALL_PREFIX@/include'))
diff --git a/libunwind/test/floatregister.pass.cpp b/libunwind/test/floatregister.pass.cpp
index 6be3e1f3f7385..48e1b7797a885 100644
--- a/libunwind/test/floatregister.pass.cpp
+++ b/libunwind/test/floatregister.pass.cpp
@@ -12,16 +12,12 @@
 
 // Basic test for float registers number are accepted.
 
+#include "support/func_bounds.h"
 #include <stdlib.h>
 #include <string.h>
 #include <unwind.h>
 
-// Using __attribute__((section("main_func"))) is ELF specific, but then
-// this entire test is marked as requiring Linux, so we should be good.
-//
-// We don't use dladdr() because on musl it's a no-op when statically linked.
-extern char __start_main_func;
-extern char __stop_main_func;
+FUNC_BOUNDS_DECL(main_func);
 
 _Unwind_Reason_Code frame_handler(struct _Unwind_Context *ctx, void *arg) {
   (void)arg;
@@ -29,8 +25,8 @@ _Unwind_Reason_Code frame_handler(struct _Unwind_Context *ctx, void *arg) {
   // Unwind until the main is reached, above frames depend on the platform and
   // architecture.
   uintptr_t ip = _Unwind_GetIP(ctx);
-  if (ip >= (uintptr_t)&__start_main_func &&
-      ip < (uintptr_t)&__stop_main_func) {
+  if (ip >= (uintptr_t)FUNC_START(main_func) &&
+      ip < (uintptr_t)FUNC_END(main_func)) {
     _Exit(0);
   }
 
@@ -53,7 +49,7 @@ __attribute__((noinline)) void foo() {
   _Unwind_Backtrace(frame_handler, NULL);
 }
 
-__attribute__((section("main_func"))) int main(int, char **) {
+FUNC_ATTR(main_func) int main(int, char **) {
   foo();
   return -2;
 }
diff --git a/libunwind/test/forceunwind.pass.cpp b/libunwind/test/forceunwind.pass.cpp
index e5437c31a0f65..fd61912acb734 100644
--- a/libunwind/test/forceunwind.pass.cpp
+++ b/libunwind/test/forceunwind.pass.cpp
@@ -7,7 +7,6 @@
 //
 //===----------------------------------------------------------------------===//
 
-// UNSUPPORTED: target={{.*-apple.*}}
 // UNSUPPORTED: target={{.*-aix.*}}
 // UNSUPPORTED: target={{.*-windows.*}}
 
@@ -18,6 +17,7 @@
 // See libcxxabi/test/forced_unwind* tests too.
 
 #undef NDEBUG
+#include "support/func_bounds.h"
 #include <assert.h>
 #include <signal.h>
 #include <stdint.h>
@@ -28,12 +28,7 @@
 #include <unistd.h>
 #include <unwind.h>
 
-// Using __attribute__((section("main_func"))) is Linux specific, but then
-// this entire test is marked as requiring Linux, so we should be good.
-//
-// We don't use dladdr() because on musl it's a no-op when statically linked.
-extern char __start_main_func;
-extern char __stop_main_func;
+FUNC_BOUNDS_DECL(main_func);
 
 void foo();
 _Unwind_Exception ex;
@@ -52,8 +47,8 @@ _Unwind_Reason_Code stop(int version, _Unwind_Action actions,
   // Unwind until the main is reached, above frames depend on the platform and
   // architecture.
   uintptr_t ip = _Unwind_GetIP(context);
-  if (ip >= (uintptr_t)&__start_main_func &&
-      ip < (uintptr_t)&__stop_main_func) {
+  if (ip >= (uintptr_t)FUNC_START(main_func) &&
+      ip < (uintptr_t)FUNC_END(main_func)) {
     _Exit(0);
   }
 
@@ -74,7 +69,7 @@ __attribute__((noinline)) void foo() {
   _Unwind_ForcedUnwind(e, stop, (void *)&foo);
 }
 
-__attribute__((section("main_func"))) int main(int, char **) {
+FUNC_ATTR(main_func) int main(int, char **) {
   foo();
   return -2;
 }
diff --git a/libunwind/test/signal_unwind.pass.cpp b/libunwind/test/signal_unwind.pass.cpp
index ca50f83964c11..f8798bc3eabeb 100644
--- a/libunwind/test/signal_unwind.pass.cpp
+++ b/libunwind/test/signal_unwind.pass.cpp
@@ -10,7 +10,6 @@
 // Ensure that the unwinder can cope with the signal handler.
 // REQUIRES: target={{(aarch64|loongarch64|riscv64|s390x|x86_64)-.+}}
 // UNSUPPORTED: target={{.*-windows.*}}
-// UNSUPPORTED: target={{.*-apple.*}}
 
 // TODO: Figure out why this fails with Memory Sanitizer.
 // XFAIL: msan
@@ -23,6 +22,7 @@
 // XFAIL: target={{.*}}-musl
 
 #undef NDEBUG
+#include "support/func_bounds.h"
 #include <assert.h>
 #include <signal.h>
 #include <stdio.h>
@@ -32,12 +32,7 @@
 #include <unistd.h>
 #include <unwind.h>
 
-// Using __attribute__((section("main_func"))) is ELF specific, but then
-// this entire test is marked as requiring Linux, so we should be good.
-//
-// We don't use dladdr() because on musl it's a no-op when statically linked.
-extern char __start_main_func;
-extern char __stop_main_func;
+FUNC_BOUNDS_DECL(main_func);
 
 _Unwind_Reason_Code frame_handler(struct _Unwind_Context* ctx, void* arg) {
   (void)arg;
@@ -45,8 +40,8 @@ _Unwind_Reason_Code frame_handler(struct _Unwind_Context* ctx, void* arg) {
   // Unwind until the main is reached, above frames depend on the platform and
   // architecture.
   uintptr_t ip = _Unwind_GetIP(ctx);
-  if (ip >= (uintptr_t)&__start_main_func &&
-      ip < (uintptr_t)&__stop_main_func) {
+  if (ip >= (uintptr_t)FUNC_START(main_func) &&
+      ip < (uintptr_t)FUNC_END(main_func)) {
     _Exit(0);
   }
 
@@ -59,7 +54,7 @@ void signal_handler(int signum) {
   _Exit(-1);
 }
 
-__attribute__((section("main_func"))) int main(int, char **) {
+FUNC_ATTR(main_func) int main(int, char **) {
   signal(SIGUSR1, signal_handler);
   kill(getpid(), SIGUSR1);
   return -2;
diff --git a/libunwind/test/support/func_bounds.h b/libunwind/test/support/func_bounds.h
new file mode 100644
index 0000000000000..b6d9d25280d66
--- /dev/null
+++ b/libunwind/test/support/func_bounds.h
@@ -0,0 +1,40 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Utilities for locating the address range of a function.
+//
+// On MachO targets the linker synthesises section$start$SEG$sect /
+// section$end$SEG$sect symbols; __asm() is used to bind them to valid C
+// identifiers without the leading '_' implied by the Darwin User Label Prefix.
+//
+// On ELF targets the linker synthesises __start_<section> / __stop_<section>
+// symbols for any section whose name is a valid C identifier.
+// We don't use dladdr() because on musl it's a no-op when statically linked.
+
+#ifndef LIBUNWIND_TEST_SUPPORT_FUNC_BOUNDS_H
+#define LIBUNWIND_TEST_SUPPORT_FUNC_BOUNDS_H
+
+#ifdef __APPLE__
+#define FUNC_BOUNDS_DECL(name)                                                 \
+  extern char name##_start __asm("section$start$__TEXT$__" #name);             \
+  extern char name##_end __asm("section$end$__TEXT$__" #name)
+#define FUNC_ATTR(name)                                                        \
+  __attribute__((section("__TEXT,__" #name ",regular,pure_instructions")))
+#define FUNC_START(name) (&name##_start)
+#define FUNC_END(name) (&name##_end)
+#else
+#define FUNC_BOUNDS_DECL(name)                                                 \
+  extern char __start_##name;                                                  \
+  extern char __stop_##name
+#define FUNC_ATTR(name) __attribute__((section(#name)))
+#define FUNC_START(name) (&__start_##name)
+#define FUNC_END(name) (&__stop_##name)
+#endif
+
+#endif // LIBUNWIND_TEST_SUPPORT_FUNC_BOUNDS_H
diff --git a/libunwind/test/unwind_leaffunction.pass.cpp b/libunwind/test/unwind_leaffunction.pass.cpp
index af791a6b2ed31..a709493604e3c 100644
--- a/libunwind/test/unwind_leaffunction.pass.cpp
+++ b/libunwind/test/unwind_leaffunction.pass.cpp
@@ -10,7 +10,6 @@
 // Ensure that leaf function can be unwund.
 // REQUIRES: target={{(aarch64|loongarch64|riscv64|s390x|x86_64)-.+}}
 // UNSUPPORTED: target={{.*-windows.*}}
-// UNSUPPORTED: target={{.*-apple.*}}
 
 // TODO: Figure out why this fails with Memory Sanitizer.
 // XFAIL: msan
@@ -23,6 +22,7 @@
 // XFAIL: target={{.*}}-musl
 
 #undef NDEBUG
+#include "support/func_bounds.h"
 #include <assert.h>
 #include <signal.h>
 #include <stdio.h>
@@ -32,12 +32,7 @@
 #include <unistd.h>
 #include <unwind.h>
 
-// Using __attribute__((section("main_func"))) is ELF specific, but then
-// this entire test is marked as requiring Linux, so we should be good.
-//
-// We don't use dladdr() because on musl it's a no-op when statically linked.
-extern char __start_main_func;
-extern char __stop_main_func;
+FUNC_BOUNDS_DECL(main_func);
 
 _Unwind_Reason_Code frame_handler(struct _Unwind_Context* ctx, void* arg) {
   (void)arg;
@@ -45,8 +40,8 @@ _Unwind_Reason_Code frame_handler(struct _Unwind_Context* ctx, void* arg) {
   // Unwind until the main is reached, above frames depend on the platform and
   // architecture.
   uintptr_t ip = _Unwind_GetIP(ctx);
-  if (ip >= (uintptr_t)&__start_main_func &&
-      ip < (uintptr_t)&__stop_main_func) {
+  if (ip >= (uintptr_t)FUNC_START(main_func) &&
+      ip < (uintptr_t)FUNC_END(main_func)) {
     _Exit(0);
   }
 
@@ -72,7 +67,7 @@ __attribute__((noinline)) void crashing_leaf_func(int do_trap) {
     __builtin_trap();
 }
 
-__attribute__((section("main_func"))) int main(int, char **) {
+FUNC_ATTR(main_func) int main(int, char **) {
   signal(SIGTRAP, signal_handler);
   signal(SIGILL, signal_handler);
   crashing_leaf_func(1);



More information about the cfe-commits mailing list