fix(libc): bridge guest thread destructors to host ABI

This commit is contained in:
Ethan Barr 2026-10-07 17:58:43 -07:00
parent df16c4c256
commit 0d27cbc83a
4 changed files with 136 additions and 6 deletions

View File

@ -166,6 +166,16 @@ if(BUILD_TESTING)
set_tests_properties(guest_lifecycle PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
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:$<TARGET_FILE_DIR:libc>")
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)

View File

@ -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<ThreadAtexitContext*>(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);

View File

@ -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<int> threadCleanups{0};
void ThreadDone(void*) { ++threadCleanups; }
std::atomic<void*> 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);

View File

@ -0,0 +1,96 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <array>
#include <atomic>
#include <cstddef>
#include <cstdio>
#include <thread>
#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#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<Call, 3> calls{};
std::atomic<std::size_t> callCount{0};
std::atomic<bool> 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<void*>(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<Call, 3> 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;
}