From 09e68cca06f495f60dc8b209b33608983fde5be5 Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Tue, 25 Aug 2026 17:03:10 -0400 Subject: [PATCH 1/5] Report the failing condition in BOOST_TEST_SYS_ASSERT The macro takes `cond` but stringizes the literal token `exp`, so every one of its call sites produces the same text regardless of what failed: system_error produced by: exp: Out of memory All six uses in execution_monitor.ipp are sigaction/sigaltstack/signal calls, and the message identifies none of them. Stringize the parameter. --- include/boost/test/execution_monitor.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/test/execution_monitor.hpp b/include/boost/test/execution_monitor.hpp index 6f1893bcd6..852108840d 100644 --- a/include/boost/test/execution_monitor.hpp +++ b/include/boost/test/execution_monitor.hpp @@ -495,7 +495,7 @@ class system_error { }; //!@internal -#define BOOST_TEST_SYS_ASSERT( cond ) BOOST_TEST_I_ASSRT( cond, ::boost::system_error( BOOST_STRINGIZE( exp ) ) ) +#define BOOST_TEST_SYS_ASSERT( cond ) BOOST_TEST_I_ASSRT( cond, ::boost::system_error( BOOST_STRINGIZE( cond ) ) ) // ************************************************************************** // // **************Floating point exception management interface ************** // From 3ed098b3281ddf73683f95c21944626fc7ad211d Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Tue, 25 Aug 2026 17:03:23 -0400 Subject: [PATCH 2/5] Correct the documented default for p_use_alt_stack The documentation says the flag defaults to false; execution_monitor's constructor initialises it to true. The true default is why the alternate stack is installed on paths that never set the property. --- include/boost/test/execution_monitor.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/test/execution_monitor.hpp b/include/boost/test/execution_monitor.hpp index 852108840d..ceb1747e80 100644 --- a/include/boost/test/execution_monitor.hpp +++ b/include/boost/test/execution_monitor.hpp @@ -344,7 +344,7 @@ class BOOST_TEST_DECL execution_monitor { /// Should monitor use alternative stack for the signal catching. /// - /// The @em p_use_alt_stack property is a boolean flag (default value is false) specifying whether or not execution_monitor should use an alternative stack + /// The @em p_use_alt_stack property is a boolean flag (default value is true) specifying whether or not execution_monitor should use an alternative stack /// for the sigaction based signal catching. When enabled the signals are delivered to the execution_monitor on a stack different from current execution /// stack, which is safer in case if it is corrupted by monitored function. For more details on alternative stack handling see appropriate manuals. unit_test::readwrite_property p_use_alt_stack; From 6955ee73856827b699f2f18fcd60f7c54eca2030 Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Tue, 25 Aug 2026 17:07:08 -0400 Subject: [PATCH 3/5] Size the alternate signal stack from the kernel's own minimum BOOST_TEST_ALT_STACK_SIZE is SIGSTKSZ. glibc 2.34+ made that dynamic -- sysconf(_SC_SIGSTKSZ) -- so it tracks what the running kernel requires. Other C libraries still define it as a compile time constant: musl uses 8192. The kernel derives its real minimum from the CPU's XSAVE area and publishes it as auxv AT_MINSIGSTKSZ, and that is the figure sigaltstack(2) validates against. Measured, it tracks the XSAVE size plus ~940 bytes of signal frame: Xeon E5-2687W v2 (AVX) XSAVE 832 -> AT_MINSIGSTKSZ 1776 Core i9-13900K (AVX2) XSAVE 2696 -> AT_MINSIGSTKSZ 3632 AMX-capable host -> AT_MINSIGSTKSZ 11952 ~11 KB is reached once AMX tile state (8192 bytes) sits on top of AVX-512's ~2.7 KB, i.e. Sapphire Rapids and later. On such a host under musl the kernel minimum exceeds the hardcoded 8192, sigaltstack fails with ENOMEM, and the test binary aborts before running anything: Test setup error: system_error produced by: exp: Out of memory Prefer AT_MINSIGSTKSZ where it is available, and never go below MINSIGSTKSZ. Verified under musl in an Alpine container that getauxval is present and returns the expected value; musl also defines AT_MINSIGSTKSZ itself, so the fallback define is only for libcs that do not. No effect where SIGSTKSZ is already dynamic: on glibc the value chosen is identical before and after, because SIGSTKSZ there already exceeds the kernel minimum. --- include/boost/test/impl/execution_monitor.ipp | 67 ++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/include/boost/test/impl/execution_monitor.ipp b/include/boost/test/impl/execution_monitor.ipp index 8d7500afd2..c1bec53630 100644 --- a/include/boost/test/impl/execution_monitor.ipp +++ b/include/boost/test/impl/execution_monitor.ipp @@ -175,8 +175,38 @@ namespace { void _set_se_translator( void* ) {} } # define BOOST_TEST_CATCH_SIGPOLL # endif +# if defined(__linux__) && defined(__has_include) +# if __has_include() +# include +# define BOOST_TEST_HAS_SYS_AUXV +// Not defined by every libc's ; the value is kernel ABI. +# ifndef AT_MINSIGSTKSZ +# define AT_MINSIGSTKSZ 51 +# endif +# endif +# endif + # ifdef BOOST_TEST_USE_ALT_STACK -# define BOOST_TEST_ALT_STACK_SIZE SIGSTKSZ +// SIGSTKSZ is not a reliable size for the alternate signal stack. +// +// glibc 2.34+ made it dynamic -- sysconf(_SC_SIGSTKSZ) -- so it reflects what +// the running kernel requires. Other C libraries still define it as a compile +// time constant; musl uses 8192. +// +// The kernel derives its actual minimum from the CPU's XSAVE area and publishes +// it as auxv AT_MINSIGSTKSZ. On hardware with large register state that value +// exceeds a hardcoded SIGSTKSZ -- measured 11952 on an AMX-capable CPU, against +// musl's 8192 -- and sigaltstack(2) then fails with ENOMEM, aborting the test +// binary before any test runs. +// +// Prefer the kernel's own figure where it is available, and never go below +// MINSIGSTKSZ. +# if defined(__linux__) && defined(BOOST_TEST_HAS_SYS_AUXV) +# define BOOST_TEST_ALT_STACK_SIZE ::boost::detail::alt_stack_size() +# else +# define BOOST_TEST_ALT_STACK_SIZE ((std::size_t)SIGSTKSZ > (std::size_t)MINSIGSTKSZ \ + ? (std::size_t)SIGSTKSZ : (std::size_t)MINSIGSTKSZ) +# endif # endif @@ -665,6 +695,41 @@ system_signal_exception::report() const //____________________________________________________________________________// +// ************************************************************************** // +// ************** boost::detail::alt_stack_size ************** // +// ************************************************************************** // + +#if defined(BOOST_TEST_USE_ALT_STACK) && defined(BOOST_TEST_HAS_SYS_AUXV) + +inline std::size_t alt_stack_size_impl() +{ + std::size_t candidate = static_cast( SIGSTKSZ ); + + std::size_t const from_kernel = static_cast( ::getauxval( AT_MINSIGSTKSZ ) ); + if( from_kernel > candidate ) + candidate = from_kernel; + + if( candidate < static_cast( MINSIGSTKSZ ) ) + candidate = static_cast( MINSIGSTKSZ ); + + return candidate; +} + +//! Size for the alternate signal stack, taken from the running kernel. +//! +//! AT_MINSIGSTKSZ is what the kernel says a signal frame needs on this CPU. It +//! is derived from the XSAVE area, so it grows with the register state the +//! hardware carries, and it is the figure sigaltstack(2) validates against. +//! Falls back to SIGSTKSZ where the kernel does not publish it. +inline std::size_t alt_stack_size() +{ + static std::size_t const size = alt_stack_size_impl(); + + return size; +} + +#endif + // ************************************************************************** // // ************** boost::detail::signal_action ************** // // ************************************************************************** // From 5184a5d35f0282f5fbf16514bd2c144e1e908196 Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Tue, 25 Aug 2026 17:08:36 -0400 Subject: [PATCH 4/5] Restore the caller's alternate signal stack instead of disabling it The install is guarded twice: it happens only when alt_stack is non-null, and only when no alternate stack is currently active. The teardown is not guarded at all -- it runs whenever BOOST_TEST_USE_ALT_STACK is compiled in, and unconditionally disables whatever is installed. So a program that installs its own alternate signal stack before invoking Boost.Test loses it at the end of the first monitored scope, and never gets it back. That is caller state the library did not create. Demonstrated with a test that installs a 64 KiB stack before main() and checks it from a test case: before this change ss_size 14528, i.e. Boost's own (check fails) after ss_size 65536, the caller's (check passes) The sequence is: the caller installs; Boost's first handler queries, sees an active stack and correctly skips its install; the matching destructor then disables it anyway; every later scope finds nothing active and gets Boost's own. Record whether this handler installed the stack, and restore what was there rather than blanket-disabling. Where Boost did install, the saved value is a disabled stack in every case reachable today -- the install only runs when SS_DISABLE was set -- so behaviour is unchanged for programs that install nothing. --- include/boost/test/impl/execution_monitor.ipp | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/include/boost/test/impl/execution_monitor.ipp b/include/boost/test/impl/execution_monitor.ipp index c1bec53630..0e174e6658 100644 --- a/include/boost/test/impl/execution_monitor.ipp +++ b/include/boost/test/impl/execution_monitor.ipp @@ -852,6 +852,14 @@ private: sigjmp_buf m_sigjmp_buf; system_signal_exception m_sys_sig; +#ifdef BOOST_TEST_USE_ALT_STACK + // Whether THIS handler installed the alternate stack, and what was in place + // before it did. Restoring beats unconditionally disabling: a caller may have + // installed its own alternate stack, and that is not ours to discard. + bool m_installed_alt_stack; + stack_t m_prev_alt_stack; +#endif + static signal_handler* s_active_handler; }; @@ -877,6 +885,9 @@ signal_handler::signal_handler( bool catch_system_errors, #endif , m_ABRT_action( SIGABRT, catch_system_errors, attach_dbg, alt_stack ) , m_ALRM_action( SIGALRM, timeout_microseconds > 0, attach_dbg, alt_stack ) +#ifdef BOOST_TEST_USE_ALT_STACK +, m_installed_alt_stack( false ) +#endif { s_active_handler = this; @@ -892,11 +903,15 @@ signal_handler::signal_handler( bool catch_system_errors, BOOST_TEST_SYS_ASSERT( ::sigaltstack( 0, &sigstk ) != -1 ); + m_prev_alt_stack = sigstk; + if( sigstk.ss_flags & SS_DISABLE ) { sigstk.ss_sp = alt_stack; sigstk.ss_size = BOOST_TEST_ALT_STACK_SIZE; sigstk.ss_flags = 0; BOOST_TEST_SYS_ASSERT( ::sigaltstack( &sigstk, 0 ) != -1 ); + + m_installed_alt_stack = true; } } #endif @@ -921,13 +936,23 @@ signal_handler::~signal_handler() stack_t sigstk = { }; #endif - sigstk.ss_size = MINSIGSTKSZ; - sigstk.ss_flags = SS_DISABLE; - if( ::sigaltstack( &sigstk, 0 ) == -1 ) { - int error_n = errno; - std::cerr << "******** errors disabling the alternate stack:" << std::endl - << "\t#error:" << error_n << std::endl - << "\t" << std::strerror( error_n ) << std::endl; + // Only undo what this handler did. If it did not install the alternate stack, + // something else owns it -- possibly the calling program, which may have + // installed one deliberately -- and tearing that down is not ours to do. + // + // Where we did install, put back exactly what was there before, which the + // constructor captured. That was a disabled stack in every case reachable + // today, since the install only happens when SS_DISABLE was set, but + // restoring the saved value keeps this correct if that guard ever relaxes. + if( m_installed_alt_stack ) { + sigstk = m_prev_alt_stack; + + if( ::sigaltstack( &sigstk, 0 ) == -1 ) { + int error_n = errno; + std::cerr << "******** errors restoring the alternate stack:" << std::endl + << "\t#error:" << error_n << std::endl + << "\t" << std::strerror( error_n ) << std::endl; + } } #endif From 84ec34f78ae911d2ab8c819d580a523d95979c80 Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Tue, 25 Aug 2026 18:18:37 -0400 Subject: [PATCH 5/5] Take AT_MINSIGSTKSZ from rather than restating its value Review feedback on #496: the fallback '#define AT_MINSIGSTKSZ 51' was a magic number with no provenance. It is kernel ABI, from linux/auxvec.h, but it does not need restating here at all. Both glibc and musl expose the constant through , which this code already includes -- checked on glibc 2.42 and on musl in an alpine:edge container, both report 51 from that header alone. Alpine does not even ship linux/auxvec.h with musl-dev, and does not need to. So gate the feature on the constant being provided instead of supplying it. A libc with the header but not the constant now falls back to SIGSTKSZ, exactly as before this series, rather than compiling against a value this header invented. Verified after the change that the feature still enables on both: glibc alt_stack_size() = 14528 (SIGSTKSZ 14528, AT_MINSIGSTKSZ 3632) musl alt_stack_size() = 8192 (SIGSTKSZ 8192, AT_MINSIGSTKSZ 3632) --- include/boost/test/impl/execution_monitor.ipp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/include/boost/test/impl/execution_monitor.ipp b/include/boost/test/impl/execution_monitor.ipp index 0e174e6658..53537a5d13 100644 --- a/include/boost/test/impl/execution_monitor.ipp +++ b/include/boost/test/impl/execution_monitor.ipp @@ -175,13 +175,17 @@ namespace { void _set_se_translator( void* ) {} } # define BOOST_TEST_CATCH_SIGPOLL # endif +// AT_MINSIGSTKSZ lets the alternate signal stack be sized from what the running +// kernel requires rather than from SIGSTKSZ. Both glibc and musl expose the +// constant through , so it is taken from there rather than being +// restated here -- the value is kernel ABI (linux/auxvec.h) and does not belong +// in this header. Where a libc provides the header but not the constant, the +// feature is simply not enabled and SIGSTKSZ is used as before. # if defined(__linux__) && defined(__has_include) # if __has_include() # include -# define BOOST_TEST_HAS_SYS_AUXV -// Not defined by every libc's ; the value is kernel ABI. -# ifndef AT_MINSIGSTKSZ -# define AT_MINSIGSTKSZ 51 +# ifdef AT_MINSIGSTKSZ +# define BOOST_TEST_HAS_SYS_AUXV # endif # endif # endif