From d100cdaf52fd7e3b90a6c70c7673921f951fffb9 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Fri, 23 Oct 2026 22:19:19 +0200 Subject: [PATCH 1/6] feat(libkernel): implement pthread_mutex_setname_np --- core/libs/prx/libkernel/CMakeLists.txt | 10 ++++ .../prx/libkernel/Pthread/include/Pthread.hpp | 1 + core/libs/prx/libkernel/Pthread/src/Mutex.cpp | 12 +++++ .../prx/libkernel_sys/Export.Discovery.cpp | 5 -- core/libs/tests/GuestPthreadSetname.cpp | 54 +++++++++++++++++++ docs/dev/debt/libkernel.md | 1 + 6 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 core/libs/tests/GuestPthreadSetname.cpp diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index cd243bc51c..a6bac8957b 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -524,6 +524,16 @@ if(BUILD_TESTING) set_tests_properties(guest_pthread_barrier PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_pthread_setname_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadSetname.cpp) + target_include_directories(guest_pthread_setname_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_pthread_setname_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_pthread_setname_tests) + add_test(NAME guest_pthread_setname COMMAND guest_pthread_setname_tests) + set_tests_properties(guest_pthread_setname PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_pthread_setname PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_kernel_is_stack_tests ${LIBS_INCLUDE_DIR}/tests/GuestKernelIsStack.cpp) target_include_directories(guest_kernel_is_stack_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_kernel_is_stack_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp index 097d609562..5a70393fae 100644 --- a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp @@ -47,6 +47,7 @@ struct PthreadMutexPrivate { int _count; int _spinloops = 0; int _yieldloops = 0; + std::string _name; PthreadMutexPrivate() : _type(MutexType::ErrorCheck), _owner(std::thread::id{}), _count(0) {} diff --git a/core/libs/prx/libkernel/Pthread/src/Mutex.cpp b/core/libs/prx/libkernel/Pthread/src/Mutex.cpp index 62e5e38db1..a3bd9370f5 100644 --- a/core/libs/prx/libkernel/Pthread/src/Mutex.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Mutex.cpp @@ -282,4 +282,16 @@ int APS5_VABI scePthreadMutexSetyieldloops(PthreadMutex* mutex, int count) { return 0; } +int APS5_VABI pthread_mutex_setname_np_nid_postfix(PthreadMutex* mutex, const char* name) { + if (!mutex) + throw std::invalid_argument("pthread_mutex_setname_np: null mutex"); + std::lock_guard lock(InitializationMutex()); + const auto current = std::atomic_ref(*mutex).load(std::memory_order_acquire); + if (!isInitializedMutex(current)) + throw std::invalid_argument("pthread_mutex_setname_np: mutex is statically initialized or destroyed"); + if (name) + current->_name = name; + return 0; +} + } diff --git a/core/libs/prx/libkernel_sys/Export.Discovery.cpp b/core/libs/prx/libkernel_sys/Export.Discovery.cpp index 2cb89a6cb3..ee299fbeee 100644 --- a/core/libs/prx/libkernel_sys/Export.Discovery.cpp +++ b/core/libs/prx/libkernel_sys/Export.Discovery.cpp @@ -326,11 +326,6 @@ int APS5_VABI pthread_mutex_init_for_mono_nid_postfix(void) { return 0; } -int APS5_VABI pthread_mutex_setname_np_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI pthread_mutex_setprioceiling_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/tests/GuestPthreadSetname.cpp b/core/libs/tests/GuestPthreadSetname.cpp new file mode 100644 index 0000000000..21764aa925 --- /dev/null +++ b/core/libs/tests/GuestPthreadSetname.cpp @@ -0,0 +1,54 @@ +#include "SceTypes.hpp" +#include "prx/libkernel/Pthread/include/Pthread.hpp" +#include +#include +#include +#include +#include + +extern "C" { +int APS5_VABI pthread_mutex_init_nid_postfix(PthreadMutex*, const PthreadMutexattr*); +int APS5_VABI pthread_mutex_destroy_nid_postfix(PthreadMutex*); +int APS5_VABI pthread_mutex_lock_nid_postfix(PthreadMutex*); +int APS5_VABI pthread_mutex_unlock_nid_postfix(PthreadMutex*); +int APS5_VABI pthread_mutex_setname_np_nid_postfix(PthreadMutex*, const char*); +} + +static constexpr int GuestEinval = 22; + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "pthread setname check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestMutex() { + PthreadMutex mutex = nullptr; + Require(pthread_mutex_init_nid_postfix(&mutex, nullptr) == 0); + Require(pthread_mutex_setname_np_nid_postfix(&mutex, "first") == 0 && mutex->_name == "first"); + Require(pthread_mutex_setname_np_nid_postfix(&mutex, "second") == 0 && mutex->_name == "second"); + Require(pthread_mutex_setname_np_nid_postfix(&mutex, nullptr) == 0 && mutex->_name == "second"); + Require(pthread_mutex_lock_nid_postfix(&mutex) == 0); + Require(pthread_mutex_setname_np_nid_postfix(&mutex, "held") == 0 && mutex->_name == "held"); + Require(pthread_mutex_unlock_nid_postfix(&mutex) == 0); + RequireThrows([] { pthread_mutex_setname_np_nid_postfix(nullptr, "mutex"); }); + Require(pthread_mutex_destroy_nid_postfix(&mutex) == 0); + RequireThrows([&] { pthread_mutex_setname_np_nid_postfix(&mutex, "destroyed"); }); + + PthreadMutex unset = nullptr; + RequireThrows([&] { pthread_mutex_setname_np_nid_postfix(&unset, "static"); }); + Require(unset == nullptr); + PthreadMutex adaptive = reinterpret_cast(std::uintptr_t{1}); + RequireThrows([&] { pthread_mutex_setname_np_nid_postfix(&adaptive, "adaptive"); }); + Require(reinterpret_cast(adaptive) == 1); +} + +int main() { + TestMutex(); + std::puts("pthread setname tests passed"); +} diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 81edeb1e48..d44757590e 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -287,6 +287,7 @@ - [pthread_rwlockattr_gettype_np, scePthreadRwlockattrGettype](../../../core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp) (libkernel) report the type stored by `scePthreadRwlockattrSettype`, which accepts any value and which read-write locks do not apply; the console's valid types and their effect are unverified. A null or uninitialized attribute throws, as for `pthread_rwlockattr_getpshared` - [sceKernelGetEventError](../../../core/libs/prx/libkernel/Equeue/src/EventAccessors.cpp) (libkernel) throws for `fflags` above 0xFFFF, where OR and addition of 0x80020000 differ and the console was not measured. Otherwise it returns 0 for `fflags` 0 and `0x80020000 | fflags`, whatever `flags` and `data` hold, as [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_queue.pas#L643) does and a PS5 (firmware 13.40, measured 2026-10-19 in a payload process) answered: `{flags 0x4000, fflags 0, data 22}`, `{0x4000, 0, -1}`, `{0x4000, 0, 0x80020016}` and `{0, 0, 0}` gave 0, `{0x4000, 5, 0}` and `{0x4000, 5, 22}` 0x80020005, `{0, 7, 22}` 0x80020007, `{0x8000, 3, 9}` 0x80020003. Video-out events carry their count in `fflags`, so they get 0x80020001 and up; only events built by the caller were measured. Closes when: `fflags` above 0xFFFF is measured - [pthread_cond_reltimedwait_np](../../../core/libs/prx/libkernel/Pthread/Posix/Cond.cpp) (libkernel) takes `(cond, mutex, usec)`, a relative timeout in microseconds, and is `scePthreadCondTimedwait` with POSIX error numbers. FreeBSD 11.4 has no such function; the signature comes only from shadPS4, which registers one function for this NID and for `scePthreadCondTimedwait` ([`condvar.cpp`](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/libraries/kernel/threads/condvar.cpp#L229-L234)). shadPS4 reads a 64-bit count; here it is the 32-bit `SceKernelUseconds` of the SCE twin, as for `sem_reltimedwait_np`. Unverified on the console +- [pthread_mutex_setname_np](../../../core/libs/prx/libkernel/Pthread/src/Mutex.cpp) (libkernel) takes `(mutex, name)` and copies `name` onto an initialized mutex, returning 0. A null `name` returns 0 and leaves the name; [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/kernel/threads/mutex.cpp#L105-L113) and [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_mutex.pas#L481-L494) both return 0 there (shadPS4 stores a default name, fpPS4 keeps the old one). Nothing reads the name back, and `scePthreadMutexInit` still ignores its name. A null mutex pointer, a statically initialized mutex and a destroyed one throw, because the two disagree or fault: fpPS4 returns `EINVAL`, shadPS4 returns 0 without naming or dereferences null. Unverified on the console - [pthread_mutex_reltimedlock_np](../../../core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp) (libkernel) takes `(mutex, usec)`, a relative timeout in microseconds, and is `scePthreadMutexTimedlock` with POSIX error numbers. FreeBSD 11.4 has no such function; the signature comes only from shadPS4, which registers one function for this NID and for `scePthreadMutexTimedlock` ([`mutex.cpp`](https://github.com/shadps4-emu/shadPS4/blob/7c960eae136b956cf4c1d72aacd2426b650330df/src/core/libraries/kernel/threads/mutex.cpp#L284-L287)). shadPS4 reads a 64-bit count; here it is the 32-bit `SceKernelUseconds` of the SCE twin, as for `pthread_cond_reltimedwait_np`, so a count of 2^32 microseconds or more would be truncated if the console reads 64 bits. Unverified on the console - [sysctl kern.arandom](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) serves FreeBSD 11's MIB {1, 37} (CTL_KERN, KERN_ARANDOM) and the "kern.arandom" sysctlbyname lookup through std::random_device, returning min(*oldLength, 256) random bytes. Following FreeBSD's sysctl_kern_arnd, a null buffer reports min(*oldLength, 256) without writing, a null length returns 0 without writing, and requests with a new value return EPERM (1) as the node is read-only. Cryptographic quality depends on the host's random device implementation. - [pthread_atfork](../../../core/libs/prx/libkernel/Pthread/Posix/Atfork.cpp) (libkernel) appends the three handlers to a process-wide list and returns 0, or `ENOMEM` when the entry cannot be allocated, as FreeBSD 11.4's [`_pthread_atfork`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_fork.c#L78-L100) does; null handlers are accepted. The handlers never run: `fork` throws and `vfork` is absent, so no child process is created. Handlers of a module that is unloaded stay in the list, where FreeBSD's `__pthread_cxa_finalize` removes them. PPSA97358 has a source-level call from the constructor `rcu_init` ([rcu.c](https://github.com/ZiZc3/XPSemu/blob/8eec07b4e31f66dce8e0b05c1ca1281af303e933/util/rcu.c#L488-L495)), but current-main runtime reachability is unverified From a27cecc9502f1bad7e52739c31f7cc5bfc11cd56 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Fri, 23 Oct 2026 22:19:19 +0200 Subject: [PATCH 2/6] feat(libkernel): implement pthread_rwlock_setname_np --- .../prx/libkernel/Pthread/include/Pthread.hpp | 1 + .../libs/prx/libkernel/Pthread/src/Rwlock.cpp | 12 ++++++++++ .../prx/libkernel_sys/Export.Discovery.cpp | 5 ---- core/libs/tests/GuestPthreadSetname.cpp | 23 +++++++++++++++++++ docs/dev/debt/libkernel.md | 1 + 5 files changed, 37 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp index 5a70393fae..586b6d86ae 100644 --- a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp @@ -67,6 +67,7 @@ struct PthreadRwlockPrivate { std::atomic _writer; std::atomic _readers{0}; int _waitingWriters = 0; + std::string _name; }; struct PthreadCondattrPrivate { diff --git a/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp b/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp index 8d06b7e9ea..3a7cb12ab2 100644 --- a/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp @@ -17,6 +17,7 @@ static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = 0x8002003C; static constexpr int SCE_KERNEL_ERROR_EINVAL = 0x80020016; static constexpr int FIRST_STORED_RWLOCK_TYPE = 1; static constexpr int LAST_STORED_RWLOCK_TYPE = 2; +static constexpr int POSIX_EINVAL = 22; static PthreadRwlockPrivate* const DestroyedRwlock = reinterpret_cast(std::uintptr_t{1}); @@ -188,4 +189,15 @@ int APS5_VABI scePthreadRwlockattrSettype(PthreadRwlockattr* attr, int type) { return SCE_OK; } +int APS5_VABI pthread_rwlock_setname_np_nid_postfix(PthreadRwlock* rwlock, const char* name) { + if (!name) return POSIX_EINVAL; + if (!rwlock) throw std::runtime_error("pthread_rwlock_setname_np: null rwlock"); + PthreadRwlock current = std::atomic_ref(*rwlock).load(std::memory_order_acquire); + if (current == nullptr) return SCE_OK; + if (current == DestroyedRwlock) throw std::runtime_error("pthread_rwlock_setname_np: destroyed rwlock"); + std::lock_guard state(current->_state); + current->_name = name; + return SCE_OK; +} + } diff --git a/core/libs/prx/libkernel_sys/Export.Discovery.cpp b/core/libs/prx/libkernel_sys/Export.Discovery.cpp index ee299fbeee..cd46940a07 100644 --- a/core/libs/prx/libkernel_sys/Export.Discovery.cpp +++ b/core/libs/prx/libkernel_sys/Export.Discovery.cpp @@ -357,11 +357,6 @@ int APS5_VABI pthread_resume_user_context_np_nid_postfix(void) { return 0; } -int APS5_VABI pthread_rwlock_setname_np_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI pthread_set_user_context_np_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/tests/GuestPthreadSetname.cpp b/core/libs/tests/GuestPthreadSetname.cpp index 21764aa925..6bb70315c9 100644 --- a/core/libs/tests/GuestPthreadSetname.cpp +++ b/core/libs/tests/GuestPthreadSetname.cpp @@ -12,6 +12,11 @@ int APS5_VABI pthread_mutex_destroy_nid_postfix(PthreadMutex*); int APS5_VABI pthread_mutex_lock_nid_postfix(PthreadMutex*); int APS5_VABI pthread_mutex_unlock_nid_postfix(PthreadMutex*); int APS5_VABI pthread_mutex_setname_np_nid_postfix(PthreadMutex*, const char*); +int APS5_VABI pthread_rwlock_init_nid_postfix(PthreadRwlock*, const PthreadRwlockattr*); +int APS5_VABI pthread_rwlock_destroy_nid_postfix(PthreadRwlock*); +int APS5_VABI pthread_rwlock_rdlock_nid_postfix(PthreadRwlock*); +int APS5_VABI pthread_rwlock_unlock_nid_postfix(PthreadRwlock*); +int APS5_VABI pthread_rwlock_setname_np_nid_postfix(PthreadRwlock*, const char*); } static constexpr int GuestEinval = 22; @@ -48,7 +53,25 @@ static void TestMutex() { Require(reinterpret_cast(adaptive) == 1); } +static void TestRwlock() { + PthreadRwlock rwlock = nullptr; + Require(pthread_rwlock_init_nid_postfix(&rwlock, nullptr) == 0); + Require(pthread_rwlock_setname_np_nid_postfix(&rwlock, "first") == 0 && rwlock->_name == "first"); + Require(pthread_rwlock_setname_np_nid_postfix(&rwlock, nullptr) == GuestEinval && rwlock->_name == "first"); + Require(pthread_rwlock_rdlock_nid_postfix(&rwlock) == 0); + Require(pthread_rwlock_setname_np_nid_postfix(&rwlock, "read") == 0 && rwlock->_name == "read"); + Require(pthread_rwlock_unlock_nid_postfix(&rwlock) == 0); + Require(pthread_rwlock_setname_np_nid_postfix(nullptr, nullptr) == GuestEinval); + RequireThrows([] { pthread_rwlock_setname_np_nid_postfix(nullptr, "rwlock"); }); + Require(pthread_rwlock_destroy_nid_postfix(&rwlock) == 0); + RequireThrows([&] { pthread_rwlock_setname_np_nid_postfix(&rwlock, "destroyed"); }); + + PthreadRwlock unset = nullptr; + Require(pthread_rwlock_setname_np_nid_postfix(&unset, "static") == 0 && unset == nullptr); +} + int main() { TestMutex(); + TestRwlock(); std::puts("pthread setname tests passed"); } diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index d44757590e..f3ed21a362 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -288,6 +288,7 @@ - [sceKernelGetEventError](../../../core/libs/prx/libkernel/Equeue/src/EventAccessors.cpp) (libkernel) throws for `fflags` above 0xFFFF, where OR and addition of 0x80020000 differ and the console was not measured. Otherwise it returns 0 for `fflags` 0 and `0x80020000 | fflags`, whatever `flags` and `data` hold, as [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_queue.pas#L643) does and a PS5 (firmware 13.40, measured 2026-10-19 in a payload process) answered: `{flags 0x4000, fflags 0, data 22}`, `{0x4000, 0, -1}`, `{0x4000, 0, 0x80020016}` and `{0, 0, 0}` gave 0, `{0x4000, 5, 0}` and `{0x4000, 5, 22}` 0x80020005, `{0, 7, 22}` 0x80020007, `{0x8000, 3, 9}` 0x80020003. Video-out events carry their count in `fflags`, so they get 0x80020001 and up; only events built by the caller were measured. Closes when: `fflags` above 0xFFFF is measured - [pthread_cond_reltimedwait_np](../../../core/libs/prx/libkernel/Pthread/Posix/Cond.cpp) (libkernel) takes `(cond, mutex, usec)`, a relative timeout in microseconds, and is `scePthreadCondTimedwait` with POSIX error numbers. FreeBSD 11.4 has no such function; the signature comes only from shadPS4, which registers one function for this NID and for `scePthreadCondTimedwait` ([`condvar.cpp`](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/libraries/kernel/threads/condvar.cpp#L229-L234)). shadPS4 reads a 64-bit count; here it is the 32-bit `SceKernelUseconds` of the SCE twin, as for `sem_reltimedwait_np`. Unverified on the console - [pthread_mutex_setname_np](../../../core/libs/prx/libkernel/Pthread/src/Mutex.cpp) (libkernel) takes `(mutex, name)` and copies `name` onto an initialized mutex, returning 0. A null `name` returns 0 and leaves the name; [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/kernel/threads/mutex.cpp#L105-L113) and [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_mutex.pas#L481-L494) both return 0 there (shadPS4 stores a default name, fpPS4 keeps the old one). Nothing reads the name back, and `scePthreadMutexInit` still ignores its name. A null mutex pointer, a statically initialized mutex and a destroyed one throw, because the two disagree or fault: fpPS4 returns `EINVAL`, shadPS4 returns 0 without naming or dereferences null. Unverified on the console +- [pthread_rwlock_setname_np](../../../core/libs/prx/libkernel/Pthread/src/Rwlock.cpp) (libkernel) follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/kernel/threads/rwlock.cpp#L79-L87), the only source: a null `name` returns `EINVAL` before the lock is read, a statically initialized lock returns 0 without being created or named, and an initialized one gets a copy of `name` and returns 0. Nothing reads the name back, and `scePthreadRwlockInit` still ignores its name. A null lock pointer, which shadPS4 dereferences, and a destroyed lock, which shadPS4 does not distinguish from a static one, throw. Unverified on the console - [pthread_mutex_reltimedlock_np](../../../core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp) (libkernel) takes `(mutex, usec)`, a relative timeout in microseconds, and is `scePthreadMutexTimedlock` with POSIX error numbers. FreeBSD 11.4 has no such function; the signature comes only from shadPS4, which registers one function for this NID and for `scePthreadMutexTimedlock` ([`mutex.cpp`](https://github.com/shadps4-emu/shadPS4/blob/7c960eae136b956cf4c1d72aacd2426b650330df/src/core/libraries/kernel/threads/mutex.cpp#L284-L287)). shadPS4 reads a 64-bit count; here it is the 32-bit `SceKernelUseconds` of the SCE twin, as for `pthread_cond_reltimedwait_np`, so a count of 2^32 microseconds or more would be truncated if the console reads 64 bits. Unverified on the console - [sysctl kern.arandom](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) serves FreeBSD 11's MIB {1, 37} (CTL_KERN, KERN_ARANDOM) and the "kern.arandom" sysctlbyname lookup through std::random_device, returning min(*oldLength, 256) random bytes. Following FreeBSD's sysctl_kern_arnd, a null buffer reports min(*oldLength, 256) without writing, a null length returns 0 without writing, and requests with a new value return EPERM (1) as the node is read-only. Cryptographic quality depends on the host's random device implementation. - [pthread_atfork](../../../core/libs/prx/libkernel/Pthread/Posix/Atfork.cpp) (libkernel) appends the three handlers to a process-wide list and returns 0, or `ENOMEM` when the entry cannot be allocated, as FreeBSD 11.4's [`_pthread_atfork`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_fork.c#L78-L100) does; null handlers are accepted. The handlers never run: `fork` throws and `vfork` is absent, so no child process is created. Handlers of a module that is unloaded stay in the list, where FreeBSD's `__pthread_cxa_finalize` removes them. PPSA97358 has a source-level call from the constructor `rcu_init` ([rcu.c](https://github.com/ZiZc3/XPSemu/blob/8eec07b4e31f66dce8e0b05c1ca1281af303e933/util/rcu.c#L488-L495)), but current-main runtime reachability is unverified From 5c30b97440277c99cc67d21eec873e69a3369810 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Fri, 23 Oct 2026 22:19:20 +0200 Subject: [PATCH 3/6] feat(libkernel): implement pthread_barrier_setname_np --- .../prx/libkernel/Pthread/Posix/Barrier.cpp | 8 ++++++++ core/libs/prx/libkernel/include/Api.hpp | 2 ++ .../prx/libkernel_sys/Export.Discovery.cpp | 5 ----- core/libs/tests/GuestPthreadSetname.cpp | 19 +++++++++++++++++++ docs/dev/debt/libkernel.md | 1 + 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp b/core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp index 9d98ca6274..2aa63b94f5 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp @@ -91,4 +91,12 @@ int APS5_VABI pthread_barrier_destroy_nid_postfix(PthreadBarrierPrivate** barrie return 0; } +int APS5_VABI pthread_barrier_setname_np_nid_postfix(PthreadBarrierPrivate** barrier, const char* name) { + if (!barrier || !*barrier) return 22; + auto& value = **barrier; + std::lock_guard lock(value.mutex); + if (name) value.name = name; + return 0; +} + } diff --git a/core/libs/prx/libkernel/include/Api.hpp b/core/libs/prx/libkernel/include/Api.hpp index b5c59c40d6..1ee202c1c8 100644 --- a/core/libs/prx/libkernel/include/Api.hpp +++ b/core/libs/prx/libkernel/include/Api.hpp @@ -10,6 +10,7 @@ #include #include #include +#include struct KernelIovec { void* base; @@ -116,6 +117,7 @@ struct PthreadBarrierPrivate { unsigned arrived = 0; unsigned active = 0; std::uint64_t generation = 0; + std::string name; }; struct PthreadBarrierattrPrivate { diff --git a/core/libs/prx/libkernel_sys/Export.Discovery.cpp b/core/libs/prx/libkernel_sys/Export.Discovery.cpp index cd46940a07..67f81e08cc 100644 --- a/core/libs/prx/libkernel_sys/Export.Discovery.cpp +++ b/core/libs/prx/libkernel_sys/Export.Discovery.cpp @@ -291,11 +291,6 @@ int APS5_VABI pthread_attr_setstackaddr_nid_postfix(void) { return 0; } -int APS5_VABI pthread_barrier_setname_np_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI pthread_get_specificarray_np_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/tests/GuestPthreadSetname.cpp b/core/libs/tests/GuestPthreadSetname.cpp index 6bb70315c9..8754f6a61d 100644 --- a/core/libs/tests/GuestPthreadSetname.cpp +++ b/core/libs/tests/GuestPthreadSetname.cpp @@ -17,6 +17,10 @@ int APS5_VABI pthread_rwlock_destroy_nid_postfix(PthreadRwlock*); int APS5_VABI pthread_rwlock_rdlock_nid_postfix(PthreadRwlock*); int APS5_VABI pthread_rwlock_unlock_nid_postfix(PthreadRwlock*); int APS5_VABI pthread_rwlock_setname_np_nid_postfix(PthreadRwlock*, const char*); +int APS5_VABI pthread_barrier_init_nid_postfix(void**, void* const*, unsigned); +int APS5_VABI pthread_barrier_destroy_nid_postfix(void**); +int APS5_VABI pthread_barrier_wait_nid_postfix(void**); +int APS5_VABI pthread_barrier_setname_np_nid_postfix(void**, const char*); } static constexpr int GuestEinval = 22; @@ -70,8 +74,23 @@ static void TestRwlock() { Require(pthread_rwlock_setname_np_nid_postfix(&unset, "static") == 0 && unset == nullptr); } +static void TestBarrier() { + void* barrier = nullptr; + Require(pthread_barrier_init_nid_postfix(&barrier, nullptr, 1) == 0); + Require(pthread_barrier_setname_np_nid_postfix(&barrier, "first") == 0); + Require(pthread_barrier_setname_np_nid_postfix(&barrier, nullptr) == 0); + Require(pthread_barrier_wait_nid_postfix(&barrier) == -1); + Require(pthread_barrier_setname_np_nid_postfix(&barrier, "second") == 0); + Require(pthread_barrier_setname_np_nid_postfix(nullptr, "barrier") == GuestEinval); + Require(pthread_barrier_destroy_nid_postfix(&barrier) == 0); + Require(pthread_barrier_setname_np_nid_postfix(&barrier, "destroyed") == GuestEinval); + void* unset = nullptr; + Require(pthread_barrier_setname_np_nid_postfix(&unset, "unset") == GuestEinval && unset == nullptr); +} + int main() { TestMutex(); TestRwlock(); + TestBarrier(); std::puts("pthread setname tests passed"); } diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index f3ed21a362..a1251391e8 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -289,6 +289,7 @@ - [pthread_cond_reltimedwait_np](../../../core/libs/prx/libkernel/Pthread/Posix/Cond.cpp) (libkernel) takes `(cond, mutex, usec)`, a relative timeout in microseconds, and is `scePthreadCondTimedwait` with POSIX error numbers. FreeBSD 11.4 has no such function; the signature comes only from shadPS4, which registers one function for this NID and for `scePthreadCondTimedwait` ([`condvar.cpp`](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/libraries/kernel/threads/condvar.cpp#L229-L234)). shadPS4 reads a 64-bit count; here it is the 32-bit `SceKernelUseconds` of the SCE twin, as for `sem_reltimedwait_np`. Unverified on the console - [pthread_mutex_setname_np](../../../core/libs/prx/libkernel/Pthread/src/Mutex.cpp) (libkernel) takes `(mutex, name)` and copies `name` onto an initialized mutex, returning 0. A null `name` returns 0 and leaves the name; [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/kernel/threads/mutex.cpp#L105-L113) and [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_mutex.pas#L481-L494) both return 0 there (shadPS4 stores a default name, fpPS4 keeps the old one). Nothing reads the name back, and `scePthreadMutexInit` still ignores its name. A null mutex pointer, a statically initialized mutex and a destroyed one throw, because the two disagree or fault: fpPS4 returns `EINVAL`, shadPS4 returns 0 without naming or dereferences null. Unverified on the console - [pthread_rwlock_setname_np](../../../core/libs/prx/libkernel/Pthread/src/Rwlock.cpp) (libkernel) follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/kernel/threads/rwlock.cpp#L79-L87), the only source: a null `name` returns `EINVAL` before the lock is read, a statically initialized lock returns 0 without being created or named, and an initialized one gets a copy of `name` and returns 0. Nothing reads the name back, and `scePthreadRwlockInit` still ignores its name. A null lock pointer, which shadPS4 dereferences, and a destroyed lock, which shadPS4 does not distinguish from a static one, throw. Unverified on the console +- [pthread_barrier_setname_np](../../../core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp) (libkernel) follows the fpPS4 [`_pthread_barrier_setname_np`](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_barrier.pas#L273-L284) and [`bar_enter`](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_barrier.pas#L177-L190), the only source: a null barrier pointer or a barrier that is not initialized (or was destroyed) returns `EINVAL`, a null `name` returns 0 and leaves the name, and otherwise `name` is copied onto the barrier and 0 is returned. fpPS4 keeps at most 32 bytes of the name; the full string is kept here. Nothing reads the name back. Unverified on the console - [pthread_mutex_reltimedlock_np](../../../core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp) (libkernel) takes `(mutex, usec)`, a relative timeout in microseconds, and is `scePthreadMutexTimedlock` with POSIX error numbers. FreeBSD 11.4 has no such function; the signature comes only from shadPS4, which registers one function for this NID and for `scePthreadMutexTimedlock` ([`mutex.cpp`](https://github.com/shadps4-emu/shadPS4/blob/7c960eae136b956cf4c1d72aacd2426b650330df/src/core/libraries/kernel/threads/mutex.cpp#L284-L287)). shadPS4 reads a 64-bit count; here it is the 32-bit `SceKernelUseconds` of the SCE twin, as for `pthread_cond_reltimedwait_np`, so a count of 2^32 microseconds or more would be truncated if the console reads 64 bits. Unverified on the console - [sysctl kern.arandom](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) serves FreeBSD 11's MIB {1, 37} (CTL_KERN, KERN_ARANDOM) and the "kern.arandom" sysctlbyname lookup through std::random_device, returning min(*oldLength, 256) random bytes. Following FreeBSD's sysctl_kern_arnd, a null buffer reports min(*oldLength, 256) without writing, a null length returns 0 without writing, and requests with a new value return EPERM (1) as the node is read-only. Cryptographic quality depends on the host's random device implementation. - [pthread_atfork](../../../core/libs/prx/libkernel/Pthread/Posix/Atfork.cpp) (libkernel) appends the three handlers to a process-wide list and returns 0, or `ENOMEM` when the entry cannot be allocated, as FreeBSD 11.4's [`_pthread_atfork`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_fork.c#L78-L100) does; null handlers are accepted. The handlers never run: `fork` throws and `vfork` is absent, so no child process is created. Handlers of a module that is unloaded stay in the list, where FreeBSD's `__pthread_cxa_finalize` removes them. PPSA97358 has a source-level call from the constructor `rcu_init` ([rcu.c](https://github.com/ZiZc3/XPSemu/blob/8eec07b4e31f66dce8e0b05c1ca1281af303e933/util/rcu.c#L488-L495)), but current-main runtime reachability is unverified From 20ead24d8bd008aa3bcb9f3a6ca423ac93075f94 Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sun, 25 Oct 2026 06:01:40 +0100 Subject: [PATCH 4/6] refactor(libkernel): declare the setname exports in the public header --- core/libs/prx/libkernel/Pthread/src/Mutex.cpp | 1 + .../libs/prx/libkernel/Pthread/src/Rwlock.cpp | 1 + core/libs/prx/libkernel/include/Api.hpp | 3 +++ core/libs/prx/libkernel_sys/include/Api.hpp | 3 --- core/libs/tests/GuestPthreadSetname.cpp | 22 +++---------------- 5 files changed, 8 insertions(+), 22 deletions(-) diff --git a/core/libs/prx/libkernel/Pthread/src/Mutex.cpp b/core/libs/prx/libkernel/Pthread/src/Mutex.cpp index a3bd9370f5..64195fd754 100644 --- a/core/libs/prx/libkernel/Pthread/src/Mutex.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Mutex.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libkernel/Pthread/include/Mutex.hpp" #include "prx/libkernel/include/Api.hpp" #include "../include/Pthread.hpp" diff --git a/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp b/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp index 3a7cb12ab2..6b4550f365 100644 --- a/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libkernel/Pthread/include/Rwlock.hpp" #include "../include/Pthread.hpp" #include "../include/Rwlock.hpp" diff --git a/core/libs/prx/libkernel/include/Api.hpp b/core/libs/prx/libkernel/include/Api.hpp index 1ee202c1c8..2375933ee2 100644 --- a/core/libs/prx/libkernel/include/Api.hpp +++ b/core/libs/prx/libkernel/include/Api.hpp @@ -416,6 +416,7 @@ int APS5_VABI pthread_barrierattr_setpshared_nid_postfix(PthreadBarrierattrPriva int APS5_VABI pthread_barrier_init_nid_postfix(PthreadBarrierPrivate** barrier, PthreadBarrierattrPrivate* const* attr, unsigned count); int APS5_VABI pthread_barrier_wait_nid_postfix(PthreadBarrierPrivate** barrier); int APS5_VABI pthread_barrier_destroy_nid_postfix(PthreadBarrierPrivate** barrier); +int APS5_VABI pthread_barrier_setname_np_nid_postfix(PthreadBarrierPrivate** barrier, const char* name); int APS5_VABI pthread_cond_destroy_nid_postfix(PthreadCond* cond); int APS5_VABI pthread_cond_broadcast_nid_postfix(PthreadCond* cond); int APS5_VABI pthread_cond_init_nid_postfix(PthreadCond* cond, const PthreadCondattr* attr); @@ -441,6 +442,7 @@ int APS5_VABI pthread_mutex_getspinloops_np_nid_postfix(PthreadMutex* mutex, int int APS5_VABI pthread_mutex_setspinloops_np_nid_postfix(PthreadMutex* mutex, int count); int APS5_VABI pthread_mutex_getyieldloops_np_nid_postfix(PthreadMutex* mutex, int* count); int APS5_VABI pthread_mutex_setyieldloops_np_nid_postfix(PthreadMutex* mutex, int count); +int APS5_VABI pthread_mutex_setname_np_nid_postfix(PthreadMutex* mutex, const char* name); int APS5_VABI pthread_mutexattr_destroy_nid_postfix(PthreadMutexattr* attr); int APS5_VABI pthread_mutexattr_init_nid_postfix(PthreadMutexattr* attr); int APS5_VABI pthread_mutexattr_getpshared_nid_postfix(const PthreadMutexattr* attr, int* pshared); @@ -467,6 +469,7 @@ int APS5_VABI pthread_rwlock_tryrdlock_nid_postfix(PthreadRwlock* rwlock); int APS5_VABI pthread_rwlock_trywrlock_nid_postfix(PthreadRwlock* rwlock); int APS5_VABI pthread_rwlock_unlock_nid_postfix(PthreadRwlock* rwlock); int APS5_VABI pthread_rwlock_wrlock_nid_postfix(PthreadRwlock* rwlock); +int APS5_VABI pthread_rwlock_setname_np_nid_postfix(PthreadRwlock* rwlock, const char* name); int APS5_VABI sem_init_nid_postfix(void* sem, int pshared, unsigned int value); int APS5_VABI sem_destroy_nid_postfix(void* sem); int APS5_VABI sem_wait_nid_postfix(void* sem); diff --git a/core/libs/prx/libkernel_sys/include/Api.hpp b/core/libs/prx/libkernel_sys/include/Api.hpp index ae38852b68..a0c283f9a9 100644 --- a/core/libs/prx/libkernel_sys/include/Api.hpp +++ b/core/libs/prx/libkernel_sys/include/Api.hpp @@ -135,7 +135,6 @@ int APS5_VABI pthread_attr_getsolosched_np_nid_postfix(void); int APS5_VABI pthread_attr_setaffinity_np_nid_postfix(void); int APS5_VABI pthread_attr_setcreatesuspend_np_nid_postfix(PthreadAttr* attr); int APS5_VABI pthread_attr_setstackaddr_nid_postfix(void); -int APS5_VABI pthread_barrier_setname_np_nid_postfix(void); int APS5_VABI pthread_get_specificarray_np_nid_postfix(void); int APS5_VABI pthread_get_user_context_np_nid_postfix(void); int APS5_VABI pthread_getstack_np_nid_postfix(void); @@ -143,14 +142,12 @@ int APS5_VABI pthread_main_np_nid_postfix(void); int APS5_VABI pthread_multi_np_nid_postfix(void); int APS5_VABI pthread_mutex_getprioceiling_nid_postfix(void); int APS5_VABI pthread_mutex_init_for_mono_nid_postfix(void); -int APS5_VABI pthread_mutex_setname_np_nid_postfix(void); int APS5_VABI pthread_mutex_setprioceiling_nid_postfix(void); int APS5_VABI pthread_mutexattr_getgen_np_nid_postfix(void); int APS5_VABI pthread_mutexattr_setgen_np_nid_postfix(void); void APS5_VABI pthread_resume_all_np_nid_postfix(void); int APS5_VABI pthread_resume_np_nid_postfix(Pthread thread); int APS5_VABI pthread_resume_user_context_np_nid_postfix(void); -int APS5_VABI pthread_rwlock_setname_np_nid_postfix(void); int APS5_VABI pthread_set_user_context_np_nid_postfix(void); int APS5_VABI pthread_single_np_nid_postfix(void); int APS5_VABI pthread_suspend_all_np_nid_postfix(void); diff --git a/core/libs/tests/GuestPthreadSetname.cpp b/core/libs/tests/GuestPthreadSetname.cpp index 8754f6a61d..2a342545a3 100644 --- a/core/libs/tests/GuestPthreadSetname.cpp +++ b/core/libs/tests/GuestPthreadSetname.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libkernel/Pthread/include/Pthread.hpp" #include @@ -6,23 +7,6 @@ #include #include -extern "C" { -int APS5_VABI pthread_mutex_init_nid_postfix(PthreadMutex*, const PthreadMutexattr*); -int APS5_VABI pthread_mutex_destroy_nid_postfix(PthreadMutex*); -int APS5_VABI pthread_mutex_lock_nid_postfix(PthreadMutex*); -int APS5_VABI pthread_mutex_unlock_nid_postfix(PthreadMutex*); -int APS5_VABI pthread_mutex_setname_np_nid_postfix(PthreadMutex*, const char*); -int APS5_VABI pthread_rwlock_init_nid_postfix(PthreadRwlock*, const PthreadRwlockattr*); -int APS5_VABI pthread_rwlock_destroy_nid_postfix(PthreadRwlock*); -int APS5_VABI pthread_rwlock_rdlock_nid_postfix(PthreadRwlock*); -int APS5_VABI pthread_rwlock_unlock_nid_postfix(PthreadRwlock*); -int APS5_VABI pthread_rwlock_setname_np_nid_postfix(PthreadRwlock*, const char*); -int APS5_VABI pthread_barrier_init_nid_postfix(void**, void* const*, unsigned); -int APS5_VABI pthread_barrier_destroy_nid_postfix(void**); -int APS5_VABI pthread_barrier_wait_nid_postfix(void**); -int APS5_VABI pthread_barrier_setname_np_nid_postfix(void**, const char*); -} - static constexpr int GuestEinval = 22; static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -75,7 +59,7 @@ static void TestRwlock() { } static void TestBarrier() { - void* barrier = nullptr; + PthreadBarrierPrivate* barrier = nullptr; Require(pthread_barrier_init_nid_postfix(&barrier, nullptr, 1) == 0); Require(pthread_barrier_setname_np_nid_postfix(&barrier, "first") == 0); Require(pthread_barrier_setname_np_nid_postfix(&barrier, nullptr) == 0); @@ -84,7 +68,7 @@ static void TestBarrier() { Require(pthread_barrier_setname_np_nid_postfix(nullptr, "barrier") == GuestEinval); Require(pthread_barrier_destroy_nid_postfix(&barrier) == 0); Require(pthread_barrier_setname_np_nid_postfix(&barrier, "destroyed") == GuestEinval); - void* unset = nullptr; + PthreadBarrierPrivate* unset = nullptr; Require(pthread_barrier_setname_np_nid_postfix(&unset, "unset") == GuestEinval && unset == nullptr); } From b78878abbc153bb801302a25cf49861a7effbf1a Mon Sep 17 00:00:00 2001 From: Alvaro Date: Sun, 25 Oct 2026 18:00:01 +0100 Subject: [PATCH 5/6] test(libkernel): drop the static and adaptive initializer cases from the mutex setname test --- core/libs/tests/GuestPthreadSetname.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/core/libs/tests/GuestPthreadSetname.cpp b/core/libs/tests/GuestPthreadSetname.cpp index 2a342545a3..a51a48a9f2 100644 --- a/core/libs/tests/GuestPthreadSetname.cpp +++ b/core/libs/tests/GuestPthreadSetname.cpp @@ -1,7 +1,6 @@ #include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libkernel/Pthread/include/Pthread.hpp" -#include #include #include #include @@ -32,13 +31,6 @@ static void TestMutex() { RequireThrows([] { pthread_mutex_setname_np_nid_postfix(nullptr, "mutex"); }); Require(pthread_mutex_destroy_nid_postfix(&mutex) == 0); RequireThrows([&] { pthread_mutex_setname_np_nid_postfix(&mutex, "destroyed"); }); - - PthreadMutex unset = nullptr; - RequireThrows([&] { pthread_mutex_setname_np_nid_postfix(&unset, "static"); }); - Require(unset == nullptr); - PthreadMutex adaptive = reinterpret_cast(std::uintptr_t{1}); - RequireThrows([&] { pthread_mutex_setname_np_nid_postfix(&adaptive, "adaptive"); }); - Require(reinterpret_cast(adaptive) == 1); } static void TestRwlock() { From cf2aa7c3627c945bda2519301b77791131cce39f Mon Sep 17 00:00:00 2001 From: Alvaro Date: Mon, 26 Oct 2026 15:39:37 +0100 Subject: [PATCH 6/6] fix(libkernel): return EAGAIN when renaming a named mutex, rwlock or barrier --- .../prx/libkernel/Pthread/Posix/Barrier.cpp | 9 +++++-- .../prx/libkernel/Pthread/include/Pthread.hpp | 2 ++ core/libs/prx/libkernel/Pthread/src/Mutex.cpp | 7 +++++- .../libs/prx/libkernel/Pthread/src/Rwlock.cpp | 7 ++++-- core/libs/prx/libkernel/include/Api.hpp | 1 + core/libs/tests/GuestPthreadSetname.cpp | 25 ++++++++----------- docs/dev/debt/libkernel.md | 6 ++--- 7 files changed, 34 insertions(+), 23 deletions(-) diff --git a/core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp b/core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp index 2aa63b94f5..87ededece4 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp @@ -92,10 +92,15 @@ int APS5_VABI pthread_barrier_destroy_nid_postfix(PthreadBarrierPrivate** barrie } int APS5_VABI pthread_barrier_setname_np_nid_postfix(PthreadBarrierPrivate** barrier, const char* name) { - if (!barrier || !*barrier) return 22; + if (!barrier) throw std::invalid_argument("pthread_barrier_setname_np: null barrier"); + if (!*barrier) throw std::invalid_argument("pthread_barrier_setname_np: barrier is not initialized or was destroyed"); auto& value = **barrier; std::lock_guard lock(value.mutex); - if (name) value.name = name; + if (value.named) return 35; + if (name) { + value.name = name; + value.named = true; + } return 0; } diff --git a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp index 586b6d86ae..378bb1a46e 100644 --- a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp @@ -48,6 +48,7 @@ struct PthreadMutexPrivate { int _spinloops = 0; int _yieldloops = 0; std::string _name; + bool _named = false; PthreadMutexPrivate() : _type(MutexType::ErrorCheck), _owner(std::thread::id{}), _count(0) {} @@ -68,6 +69,7 @@ struct PthreadRwlockPrivate { std::atomic _readers{0}; int _waitingWriters = 0; std::string _name; + bool _named = false; }; struct PthreadCondattrPrivate { diff --git a/core/libs/prx/libkernel/Pthread/src/Mutex.cpp b/core/libs/prx/libkernel/Pthread/src/Mutex.cpp index 64195fd754..8525c3d088 100644 --- a/core/libs/prx/libkernel/Pthread/src/Mutex.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Mutex.cpp @@ -22,6 +22,7 @@ constexpr int sceDeadlock = static_cast(0x8002000bu); constexpr int sceBusy = static_cast(0x80020010u); constexpr int sceTimedOut = static_cast(0x8002003cu); constexpr std::int32_t posixFailure = -1; +constexpr int posixAgain = 35; std::mutex& InitializationMutex() { static std::mutex mutex; return mutex; @@ -290,8 +291,12 @@ int APS5_VABI pthread_mutex_setname_np_nid_postfix(PthreadMutex* mutex, const ch const auto current = std::atomic_ref(*mutex).load(std::memory_order_acquire); if (!isInitializedMutex(current)) throw std::invalid_argument("pthread_mutex_setname_np: mutex is statically initialized or destroyed"); - if (name) + if (current->_named) + return posixAgain; + if (name) { current->_name = name; + current->_named = true; + } return 0; } diff --git a/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp b/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp index 6b4550f365..82d935c17c 100644 --- a/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp @@ -19,6 +19,7 @@ static constexpr int SCE_KERNEL_ERROR_EINVAL = 0x80020016; static constexpr int FIRST_STORED_RWLOCK_TYPE = 1; static constexpr int LAST_STORED_RWLOCK_TYPE = 2; static constexpr int POSIX_EINVAL = 22; +static constexpr int POSIX_EAGAIN = 35; static PthreadRwlockPrivate* const DestroyedRwlock = reinterpret_cast(std::uintptr_t{1}); @@ -191,13 +192,15 @@ int APS5_VABI scePthreadRwlockattrSettype(PthreadRwlockattr* attr, int type) { } int APS5_VABI pthread_rwlock_setname_np_nid_postfix(PthreadRwlock* rwlock, const char* name) { - if (!name) return POSIX_EINVAL; if (!rwlock) throw std::runtime_error("pthread_rwlock_setname_np: null rwlock"); PthreadRwlock current = std::atomic_ref(*rwlock).load(std::memory_order_acquire); - if (current == nullptr) return SCE_OK; + if (current == nullptr) throw std::runtime_error("pthread_rwlock_setname_np: statically initialized rwlock"); if (current == DestroyedRwlock) throw std::runtime_error("pthread_rwlock_setname_np: destroyed rwlock"); std::lock_guard state(current->_state); + if (current->_named) return POSIX_EAGAIN; + if (!name) return POSIX_EINVAL; current->_name = name; + current->_named = true; return SCE_OK; } diff --git a/core/libs/prx/libkernel/include/Api.hpp b/core/libs/prx/libkernel/include/Api.hpp index 2375933ee2..989755ef53 100644 --- a/core/libs/prx/libkernel/include/Api.hpp +++ b/core/libs/prx/libkernel/include/Api.hpp @@ -118,6 +118,7 @@ struct PthreadBarrierPrivate { unsigned active = 0; std::uint64_t generation = 0; std::string name; + bool named = false; }; struct PthreadBarrierattrPrivate { diff --git a/core/libs/tests/GuestPthreadSetname.cpp b/core/libs/tests/GuestPthreadSetname.cpp index a51a48a9f2..3b57956564 100644 --- a/core/libs/tests/GuestPthreadSetname.cpp +++ b/core/libs/tests/GuestPthreadSetname.cpp @@ -6,7 +6,7 @@ #include #include -static constexpr int GuestEinval = 22; +static constexpr int GuestEagain = 35; static void Require(bool condition, std::source_location location = std::source_location::current()) { if (condition) return; @@ -23,10 +23,9 @@ static void TestMutex() { PthreadMutex mutex = nullptr; Require(pthread_mutex_init_nid_postfix(&mutex, nullptr) == 0); Require(pthread_mutex_setname_np_nid_postfix(&mutex, "first") == 0 && mutex->_name == "first"); - Require(pthread_mutex_setname_np_nid_postfix(&mutex, "second") == 0 && mutex->_name == "second"); - Require(pthread_mutex_setname_np_nid_postfix(&mutex, nullptr) == 0 && mutex->_name == "second"); + Require(pthread_mutex_setname_np_nid_postfix(&mutex, "second") == GuestEagain && mutex->_name == "first"); Require(pthread_mutex_lock_nid_postfix(&mutex) == 0); - Require(pthread_mutex_setname_np_nid_postfix(&mutex, "held") == 0 && mutex->_name == "held"); + Require(pthread_mutex_setname_np_nid_postfix(&mutex, "held") == GuestEagain && mutex->_name == "first"); Require(pthread_mutex_unlock_nid_postfix(&mutex) == 0); RequireThrows([] { pthread_mutex_setname_np_nid_postfix(nullptr, "mutex"); }); Require(pthread_mutex_destroy_nid_postfix(&mutex) == 0); @@ -37,31 +36,27 @@ static void TestRwlock() { PthreadRwlock rwlock = nullptr; Require(pthread_rwlock_init_nid_postfix(&rwlock, nullptr) == 0); Require(pthread_rwlock_setname_np_nid_postfix(&rwlock, "first") == 0 && rwlock->_name == "first"); - Require(pthread_rwlock_setname_np_nid_postfix(&rwlock, nullptr) == GuestEinval && rwlock->_name == "first"); Require(pthread_rwlock_rdlock_nid_postfix(&rwlock) == 0); - Require(pthread_rwlock_setname_np_nid_postfix(&rwlock, "read") == 0 && rwlock->_name == "read"); + Require(pthread_rwlock_setname_np_nid_postfix(&rwlock, "read") == GuestEagain && rwlock->_name == "first"); Require(pthread_rwlock_unlock_nid_postfix(&rwlock) == 0); - Require(pthread_rwlock_setname_np_nid_postfix(nullptr, nullptr) == GuestEinval); RequireThrows([] { pthread_rwlock_setname_np_nid_postfix(nullptr, "rwlock"); }); Require(pthread_rwlock_destroy_nid_postfix(&rwlock) == 0); RequireThrows([&] { pthread_rwlock_setname_np_nid_postfix(&rwlock, "destroyed"); }); - PthreadRwlock unset = nullptr; - Require(pthread_rwlock_setname_np_nid_postfix(&unset, "static") == 0 && unset == nullptr); + RequireThrows([&] { pthread_rwlock_setname_np_nid_postfix(&unset, "static"); }); } static void TestBarrier() { PthreadBarrierPrivate* barrier = nullptr; Require(pthread_barrier_init_nid_postfix(&barrier, nullptr, 1) == 0); - Require(pthread_barrier_setname_np_nid_postfix(&barrier, "first") == 0); - Require(pthread_barrier_setname_np_nid_postfix(&barrier, nullptr) == 0); + Require(pthread_barrier_setname_np_nid_postfix(&barrier, "first") == 0 && barrier->name == "first"); Require(pthread_barrier_wait_nid_postfix(&barrier) == -1); - Require(pthread_barrier_setname_np_nid_postfix(&barrier, "second") == 0); - Require(pthread_barrier_setname_np_nid_postfix(nullptr, "barrier") == GuestEinval); + Require(pthread_barrier_setname_np_nid_postfix(&barrier, "second") == GuestEagain && barrier->name == "first"); + RequireThrows([] { pthread_barrier_setname_np_nid_postfix(nullptr, "barrier"); }); Require(pthread_barrier_destroy_nid_postfix(&barrier) == 0); - Require(pthread_barrier_setname_np_nid_postfix(&barrier, "destroyed") == GuestEinval); + RequireThrows([&] { pthread_barrier_setname_np_nid_postfix(&barrier, "destroyed"); }); PthreadBarrierPrivate* unset = nullptr; - Require(pthread_barrier_setname_np_nid_postfix(&unset, "unset") == GuestEinval && unset == nullptr); + RequireThrows([&] { pthread_barrier_setname_np_nid_postfix(&unset, "unset"); }); } int main() { diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index a1251391e8..d501db0309 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -287,9 +287,9 @@ - [pthread_rwlockattr_gettype_np, scePthreadRwlockattrGettype](../../../core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp) (libkernel) report the type stored by `scePthreadRwlockattrSettype`, which accepts any value and which read-write locks do not apply; the console's valid types and their effect are unverified. A null or uninitialized attribute throws, as for `pthread_rwlockattr_getpshared` - [sceKernelGetEventError](../../../core/libs/prx/libkernel/Equeue/src/EventAccessors.cpp) (libkernel) throws for `fflags` above 0xFFFF, where OR and addition of 0x80020000 differ and the console was not measured. Otherwise it returns 0 for `fflags` 0 and `0x80020000 | fflags`, whatever `flags` and `data` hold, as [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_queue.pas#L643) does and a PS5 (firmware 13.40, measured 2026-10-19 in a payload process) answered: `{flags 0x4000, fflags 0, data 22}`, `{0x4000, 0, -1}`, `{0x4000, 0, 0x80020016}` and `{0, 0, 0}` gave 0, `{0x4000, 5, 0}` and `{0x4000, 5, 22}` 0x80020005, `{0, 7, 22}` 0x80020007, `{0x8000, 3, 9}` 0x80020003. Video-out events carry their count in `fflags`, so they get 0x80020001 and up; only events built by the caller were measured. Closes when: `fflags` above 0xFFFF is measured - [pthread_cond_reltimedwait_np](../../../core/libs/prx/libkernel/Pthread/Posix/Cond.cpp) (libkernel) takes `(cond, mutex, usec)`, a relative timeout in microseconds, and is `scePthreadCondTimedwait` with POSIX error numbers. FreeBSD 11.4 has no such function; the signature comes only from shadPS4, which registers one function for this NID and for `scePthreadCondTimedwait` ([`condvar.cpp`](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/libraries/kernel/threads/condvar.cpp#L229-L234)). shadPS4 reads a 64-bit count; here it is the 32-bit `SceKernelUseconds` of the SCE twin, as for `sem_reltimedwait_np`. Unverified on the console -- [pthread_mutex_setname_np](../../../core/libs/prx/libkernel/Pthread/src/Mutex.cpp) (libkernel) takes `(mutex, name)` and copies `name` onto an initialized mutex, returning 0. A null `name` returns 0 and leaves the name; [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/kernel/threads/mutex.cpp#L105-L113) and [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_mutex.pas#L481-L494) both return 0 there (shadPS4 stores a default name, fpPS4 keeps the old one). Nothing reads the name back, and `scePthreadMutexInit` still ignores its name. A null mutex pointer, a statically initialized mutex and a destroyed one throw, because the two disagree or fault: fpPS4 returns `EINVAL`, shadPS4 returns 0 without naming or dereferences null. Unverified on the console -- [pthread_rwlock_setname_np](../../../core/libs/prx/libkernel/Pthread/src/Rwlock.cpp) (libkernel) follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/kernel/threads/rwlock.cpp#L79-L87), the only source: a null `name` returns `EINVAL` before the lock is read, a statically initialized lock returns 0 without being created or named, and an initialized one gets a copy of `name` and returns 0. Nothing reads the name back, and `scePthreadRwlockInit` still ignores its name. A null lock pointer, which shadPS4 dereferences, and a destroyed lock, which shadPS4 does not distinguish from a static one, throw. Unverified on the console -- [pthread_barrier_setname_np](../../../core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp) (libkernel) follows the fpPS4 [`_pthread_barrier_setname_np`](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_barrier.pas#L273-L284) and [`bar_enter`](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_barrier.pas#L177-L190), the only source: a null barrier pointer or a barrier that is not initialized (or was destroyed) returns `EINVAL`, a null `name` returns 0 and leaves the name, and otherwise `name` is copied onto the barrier and 0 is returned. fpPS4 keeps at most 32 bytes of the name; the full string is kept here. Nothing reads the name back. Unverified on the console +- [pthread_mutex_setname_np](../../../core/libs/prx/libkernel/Pthread/src/Mutex.cpp) (libkernel) - the console passes `name` to the kernel's `namedobj_create` and stores the returned id instead of the name; here the name is copied and never read back. Once named, a mutex returns `EAGAIN` (35) for every later call and keeps its name, as [firmware 13.42's module run on a PC](https://github.com/boykopovar/AnyPS5/pull/10385#issuecomment-6415603047) does. The kernel's handling of a null `name` is unmeasured: here it returns 0 and leaves the mutex unnamed. A null mutex pointer faults in the module and throws here; a statically initialized or destroyed mutex also throws, and the module's result for those is unmeasured. The module's separate branch for a field holding `0x5eaf00d`, which copies the name into the object, is known only from disassembly and is not reproduced +- [pthread_rwlock_setname_np](../../../core/libs/prx/libkernel/Pthread/src/Rwlock.cpp) (libkernel) - the console passes `name` to the kernel's `namedobj_create` and stores the returned id instead of the name; here the name is copied and never read back. Once named, a lock returns `EAGAIN` (35) for every later call and keeps its name, as [firmware 13.42's module run on a PC](https://github.com/boykopovar/AnyPS5/pull/10385#issuecomment-6415603047) does. The kernel's handling of a null `name` is unmeasured: here it returns `EINVAL`, following [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/kernel/threads/rwlock.cpp#L79-L87). A null lock pointer, a statically initialized lock and a destroyed one fault in the module and throw here +- [pthread_barrier_setname_np](../../../core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp) (libkernel) - the console passes `name` to the kernel's `namedobj_create` and stores the returned id instead of the name; here the name is copied and never read back. Once named, a barrier returns `EAGAIN` (35) for every later call and keeps its name, as [firmware 13.42's module run on a PC](https://github.com/boykopovar/AnyPS5/pull/10385#issuecomment-6415603047) does. The kernel's handling of a null `name` is unmeasured: here it returns 0 and leaves the barrier unnamed. A null barrier pointer, an unset barrier and a destroyed one fault in the module and throw here - [pthread_mutex_reltimedlock_np](../../../core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp) (libkernel) takes `(mutex, usec)`, a relative timeout in microseconds, and is `scePthreadMutexTimedlock` with POSIX error numbers. FreeBSD 11.4 has no such function; the signature comes only from shadPS4, which registers one function for this NID and for `scePthreadMutexTimedlock` ([`mutex.cpp`](https://github.com/shadps4-emu/shadPS4/blob/7c960eae136b956cf4c1d72aacd2426b650330df/src/core/libraries/kernel/threads/mutex.cpp#L284-L287)). shadPS4 reads a 64-bit count; here it is the 32-bit `SceKernelUseconds` of the SCE twin, as for `pthread_cond_reltimedwait_np`, so a count of 2^32 microseconds or more would be truncated if the console reads 64 bits. Unverified on the console - [sysctl kern.arandom](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) serves FreeBSD 11's MIB {1, 37} (CTL_KERN, KERN_ARANDOM) and the "kern.arandom" sysctlbyname lookup through std::random_device, returning min(*oldLength, 256) random bytes. Following FreeBSD's sysctl_kern_arnd, a null buffer reports min(*oldLength, 256) without writing, a null length returns 0 without writing, and requests with a new value return EPERM (1) as the node is read-only. Cryptographic quality depends on the host's random device implementation. - [pthread_atfork](../../../core/libs/prx/libkernel/Pthread/Posix/Atfork.cpp) (libkernel) appends the three handlers to a process-wide list and returns 0, or `ENOMEM` when the entry cannot be allocated, as FreeBSD 11.4's [`_pthread_atfork`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_fork.c#L78-L100) does; null handlers are accepted. The handlers never run: `fork` throws and `vfork` is absent, so no child process is created. Handlers of a module that is unloaded stay in the list, where FreeBSD's `__pthread_cxa_finalize` removes them. PPSA97358 has a source-level call from the constructor `rcu_init` ([rcu.c](https://github.com/ZiZc3/XPSemu/blob/8eec07b4e31f66dce8e0b05c1ca1281af303e933/util/rcu.c#L488-L495)), but current-main runtime reachability is unverified