From 304655816c4098f6a7c388cf6e90e34c9087f6e4 Mon Sep 17 00:00:00 2001 From: xXPBSXx <20614131+xXPBSXx@users.noreply.github.com> Date: Mon, 26 Oct 2026 08:58:54 +0100 Subject: [PATCH 1/2] test(libc): cover the __cxa_guard_acquire, release and abort behaviour The three guard exports had no test. The new one checks the Itanium C++ ABI guard protocol: a fresh guard is acquired once and reads initialized after the release, an aborted acquisition leaves it uninitialized and lets the next acquire succeed, two different guards nest on one thread, and a second thread blocks until the holder releases (then sees the object initialized) or takes over after an abort. Co-Authored-By: Claude Sonnet 5.5 --- core/libs/prx/libc/CMakeLists.txt | 10 ++++ core/libs/tests/GuestCxaGuard.cpp | 91 +++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 core/libs/tests/GuestCxaGuard.cpp diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 7930a2f5fc..04b6b0e41a 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -697,6 +697,16 @@ if(BUILD_TESTING) set_tests_properties(guest_qsort PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_cxa_guard_tests ${LIBS_INCLUDE_DIR}/tests/GuestCxaGuard.cpp) + target_include_directories(guest_cxa_guard_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_cxa_guard_tests PRIVATE libc) + configure_windows_unwind(guest_cxa_guard_tests) + add_test(NAME guest_cxa_guard COMMAND guest_cxa_guard_tests) + set_tests_properties(guest_cxa_guard PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_cxa_guard PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_strftime_tests ${LIBS_INCLUDE_DIR}/tests/GuestStrftime.cpp) target_include_directories(guest_strftime_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_strftime_tests PRIVATE libc) diff --git a/core/libs/tests/GuestCxaGuard.cpp b/core/libs/tests/GuestCxaGuard.cpp new file mode 100644 index 0000000000..b287afd874 --- /dev/null +++ b/core/libs/tests/GuestCxaGuard.cpp @@ -0,0 +1,91 @@ +#include "prx/libc/include/Api.hpp" + +#include +#include +#include +#include +#include +#include + +static void RequireAt(bool value, int line) { + if (!value) { + std::fprintf(stderr, "cxa guard check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) RequireAt((value), __LINE__) + +static bool Initialized(const std::uint64_t& guard) { + return (guard & 1) != 0; +} + +int main() { + { + std::uint64_t guard = 0; + Require(__cxa_guard_acquire_nid_postfix(&guard) != 0); + Require(!Initialized(guard)); + __cxa_guard_release_nid_postfix(&guard); + Require(Initialized(guard)); + Require(__cxa_guard_acquire_nid_postfix(&guard) == 0); + Require(Initialized(guard)); + } + + { + std::uint64_t guard = 0; + Require(__cxa_guard_acquire_nid_postfix(&guard) != 0); + __cxa_guard_abort_nid_postfix(&guard); + Require(!Initialized(guard)); + Require(__cxa_guard_acquire_nid_postfix(&guard) != 0); + __cxa_guard_release_nid_postfix(&guard); + Require(Initialized(guard)); + Require(__cxa_guard_acquire_nid_postfix(&guard) == 0); + } + + { + std::uint64_t outer = 0; + std::uint64_t inner = 0; + Require(__cxa_guard_acquire_nid_postfix(&outer) != 0); + Require(__cxa_guard_acquire_nid_postfix(&inner) != 0); + __cxa_guard_release_nid_postfix(&inner); + Require(Initialized(inner) && !Initialized(outer)); + __cxa_guard_release_nid_postfix(&outer); + Require(Initialized(outer)); + } + + { + std::uint64_t guard = 0; + Require(__cxa_guard_acquire_nid_postfix(&guard) != 0); + std::atomic returned{false}; + std::atomic result{-1}; + std::thread waiter([&] { + result = __cxa_guard_acquire_nid_postfix(&guard); + returned = true; + }); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + Require(!returned); + __cxa_guard_release_nid_postfix(&guard); + waiter.join(); + Require(returned && result == 0); + Require(Initialized(guard)); + } + + { + std::uint64_t guard = 0; + Require(__cxa_guard_acquire_nid_postfix(&guard) != 0); + std::atomic returned{false}; + std::atomic result{-1}; + std::thread waiter([&] { + result = __cxa_guard_acquire_nid_postfix(&guard); + returned = true; + if (result != 0) __cxa_guard_release_nid_postfix(&guard); + }); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + Require(!returned); + __cxa_guard_abort_nid_postfix(&guard); + waiter.join(); + Require(returned && result != 0); + Require(Initialized(guard)); + } + + return 0; +} From 44d9c8633ea775e051e46ed8258e00e769b7665e Mon Sep 17 00:00:00 2001 From: xXPBSXx <20614131+xXPBSXx@users.noreply.github.com> Date: Mon, 26 Oct 2026 12:32:31 +0100 Subject: [PATCH 2/2] test(libc): wait for the guard waiter to start before checking it blocks Both waiter cases checked that acquire had not returned after a fixed 100 ms, without knowing the waiter thread had reached the call. Each waiter now sets a started flag right before it calls acquire, and the main thread waits for it with a 5 s deadline before the 100 ms wait. The flag shows the thread got as far as the call, not that it is blocked inside it; the 100 ms wait stays. Co-Authored-By: Claude Sonnet 5.5 --- core/libs/tests/GuestCxaGuard.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/libs/tests/GuestCxaGuard.cpp b/core/libs/tests/GuestCxaGuard.cpp index b287afd874..5846411155 100644 --- a/core/libs/tests/GuestCxaGuard.cpp +++ b/core/libs/tests/GuestCxaGuard.cpp @@ -19,6 +19,13 @@ static bool Initialized(const std::uint64_t& guard) { return (guard & 1) != 0; } +static void WaitForStartAt(const std::atomic& started, int line) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (!started && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + RequireAt(started, line); +} +#define WaitForStart(started) WaitForStartAt((started), __LINE__) + int main() { { std::uint64_t guard = 0; @@ -55,12 +62,15 @@ int main() { { std::uint64_t guard = 0; Require(__cxa_guard_acquire_nid_postfix(&guard) != 0); + std::atomic started{false}; std::atomic returned{false}; std::atomic result{-1}; std::thread waiter([&] { + started = true; result = __cxa_guard_acquire_nid_postfix(&guard); returned = true; }); + WaitForStart(started); std::this_thread::sleep_for(std::chrono::milliseconds(100)); Require(!returned); __cxa_guard_release_nid_postfix(&guard); @@ -72,13 +82,16 @@ int main() { { std::uint64_t guard = 0; Require(__cxa_guard_acquire_nid_postfix(&guard) != 0); + std::atomic started{false}; std::atomic returned{false}; std::atomic result{-1}; std::thread waiter([&] { + started = true; result = __cxa_guard_acquire_nid_postfix(&guard); returned = true; if (result != 0) __cxa_guard_release_nid_postfix(&guard); }); + WaitForStart(started); std::this_thread::sleep_for(std::chrono::milliseconds(100)); Require(!returned); __cxa_guard_abort_nid_postfix(&guard);