#include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/HeapDiagnostics.hpp" #include "prx/libc/include/HostThreadLocal.hpp" #include #include #include #include #include #ifdef _WIN32 #include #else #include #endif extern "C" void APS5_VABI sceKernelSetThreadDtors(thread_dtors_func_t dtors); extern "C" int APS5_VABI sceKernelGetModuleInfoFromAddr(std::uint64_t address, int flags, ModuleInfoEx* info); namespace { using ThreadDestructorFunction = void (APS5_VABI*)(void*); struct ThreadDestructor { ThreadDestructorFunction function; void* object; void* dsoSymbol; }; struct ThreadDestructorsTag {}; std::vector& ThreadDestructors() { return HostThreadLocal, 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(address), &module) != 0; #else Dl_info info{}; return dladdr(address, &info) != 0; #endif } void CallThreadDestructor(const ThreadDestructor& destructor) { const auto* function = reinterpret_cast(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 APS5_VABI RunThreadDestructors_nid_no_patch() { auto& destructors = ThreadDestructors(); while (!destructors.empty()) { const ThreadDestructor destructor = destructors.back(); destructors.pop_back(); CallThreadDestructor(destructor); } } bool FindDsoModule(const void* dsoSymbol, KernelModule& handle) { ModuleInfoEx info{}; info.st_size = sizeof(ModuleInfoEx); if (sceKernelGetModuleInfoFromAddr(reinterpret_cast(dsoSymbol), 2, &info) != 0) { handle = 0; return false; } handle = info.id; return true; } bool ForceThreadDestructorPass(KernelModule handle) { auto& destructors = ThreadDestructors(); bool found = false; for (std::size_t index = destructors.size(); index-- > 0;) { const ThreadDestructor destructor = destructors[index]; KernelModule module = 0; const bool loaded = FindDsoModule(destructor.dsoSymbol, module); if (module != handle) continue; found = true; destructors.erase(destructors.begin() + static_cast(index)); if (loaded && *static_cast(destructor.dsoSymbol) == destructor.dsoSymbol) CallThreadDestructor(destructor); } return found; } 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) { for (int pass = 0; pass < 4; ++pass) { if (!ForceThreadDestructorPass(handle)) break; } return 0; } }