Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/libs/prx/libkernel/Pthread/include/Pthread.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
24 changes: 22 additions & 2 deletions core/libs/prx/libkernel/Pthread/src/Thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -922,6 +926,7 @@ static void RunThread(std::unique_ptr<ThreadArgs> args) {
#endif
#endif
finishThread(self, Aps5CallGuestEntry_nid_no_patch(entry, arg));
asm volatile("ldmxcsr %0" : : "m"(self->hostMxcsr));
CurrentGuestState().current = nullptr;
}

Expand Down Expand Up @@ -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<NativeThreadArgs> args(static_cast<NativeThreadArgs*>(opaque));
auto* self = args->guest->self;
self->hostMxcsr = incomingMxcsr;
try {
ULONG_PTR low = 0;
ULONG_PTR high = 0;
Expand All @@ -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<NativeThreadArgs> args(static_cast<NativeThreadArgs*>(opaque));
auto* self = args->guest->self;
self->hostMxcsr = incomingMxcsr;
try {
std::uintptr_t low = 0;
std::uintptr_t high = 0;
Expand Down Expand Up @@ -1117,6 +1129,7 @@ static void* StartNativeThread(void* opaque) {
RunThread(std::unique_ptr<ThreadArgs>(guest));
}
threadExitJump = nullptr;
asm volatile("ldmxcsr %0" : : "m"(self->hostMxcsr));
return nullptr;
}
#endif
Expand Down Expand Up @@ -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);
Expand All @@ -1359,6 +1373,9 @@ Pthread APS5_VABI scePthreadSelf() {
#ifdef _WIN32
if (!CurrentGuestState().current) {
auto adopted = std::make_unique<PthreadPrivate>();
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");
Expand All @@ -1374,6 +1391,9 @@ Pthread APS5_VABI scePthreadSelf() {
#else
if (!CurrentGuestState().current) {
adoptedThread = std::make_unique<PthreadPrivate>();
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);
Expand Down
90 changes: 89 additions & 1 deletion core/libs/tests/PthreadStart.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,31 @@
#include <windows.h>
#endif

static std::atomic<std::uint32_t> workerMxcsrInDtor{0};
static std::atomic<std::uint32_t> 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<void> checked;
std::size_t stackSize = 0;
bool exitExplicitly = false;
std::uint32_t incomingHostMxcsr = 0;
};

static void CheckLargeFrame() {
Expand All @@ -26,6 +46,11 @@ static void CheckLargeFrame() {
static void* APS5_VABI CheckThread(void* arg) {
auto& context = *static_cast<ThreadContext*>(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");
Expand Down Expand Up @@ -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);
Expand All @@ -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) {
Expand All @@ -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<bool> 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";
}
1 change: 1 addition & 0 deletions docs/dev/debt/libkernel.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading