diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 668f01ada9..675f341151 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -538,6 +538,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/Posix/Barrier.cpp b/core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp index 9d98ca6274..87ededece4 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp @@ -91,4 +91,17 @@ 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) 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 (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 0078e2b5f1..e34a09f3bf 100644 --- a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp @@ -47,6 +47,8 @@ struct PthreadMutexPrivate { int _count; int _spinloops = 0; int _yieldloops = 0; + std::string _name; + bool _named = false; PthreadMutexPrivate() : _type(MutexType::ErrorCheck), _owner(std::thread::id{}), _count(0) {} @@ -66,6 +68,8 @@ struct PthreadRwlockPrivate { std::atomic _writer; 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 62e5e38db1..8525c3d088 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" @@ -21,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; @@ -282,4 +284,20 @@ 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 (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 8d06b7e9ea..82d935c17c 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" @@ -17,6 +18,8 @@ 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 constexpr int POSIX_EAGAIN = 35; static PthreadRwlockPrivate* const DestroyedRwlock = reinterpret_cast(std::uintptr_t{1}); @@ -188,4 +191,17 @@ 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 (!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) 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 52f4f31516..c2263e6b18 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,8 @@ struct PthreadBarrierPrivate { unsigned arrived = 0; unsigned active = 0; std::uint64_t generation = 0; + std::string name; + bool named = false; }; struct PthreadBarrierattrPrivate { @@ -414,6 +417,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); @@ -440,6 +444,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); @@ -466,6 +471,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/Export.Discovery.cpp b/core/libs/prx/libkernel_sys/Export.Discovery.cpp index 9edd36a335..81086d1483 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; @@ -326,11 +321,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; @@ -362,11 +352,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/prx/libkernel_sys/include/Api.hpp b/core/libs/prx/libkernel_sys/include/Api.hpp index 4eb1e56397..a7cf7157c6 100644 --- a/core/libs/prx/libkernel_sys/include/Api.hpp +++ b/core/libs/prx/libkernel_sys/include/Api.hpp @@ -144,7 +144,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); @@ -152,14 +151,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 new file mode 100644 index 0000000000..3b57956564 --- /dev/null +++ b/core/libs/tests/GuestPthreadSetname.cpp @@ -0,0 +1,67 @@ +#include "prx/libkernel/include/Api.hpp" +#include "SceTypes.hpp" +#include "prx/libkernel/Pthread/include/Pthread.hpp" +#include +#include +#include +#include + +static constexpr int GuestEagain = 35; + +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") == GuestEagain && mutex->_name == "first"); + Require(pthread_mutex_lock_nid_postfix(&mutex) == 0); + 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); + RequireThrows([&] { pthread_mutex_setname_np_nid_postfix(&mutex, "destroyed"); }); +} + +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_rdlock_nid_postfix(&rwlock) == 0); + Require(pthread_rwlock_setname_np_nid_postfix(&rwlock, "read") == GuestEagain && rwlock->_name == "first"); + Require(pthread_rwlock_unlock_nid_postfix(&rwlock) == 0); + 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; + 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 && barrier->name == "first"); + Require(pthread_barrier_wait_nid_postfix(&barrier) == -1); + 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); + RequireThrows([&] { pthread_barrier_setname_np_nid_postfix(&barrier, "destroyed"); }); + PthreadBarrierPrivate* unset = nullptr; + RequireThrows([&] { pthread_barrier_setname_np_nid_postfix(&unset, "unset"); }); +} + +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 82fd796372..b951b9ca1c 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -288,6 +288,9 @@ - [pthread_cond_signalto_np, scePthreadCondSignalto](../../../core/libs/prx/libkernel/Pthread/src/Cond.cpp) (libkernel) wake every waiter of the condition variable, not only the given thread, and do not check the thread; the other waiters see a spurious wakeup. Which waiters the console wakes, and its result for a thread that is not waiting, are unverified - [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` - [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) - 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