mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
Merge pull request #9269 from UnhealthyKraken/test/libc-crt-exit-lifecycle
test(libc): cover CRT exit and TLS cleanup lifecycle
This commit is contained in:
2 files changed
+82
-3
No files matched your search
@@ -5,15 +5,15 @@
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <barrier>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
|
||||
extern "C" {
|
||||
|
||||
}
|
||||
|
||||
static void Require(bool value) { if (!value) std::abort(); }
|
||||
|
||||
@@ -275,6 +275,80 @@ static unsigned APS5_VABI TestPadGo(GuestPad* base) {
|
||||
return 42;
|
||||
}
|
||||
|
||||
static void RequireExit(bool value, const char* message) {
|
||||
if (!value) {
|
||||
std::fprintf(stderr, "CRT exit lifecycle: %s\n", message);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
struct CrtExitState {
|
||||
int code = 0;
|
||||
int key = -1;
|
||||
std::barrier<>* ready = nullptr;
|
||||
void* parent = nullptr;
|
||||
void* self = nullptr;
|
||||
std::atomic<unsigned> destructors{0};
|
||||
std::atomic<bool> returned{false};
|
||||
};
|
||||
|
||||
static void APS5_VABI CrtExitDestructor(void* value) {
|
||||
auto& state = *static_cast<CrtExitState*>(value);
|
||||
RequireExit(_Thrd_current_nid_postfix() == state.self, "identity during destructor");
|
||||
RequireExit(_Tss_get_nid_postfix(state.key) == nullptr, "value cleared before destructor");
|
||||
if (state.destructors.fetch_add(1) == 0)
|
||||
RequireExit(_Tss_set_nid_postfix(state.key, &state) == 0, "reinstall value for another cleanup pass");
|
||||
}
|
||||
|
||||
static auto volatile crtExit = &_Thrd_exit_nid_postfix;
|
||||
|
||||
static void* APS5_VABI CrtExitEntry(void* value) {
|
||||
auto& state = *static_cast<CrtExitState*>(value);
|
||||
state.self = _Thrd_current_nid_postfix();
|
||||
RequireExit(state.self && _Thrd_equal_nid_postfix(state.self, state.self) == 1, "current identity");
|
||||
RequireExit(_Thrd_equal_nid_postfix(state.self, state.parent) == 0, "different parent identity");
|
||||
RequireExit(_Tss_get_nid_postfix(state.key) == nullptr, "child starts with empty value");
|
||||
RequireExit(_Tss_set_nid_postfix(state.key, &state) == 0, "set child value");
|
||||
state.ready->arrive_and_wait();
|
||||
RequireExit(_Tss_get_nid_postfix(state.key) == &state, "child value isolation");
|
||||
crtExit(state.code);
|
||||
state.returned = true;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void ThreadExitLifecycle() {
|
||||
constexpr std::array<int, 5> codes{0, 42, -1, std::numeric_limits<int>::min(), std::numeric_limits<int>::max()};
|
||||
constexpr std::array<std::uintptr_t, 5> expected{0, 42, 0xffffffffu, 0x80000000u, 0x7fffffffu};
|
||||
std::barrier ready(6);
|
||||
std::array<CrtExitState, 5> states;
|
||||
std::array<Pthread, 5> threads{};
|
||||
int key = -1, parentValue = 31;
|
||||
void* parent = _Thrd_current_nid_postfix();
|
||||
RequireExit(_Tss_create_nid_postfix(&key, CrtExitDestructor) == 0, "create destructor key");
|
||||
RequireExit(_Tss_set_nid_postfix(key, &parentValue) == 0, "set parent value");
|
||||
for (unsigned i = 0; i < states.size(); ++i) {
|
||||
states[i].code = codes[i];
|
||||
states[i].key = key;
|
||||
states[i].ready = &ready;
|
||||
states[i].parent = parent;
|
||||
RequireExit(scePthreadCreate(&threads[i], nullptr, CrtExitEntry, &states[i], "CRT exit audit") == 0, "create child");
|
||||
}
|
||||
RequireExit(_Tss_get_nid_postfix(key) == &parentValue, "parent isolated before release");
|
||||
ready.arrive_and_wait();
|
||||
for (unsigned i = 0; i < states.size(); ++i) {
|
||||
void* result = nullptr;
|
||||
RequireExit(scePthreadJoin(threads[i], &result) == 0, "join exiting child");
|
||||
RequireExit(reinterpret_cast<std::uintptr_t>(result) == expected[i], "full zero-extended exit result");
|
||||
RequireExit(!states[i].returned, "exit must not return");
|
||||
RequireExit(states[i].destructors == 2, "reinstalled destructor runs twice before join returns");
|
||||
RequireExit(states[i].self == threads[i], "created and current identity match");
|
||||
}
|
||||
RequireExit(_Tss_get_nid_postfix(key) == &parentValue, "parent isolated after children exit");
|
||||
RequireExit(_Tss_delete_nid_postfix(key) == 0, "delete key");
|
||||
RequireExit(_Tss_get_nid_postfix(key) == nullptr, "deleted key value unavailable");
|
||||
std::puts("PASS CRT lifecycle: five concurrent exit codes, full return width, identity, parent/child isolation, two destructor passes, key deletion");
|
||||
}
|
||||
|
||||
static void PadLaunchAndJoin() {
|
||||
unsigned (APS5_VABI* vtable[1])(GuestPad*) = {TestPadGo};
|
||||
TestPad unregPad{};
|
||||
@@ -508,6 +582,7 @@ static void ThreadHelpers() {
|
||||
}
|
||||
|
||||
int main() {
|
||||
ThreadExitLifecycle();
|
||||
KernelKeys();
|
||||
VersionedSync();
|
||||
ForwardedCalls();
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# libc technical debt
|
||||
|
||||
### Silent stubs
|
||||
|
||||
- [_Mtx_current_owns](../../../core/libs/prx/libc/src/Threads.cpp) (libSceLibcInternal, implemented in libc, NID `VYQwFs4CC4Y`) returns 1 and ignores its argument. This follows the PS5 12.00 body reading reported in [PR8859](https://github.com/boykopovar/AnyPS5/pull/8859), rather than inspecting host mutex ownership. The firmware observation is attributed to that contributor; other firmware versions and the argument's console type remain unverified.
|
||||
|
||||
### Unknown function info
|
||||
|
||||
- [strtonum](../../../core/libs/prx/libc/src/StringExtensions.cpp) (libc) is unverified on the console. Its signature, decimal parsing, inclusive bounds, error strings, error precedence and errno preservation follow [FreeBSD 11.4 strtonum.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdlib/strtonum.c). Parsing uses the host `strtoll` with base 10. It is not exported by `libSceLibcInternal.sprx` on a PS5 (firmware 13.60), so a payload process cannot call it there
|
||||
|
||||
Reference in new issue
Block a user