diff --git a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp index 0f1c2d83fdd..9963c84122e 100644 --- a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp @@ -175,6 +175,7 @@ struct PthreadPrivate { std::mutex cancelLock; TimedWait::Condition* cancelWait = nullptr; bool cancelWaitExiting = false; + std::uint32_t hostMxcsr = 0x1f80; explicit PthreadPrivate(const PthreadAttrPrivate* attr = nullptr); ~PthreadPrivate(); diff --git a/core/libs/prx/libkernel/Pthread/src/Thread.cpp b/core/libs/prx/libkernel/Pthread/src/Thread.cpp index 5c77029fcdd..6cd62ac8486 100644 --- a/core/libs/prx/libkernel/Pthread/src/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Thread.cpp @@ -879,11 +879,15 @@ asm(".text\n" push $0 .cfi_adjust_cfa_offset 8 mov %rsp, %rbp + sub $16, %rsp + .cfi_adjust_cfa_offset 16 + movl $0x9fc0, (%rsp) + ldmxcsr (%rsp) mov %rdi, %rax mov %rsi, %rdi call *%rax - add $16, %rsp - .cfi_adjust_cfa_offset -16 + add $32, %rsp + .cfi_adjust_cfa_offset -32 pop %rbp .cfi_adjust_cfa_offset -8 .cfi_restore %rbp @@ -922,6 +926,7 @@ static void RunThread(std::unique_ptr args) { #endif #endif finishThread(self, Aps5CallGuestEntry_nid_no_patch(entry, arg)); + asm volatile("ldmxcsr %0" : : "m"(self->hostMxcsr)); CurrentGuestState().current = nullptr; } @@ -1048,8 +1053,11 @@ static std::wstring ThreadDescription(const std::string& name) { } static unsigned __stdcall StartNativeThread(void* opaque) { + std::uint32_t incomingMxcsr = 0; + asm volatile("stmxcsr %0" : "=m"(incomingMxcsr)); std::unique_ptr args(static_cast(opaque)); auto* self = args->guest->self; + self->hostMxcsr = incomingMxcsr; try { ULONG_PTR low = 0; ULONG_PTR high = 0; @@ -1076,13 +1084,17 @@ static unsigned __stdcall StartNativeThread(void* opaque) { args.reset(); RunThread(std::move(guest)); CurrentGuestState().current = nullptr; + asm volatile("ldmxcsr %0" : : "m"(self->hostMxcsr)); ReleaseThread(self); return 0; } #else static void* StartNativeThread(void* opaque) { + std::uint32_t incomingMxcsr = 0; + asm volatile("stmxcsr %0" : "=m"(incomingMxcsr)); std::unique_ptr args(static_cast(opaque)); auto* self = args->guest->self; + self->hostMxcsr = incomingMxcsr; try { std::uintptr_t low = 0; std::uintptr_t high = 0; @@ -1117,6 +1129,7 @@ static void* StartNativeThread(void* opaque) { RunThread(std::unique_ptr(guest)); } threadExitJump = nullptr; + asm volatile("ldmxcsr %0" : : "m"(self->hostMxcsr)); return nullptr; } #endif @@ -1344,6 +1357,7 @@ void APS5_VABI scePthreadExit(void* retval) { while (CurrentGuestState().cleanupHandlers) __pthread_cleanup_pop_imp_nid_postfix(1); finishThread(self, retval); CurrentGuestState().current = nullptr; + asm volatile("ldmxcsr %0" : : "m"(self->hostMxcsr)); #ifdef _WIN32 ReleaseThread(self); _endthreadex(0); @@ -1359,6 +1373,9 @@ Pthread APS5_VABI scePthreadSelf() { #ifdef _WIN32 if (!CurrentGuestState().current) { auto adopted = std::make_unique(); + std::uint32_t currentMxcsr = 0x1f80; + asm volatile("stmxcsr %0" : "=m"(currentMxcsr)); + adopted->hostMxcsr = currentMxcsr; HANDLE handle = nullptr; if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &handle, 0, FALSE, DUPLICATE_SAME_ACCESS)) throw std::system_error(GetLastError(), std::system_category(), "Adopting guest thread"); @@ -1374,6 +1391,9 @@ Pthread APS5_VABI scePthreadSelf() { #else if (!CurrentGuestState().current) { adoptedThread = std::make_unique(); + std::uint32_t currentMxcsr = 0x1f80; + asm volatile("stmxcsr %0" : "=m"(currentMxcsr)); + adoptedThread->hostMxcsr = currentMxcsr; adoptedThread->_detached = true; adoptedThread->_adopted = true; adoptedThread->initialThread.store(IsInitialPthread_nid_no_patch(), std::memory_order_release); diff --git a/core/libs/tests/PthreadStart.cpp b/core/libs/tests/PthreadStart.cpp index 8dac3f063ad..75f186bb56c 100644 --- a/core/libs/tests/PthreadStart.cpp +++ b/core/libs/tests/PthreadStart.cpp @@ -8,11 +8,31 @@ #include #endif +static std::atomic workerMxcsrInDtor{0}; +static std::atomic workerMxcsrAfterReturn{0}; + +struct WorkerTlsHook { + bool active = false; + ~WorkerTlsHook() { + if (!active) return; + std::uint32_t mxcsr = 0; + asm volatile("stmxcsr %0" : "=m"(mxcsr)); + workerMxcsrAfterReturn.store(mxcsr, std::memory_order_relaxed); + } +}; +static thread_local WorkerTlsHook workerTlsHook; + +static void APS5_VABI WorkerThreadDtor() { + std::uint32_t mxcsr = 0; + asm volatile("stmxcsr %0" : "=m"(mxcsr)); + workerMxcsrInDtor.store(mxcsr, std::memory_order_relaxed); +} struct ThreadContext { Pthread thread = nullptr; std::promise checked; std::size_t stackSize = 0; bool exitExplicitly = false; + std::uint32_t incomingHostMxcsr = 0; }; static void CheckLargeFrame() { @@ -26,6 +46,11 @@ static void CheckLargeFrame() { static void* APS5_VABI CheckThread(void* arg) { auto& context = *static_cast(arg); if (!context.thread) throw std::runtime_error("Thread handle was not published"); + std::uint32_t threadMxcsr = 0; + asm volatile("stmxcsr %0" : "=m"(threadMxcsr)); + if ((threadMxcsr & 0x9fc0) != 0x9fc0) throw std::runtime_error("Guest thread MXCSR does not have FTZ and DAZ bits set"); + workerTlsHook.active = true; + context.incomingHostMxcsr = context.thread->hostMxcsr; #ifdef _WIN32 if (scePthreadSelf() != context.thread) throw std::runtime_error("Current guest thread handle is incorrect"); @@ -62,6 +87,9 @@ static void* APS5_VABI CheckThread(void* arg) { } int main() { + std::uint32_t hostMxcsrBefore = 0; + asm volatile("stmxcsr %0" : "=m"(hostMxcsrBefore)); + sceKernelSetThreadDtors(WorkerThreadDtor); for (int iteration = 0; iteration < 10000; ++iteration) { ThreadContext context; context.stackSize = (iteration % 2 + 1) * (1u << 20); @@ -76,6 +104,10 @@ int main() { void* result = nullptr; if (scePthreadJoin(context.thread, &result) != 0) throw std::runtime_error("Thread join failed"); if (result != &context) throw std::runtime_error("Thread return value was lost"); + if ((workerMxcsrInDtor.load() & 0x9fc0) != 0x9fc0) + throw std::runtime_error("Guest thread MXCSR was not active during thread cleanup"); + if (workerMxcsrAfterReturn.load() != hostMxcsrBefore) + throw std::runtime_error("Worker thread host MXCSR was not restored upon guest return"); } #ifdef _WIN32 for (int iteration = 0; iteration < 100; ++iteration) { @@ -91,5 +123,61 @@ int main() { context.checked.get_future().get(); } #endif - std::cout << "PASS: 10000 thread starts, committed stacks, large frames, explicit exits and joins; 100 detach checks\n"; + { + const std::uint32_t guestMxcsr = 0x9fc0; + asm volatile("ldmxcsr %0" : : "m"(guestMxcsr)); + ThreadContext context; + context.stackSize = 1u << 20; + if (scePthreadCreate(&context.thread, nullptr, CheckThread, &context, nullptr) != 0) + throw std::runtime_error("Thread creation from a guest-MXCSR creator failed"); + context.checked.get_future().get(); + void* result = nullptr; + if (scePthreadJoin(context.thread, &result) != 0) throw std::runtime_error("Thread join failed"); + asm volatile("ldmxcsr %0" : : "m"(hostMxcsrBefore)); +#ifndef _WIN32 + if (context.incomingHostMxcsr != guestMxcsr) + throw std::runtime_error("Worker incoming MXCSR did not match creator MXCSR on POSIX"); +#endif + if (workerMxcsrAfterReturn.load() != context.incomingHostMxcsr) + throw std::runtime_error("Worker host MXCSR was not preserved from captured incoming state"); + } + { + const std::uint32_t guestMxcsr = 0x9fc0; + asm volatile("ldmxcsr %0" : : "m"(guestMxcsr)); + ThreadContext context; + context.stackSize = 1u << 20; + context.exitExplicitly = true; + if (scePthreadCreate(&context.thread, nullptr, CheckThread, &context, nullptr) != 0) + throw std::runtime_error("Thread creation from a guest-MXCSR creator failed"); + context.checked.get_future().get(); + void* result = nullptr; + if (scePthreadJoin(context.thread, &result) != 0) throw std::runtime_error("Thread join failed"); + asm volatile("ldmxcsr %0" : : "m"(hostMxcsrBefore)); +#ifndef _WIN32 + if (context.incomingHostMxcsr != guestMxcsr) + throw std::runtime_error("Worker incoming MXCSR did not match creator MXCSR on POSIX"); +#endif + if (workerMxcsrAfterReturn.load() != context.incomingHostMxcsr) + throw std::runtime_error("Worker host MXCSR was not preserved from captured incoming state upon explicit exit"); + } + std::uint32_t hostMxcsrAfter = 0; + asm volatile("stmxcsr %0" : "=m"(hostMxcsrAfter)); + if (hostMxcsrAfter != hostMxcsrBefore) + throw std::runtime_error("Host MXCSR was modified by guest execution"); + std::atomic adoptedCompleted{false}; + std::thread nativeWorker([&] { + workerTlsHook.active = true; + const std::uint32_t adoptedMxcsr = 0x1fc0; + asm volatile("ldmxcsr %0" : : "m"(adoptedMxcsr)); + Pthread adoptedSelf = scePthreadSelf(); + if (!adoptedSelf) throw std::runtime_error("Thread adoption failed"); + adoptedCompleted.store(true); + scePthreadExit(nullptr); + }); + nativeWorker.join(); + if (!adoptedCompleted.load()) + throw std::runtime_error("Adopted thread failed to execute"); + if (workerMxcsrAfterReturn.load() != 0x1fc0) + throw std::runtime_error("Adopted thread host MXCSR was corrupted upon scePthreadExit"); + std::cout << "PASS: 10000 thread starts, committed stacks, large frames, explicit exits and joins; 100 detach checks; adopted thread exit\n"; } diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index acde30ae5d4..9723fac4172 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -28,6 +28,7 @@ - [sceKernelHasTrinityMode](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - returns 0, a base PS5's answer; a PS5 Pro is not modelled. Not measured on a console; the module's body was not read here. [Backport](https://github.com/danthespal/Backport/blob/5ba843c5ea51caad30ac05e830302bc1b8707f3c/FINDINGS.md?plain=1#L246) (11.00 to 12.70) says it reads `hw.sce_main_socid`; only [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/03484809c29fee4dc9fc3a4d61743749899ef5ca/src/driver_prospero.c#L333-L340) (firmware 11.60) gives the test, that id with the low five bits cleared compared with 0x840fc0, which the base PS5 SoC id 0x840f80 (`sceKernelGetMainSocId`) fails. The prototype `int (void)` comes from PPSA34114 01.000.005 (0x8837e3, 0x67db227, 0x78dfa9e): no argument is set and the result is stored as `setne` of `test eax, eax` - [sceCoredumpRegisterCoredumpHandler, sceCoredumpUnregisterCoredumpHandler](../../../core/libs/prx/libkernel/System/src/Coredump.cpp) retain registration metadata but do not create the native report thread or deliver callbacks. Native registration-state precedence, stack bounds and SDK capture are recovered; thread-creation failures and active-report synchronization remain unimplemented. A registered handler makes the next registration return `0x81180002`; otherwise a null handler or a stack size below 0x4000 or from 0x20000000 on returns `SCE_COREDUMP_ERROR_INVALID_ARGUMENT` (0x81180000). Every direct call in the titles checked passes a constant stack size inside that range; the handler of a call is not read, and the titles that reach the function through a tail jump are not known. - [scePthreadMutexattrSetkind, scePthreadMutexattrGetkind](../../../core/libs/prx/libkernel/Pthread/src/Mutex.cpp) (libkernel) - not measured on a console or in a title process, only by running firmware 13.42's `libkernel.sprx` and `libkernel_sys.sprx` in an emulator ([#9632](https://github.com/boykopovar/AnyPS5/pull/9632#issuecomment-6363402814), [#10725](https://github.com/boykopovar/AnyPS5/pull/10725#issuecomment-6397556344)). `scePthreadMutexattrGetkind` of a negative stored kind returns 0x80020000 plus the kind, which follows the measured rule for other kinds but was not measured. `scePthreadMutexattrSetkind` stores any kind through `pthread_mutexattr_setkind_np`; for a kind outside 1 to 4 only the refusal by `scePthreadMutexInit` and `pthread_mutex_init` was measured +- [Guest thread MXCSR initial state](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) - measured on PS5 firmware 12.70: threads of a PS5 payload process start with MXCSR 0x9fc0 (Flush-to-Zero and Denormals-are-Zero enabled, all FP exceptions masked). Created guest threads initialize 0x9fc0 in Aps5CallGuestEntry_nid_no_patch, retain 0x9fc0 across thread destruction and TSD key destructors in finishThread, and restore the host worker MXCSR upon completion of the thread across both explicit exit and normal return paths; the main ELF thread entry stub is handled separately in #7594 and is not covered here - [sceKernelError](../../../core/libs/prx/libkernel/System/src/Error.cpp) follows [shadPS4's kernel implementation](https://github.com/shadps4-emu/shadPS4/blob/23c41333822fe8eca4c609b43119180af63ae4ac/src/core/libraries/kernel/kernel.cpp): zero returns zero; other guest POSIX error values receive the `0x80020000` bias without modifying errno. Arithmetic is defined modulo 32 bits to avoid signed overflow for inputs outside the errno domain. Console behavior for those inputs and PS5 equivalence have not been measured. - [sceKernelSendNotificationRequest](../../../core/libs/prx/libkernel/System/src/Notification.cpp) (libkernel) - prints the text at +0x2d of a 0xc30-byte request of kind 0 or 5 (as `sceSysUtilSendSystemNotificationWithText` sends) to the host's standard error, as there is no system UI. Other kinds, a set byte at +0x2c, another size, device or flags are refused. Firmware 13.42's libkernel only writes the request to a notification descriptor, retrying on `EINTR`, and returns 0 or `0x80020000 | errno` ([@BunnyFl](https://github.com/boykopovar/AnyPS5/pull/9081#issuecomment-6418015178)), so those refusals are this library's; what the console's notification device accepts is not known. An unreadable or unmapped request buffer returns 0x8002000e as this implementation's host fallback; console fault behaviour is unmeasured.