Files
AnyPS5/core/libs/prx/libSceLibcInternal/Export.cpp
T
Celegans12andClaude Opus 5.5 2dea7164e3 feat(libcinternal): run thread_local destructors at thread exit
A title's own libc.prx implements __cxa_thread_atexit and the
destructor pass of exit() through _sceLibcInternalThreadAtexit and
_sceLibcInternalThreadDtors. Its __cxa_finalize calls
_sceLibcInternalForceTlsDestructor.

Destructors are kept per thread and run newest first, including those
registered while they run, as FreeBSD's __cxa_thread_call_dtors does.
A null destructor or one outside every loaded image throws instead of
being skipped. The first registration installs the pass as the
libkernel thread exit hook, so it runs on every guest thread before
the thread can be joined. _sceLibcInternalForceTlsDestructor throws.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 17:51:35 +02:00

97 lines
2.6 KiB
C++

#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/HeapDiagnostics.hpp"
#include "prx/libc/include/HostThreadLocal.hpp"
#include <sstream>
#include <stdexcept>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#endif
extern "C" void APS5_VABI sceKernelSetThreadDtors(thread_dtors_func_t dtors);
namespace {
using ThreadDestructorFunction = void (APS5_VABI*)(void*);
struct ThreadDestructor {
ThreadDestructorFunction function;
void* object;
void* dsoSymbol;
};
struct ThreadDestructorsTag {};
std::vector<ThreadDestructor>& ThreadDestructors() {
return HostThreadLocal<std::vector<ThreadDestructor>, ThreadDestructorsTag>();
}
bool IsInLoadedImage(const void* address) {
if (address == nullptr)
return false;
#ifdef _WIN32
HMODULE module = nullptr;
return GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, static_cast<LPCSTR>(address), &module) != 0;
#else
Dl_info info{};
return dladdr(address, &info) != 0;
#endif
}
void APS5_VABI RunThreadDestructors_nid_no_patch() {
auto& destructors = ThreadDestructors();
while (!destructors.empty()) {
const ThreadDestructor destructor = destructors.back();
destructors.pop_back();
const auto* function = reinterpret_cast<const void*>(destructor.function);
if (!IsInLoadedImage(function)) {
std::ostringstream message;
message << "thread_local destructor " << function << " of dso " << destructor.dsoSymbol << " is not in a loaded image";
throw std::runtime_error(message.str());
}
destructor.function(destructor.object);
}
}
void RegisterThreadExitHook() {
[[maybe_unused]] static const bool registered = [] {
sceKernelSetThreadDtors(RunThreadDestructors_nid_no_patch);
return true;
}();
}
}
extern "C" {
int Need_sceLibcInternal_nid_postfix = 1;
void APS5_VABI __cxa_finalize_nid_postfix(void* dsoHandle) {
CxaFinalize_nid_no_patch(dsoHandle);
}
void APS5_VABI sceLibcHeapGetTraceInfo_nid_postfix(Info* info) {
LibcHeapTraceInfo_nid_no_patch(info);
}
int APS5_VABI _sceLibcInternalThreadAtexit_nid_postfix(ThreadDestructorFunction destructor, void* object, void* dsoSymbol) {
RegisterThreadExitHook();
ThreadDestructors().push_back({destructor, object, dsoSymbol});
return 0;
}
void APS5_VABI _sceLibcInternalThreadDtors_nid_postfix() {
RunThreadDestructors_nid_no_patch();
}
int APS5_VABI _sceLibcInternalForceTlsDestructor_nid_postfix(KernelModule handle) {
(void)handle;
NotImplemented_nid_no_patch(__func__);
return 0;
}
}