diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index c59665ab8..e9da9e3ae 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -166,6 +166,16 @@ if(BUILD_TESTING) set_tests_properties(guest_lifecycle PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_thread_callback_abi_tests ${LIBS_INCLUDE_DIR}/tests/GuestThreadCallbackAbi.cpp) + target_include_directories(guest_thread_callback_abi_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_thread_callback_abi_tests PRIVATE libc) + configure_windows_unwind(guest_thread_callback_abi_tests) + add_test(NAME guest_thread_callback_abi COMMAND guest_thread_callback_abi_tests) + set_tests_properties(guest_thread_callback_abi PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_thread_callback_abi PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_reallocalign_tests ${LIBS_INCLUDE_DIR}/tests/GuestReallocalign.cpp) target_include_directories(guest_reallocalign_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_reallocalign_tests PRIVATE libc) diff --git a/core/libs/prx/libc/src/CxxAbiSupport.cpp b/core/libs/prx/libc/src/CxxAbiSupport.cpp index 008cc422e..1b13a5598 100644 --- a/core/libs/prx/libc/src/CxxAbiSupport.cpp +++ b/core/libs/prx/libc/src/CxxAbiSupport.cpp @@ -27,11 +27,29 @@ void* APS5_VABI __cxa_demangle_nid_postfix(const char* mangled, char* buf, std:: return abi::__cxa_demangle(mangled, buf, len, status); } -int APS5_VABI __cxa_thread_atexit_impl_nid_postfix(void (*func)(void*), void* arg, void* dso) { - return __cxxabiv1::__cxa_thread_atexit(func, arg, dso); +int APS5_VABI __cxa_thread_atexit_impl_nid_postfix(void (APS5_VABI *func)(void*), void* arg, void* dso) { + struct ThreadAtexitContext { + void (APS5_VABI *destructor)(void*); + void* object; + }; + auto* context = new (std::nothrow) ThreadAtexitContext{func, arg}; + if (context == nullptr) + return -1; + const int result = __cxxabiv1::__cxa_thread_atexit( + [](void* opaque) { + auto* context = static_cast(opaque); + const auto destructor = context->destructor; + void* object = context->object; + delete context; + destructor(object); + }, + context, dso); + if (result != 0) + delete context; + return result; } -int APS5_VABI LibcInternalExtCxaThreadAtexit_nid_postfix(void (*destructor)(void*), void* object, void* module_id) { +int APS5_VABI LibcInternalExtCxaThreadAtexit_nid_postfix(void (APS5_VABI *destructor)(void*), void* object, void* module_id) { #ifdef _WIN32 (void)module_id; return __cxa_thread_atexit_impl_nid_postfix(destructor, object, nullptr); diff --git a/core/libs/tests/GuestLifecycle.cpp b/core/libs/tests/GuestLifecycle.cpp index a78e20f06..11cb61e1e 100644 --- a/core/libs/tests/GuestLifecycle.cpp +++ b/core/libs/tests/GuestLifecycle.cpp @@ -19,7 +19,7 @@ struct InitEnvParams { extern "C" { int APS5_VABI cxa_atexit_nid_postfix(void (APS5_VABI *)(void*), void*, void*); void APS5_VABI cxa_finalize_nid_postfix(void*); -int APS5_VABI LibcInternalExtCxaThreadAtexit_nid_postfix(void (*)(void*), void*, void*); +int APS5_VABI LibcInternalExtCxaThreadAtexit_nid_postfix(void (APS5_VABI *)(void*), void*, void*); void APS5_VABI init_env_nid_postfix(const InitEnvParams*); } @@ -38,7 +38,12 @@ void APS5_VABI Second(void*) { secondAt = order++; } void APS5_VABI Other(void*) { otherAt = order++; } std::atomic threadCleanups{0}; -void ThreadDone(void*) { ++threadCleanups; } +std::atomic threadObjectSeen{nullptr}; +int threadObjectSentinel = 0; +void APS5_VABI ThreadDone(void* object) { + threadObjectSeen.store(object); + ++threadCleanups; +} } @@ -63,13 +68,14 @@ int main() { dlopen(nullptr, RTLD_NOW); #endif Require(image != nullptr); - Require(LibcInternalExtCxaThreadAtexit_nid_postfix(ThreadDone, nullptr, image) == 0); + Require(LibcInternalExtCxaThreadAtexit_nid_postfix(ThreadDone, &threadObjectSentinel, image) == 0); #ifndef _WIN32 dlclose(image); #endif }); worker.join(); Require(threadCleanups == 1); + Require(threadObjectSeen.load() == &threadObjectSentinel); try { init_env_nid_postfix(nullptr); diff --git a/core/libs/tests/GuestThreadCallbackAbi.cpp b/core/libs/tests/GuestThreadCallbackAbi.cpp new file mode 100644 index 000000000..a496f8851 --- /dev/null +++ b/core/libs/tests/GuestThreadCallbackAbi.cpp @@ -0,0 +1,96 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +using GuestDestructor = void(APS5_VABI *)(void*); + +extern "C" { +int APS5_VABI __cxa_thread_atexit_impl_nid_postfix(GuestDestructor, void*, void*); +int APS5_VABI LibcInternalExtCxaThreadAtexit_nid_postfix(GuestDestructor, void*, void*); +} + +namespace { + +struct Call { + int id; + void* object; +}; + +std::array calls{}; +std::atomic callCount{0}; +std::atomic registrationFailed{false}; + +void Record(int id, void* object) { + const auto index = callCount.fetch_add(1, std::memory_order_relaxed); + if (index < calls.size()) + calls[index] = {id, object}; +} + +void APS5_VABI FirstDestructor(void* object) { Record(1, object); } +void APS5_VABI SecondDestructor(void* object) { Record(2, object); } +void APS5_VABI ThirdDestructor(void* object) { Record(3, object); } + +void Fail(const char* message) { std::fprintf(stderr, "%s\n", message); } + +} + +int main() { + void* module = +#ifdef _WIN32 + reinterpret_cast(GetModuleHandleW(nullptr)); +#else + dlopen(nullptr, RTLD_NOW); +#endif + if (module == nullptr) { + Fail("could not acquire the test module handle"); + return 1; + } + + void* directDso = module; +#ifdef _WIN32 + directDso = nullptr; +#endif + + int firstObject = 1; + int secondObject = 2; + int thirdObject = 3; + std::thread worker([&] { + if (__cxa_thread_atexit_impl_nid_postfix(FirstDestructor, &firstObject, directDso) != 0) + registrationFailed.store(true); + if (__cxa_thread_atexit_impl_nid_postfix(SecondDestructor, &secondObject, directDso) != 0) + registrationFailed.store(true); + if (LibcInternalExtCxaThreadAtexit_nid_postfix(ThirdDestructor, &thirdObject, module) != 0) + registrationFailed.store(true); + }); + worker.join(); + +#ifndef _WIN32 + dlclose(module); +#endif + + if (registrationFailed.load()) { + Fail("thread destructor registration failed"); + return 1; + } + if (callCount.load() != calls.size()) { + Fail("thread exit did not run each registered destructor once"); + return 1; + } + const std::array expected = {{{3, &thirdObject}, {2, &secondObject}, {1, &firstObject}}}; + for (std::size_t index = 0; index < expected.size(); ++index) { + if (calls[index].id != expected[index].id || calls[index].object != expected[index].object) { + std::fprintf(stderr, "thread destructor %d received object %p; expected %p\n", calls[index].id, + calls[index].object, expected[index].object); + return 1; + } + } + return 0; +}