Files
AnyPS5/core/libs/tests/GuestThreadAtexit.cpp
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

88 lines
2.6 KiB
C++

#include "SceTypes.hpp"
#include <cstdint>
#include <cstdlib>
#include <stdexcept>
#include <vector>
extern "C" {
int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name);
int APS5_VABI scePthreadJoin(Pthread thread, void** retval);
Pthread APS5_VABI scePthreadSelf();
int APS5_VABI _sceLibcInternalThreadAtexit_nid_postfix(void (APS5_VABI* destructor)(void*), void* object, void* dsoSymbol);
void APS5_VABI _sceLibcInternalThreadDtors_nid_postfix();
}
namespace {
using Destructor = void (APS5_VABI*)(void*);
void Require(bool value) { if (!value) std::abort(); }
int dsoHandle = 0;
std::vector<std::intptr_t> calls;
std::vector<Pthread> callers;
void* Object(std::intptr_t value) { return reinterpret_cast<void*>(value); }
int Register(Destructor destructor, std::intptr_t object) {
return _sceLibcInternalThreadAtexit_nid_postfix(destructor, Object(object), &dsoHandle);
}
void APS5_VABI Record(void* object) {
calls.push_back(reinterpret_cast<std::intptr_t>(object));
callers.push_back(scePthreadSelf());
}
void APS5_VABI RecordAndRegister(void* object) {
Record(object);
Require(Register(Record, 9) == 0);
}
void* APS5_VABI Worker(void*) {
Require(Register(Record, 4) == 0);
Require(Register(Record, 5) == 0);
return nullptr;
}
bool DestructorsThrow() {
try {
_sceLibcInternalThreadDtors_nid_postfix();
} catch (const std::runtime_error&) {
return true;
}
return false;
}
}
int main() {
const Pthread mainThread = scePthreadSelf();
Require(Register(Record, 1) == 0);
Require(Register(RecordAndRegister, 2) == 0);
Require(Register(Record, 3) == 0);
_sceLibcInternalThreadDtors_nid_postfix();
Require(calls == std::vector<std::intptr_t>{3, 2, 9, 1});
Require(callers == std::vector<Pthread>(4, mainThread));
_sceLibcInternalThreadDtors_nid_postfix();
Require(calls.size() == 4);
calls.clear();
callers.clear();
Pthread worker = nullptr;
Require(scePthreadCreate(&worker, nullptr, Worker, nullptr, "ThreadAtexit") == 0);
Require(scePthreadJoin(worker, nullptr) == 0);
Require(calls == std::vector<std::intptr_t>{5, 4});
Require(callers == std::vector<Pthread>(2, worker));
calls.clear();
std::vector<unsigned char> heap(64);
Require(Register(Record, 6) == 0);
Require(Register(nullptr, 7) == 0);
Require(Register(reinterpret_cast<Destructor>(heap.data()), 8) == 0);
Require(DestructorsThrow());
Require(DestructorsThrow());
Require(calls.empty());
_sceLibcInternalThreadDtors_nid_postfix();
Require(calls == std::vector<std::intptr_t>{6});
}