From 47e4282c19f095075fda1b5ea67b021f3fa2e3dc Mon Sep 17 00:00:00 2001 From: Jon Roelofs Date: Wed, 25 Mar 2026 18:16:19 -0700 Subject: [PATCH] [libunwind][Apple] Improve test coverage on Apple platforms (#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. --- libunwind/test/aarch64_vg_unwind.pass.cpp | 3 ++ libunwind/test/aarch64_za_unwind.pass.cpp | 15 +++++++- libunwind/test/configs/cmake-bridge.cfg.in | 9 +++++ libunwind/test/floatregister.pass.cpp | 17 +++++---- 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 +++----- 8 files changed, 89 insertions(+), 40 deletions(-) create mode 100644 libunwind/test/support/func_bounds.h diff --git a/libunwind/test/aarch64_vg_unwind.pass.cpp b/libunwind/test/aarch64_vg_unwind.pass.cpp index d0c623b15509..811fb432452d 100644 --- a/libunwind/test/aarch64_vg_unwind.pass.cpp +++ b/libunwind/test/aarch64_vg_unwind.pass.cpp @@ -9,6 +9,9 @@ // REQUIRES: target={{aarch64-.+}} // UNSUPPORTED: target={{.*-windows.*}} +// TODO: investigate this failure. +// XFAIL: target={{.*-apple-.*}} && stdlib=system + #include #include #include diff --git a/libunwind/test/aarch64_za_unwind.pass.cpp b/libunwind/test/aarch64_za_unwind.pass.cpp index 9f6b106a21fe..a8f616ad1767 100644 --- a/libunwind/test/aarch64_za_unwind.pass.cpp +++ b/libunwind/test/aarch64_za_unwind.pass.cpp @@ -14,18 +14,31 @@ #include #include #include +#if defined(__APPLE__) +#include +#else #include +#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 e40497bfa997..cbb39873b68c 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 6be3e1f3f738..52561360860b 100644 --- a/libunwind/test/floatregister.pass.cpp +++ b/libunwind/test/floatregister.pass.cpp @@ -10,18 +10,17 @@ // REQUIRES: target={{aarch64-.+}} // UNSUPPORTED: target={{.*-windows.*}} +// TODO: investigate this failure. +// XFAIL: target={{.*-apple-.*}} && stdlib=system + // Basic test for float registers number are accepted. +#include "support/func_bounds.h" #include #include #include -// 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 +28,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 +52,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 e5437c31a0f6..fd61912acb73 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 #include #include @@ -28,12 +28,7 @@ #include #include -// 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 ca50f83964c1..f8798bc3eabe 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 #include #include @@ -32,12 +32,7 @@ #include #include -// 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 000000000000..b6d9d25280d6 --- /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_
/ __stop_
+// 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 af791a6b2ed3..a709493604e3 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 #include #include @@ -32,12 +32,7 @@ #include #include -// 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);