fix(libc): bridge guest thread destructors to host ABI
This commit is contained in:
parent
df16c4c256
commit
0d27cbc83a
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in New Issue