From 6131ec265d3269b130185cd7a2cf26929d304de8 Mon Sep 17 00:00:00 2001 From: Simone Date: Wed, 21 Oct 2026 10:40:25 +0200 Subject: [PATCH 1/3] feat(libkernel_sys): implement the scePthreadBarrier, cpuclockid, concurrency and msync forms Forward scePthreadBarrier*, scePthreadBarrierattr*, scePthreadGetcpuclockid, scePthreadGetconcurrency and sceKernelMsync to libkernel's POSIX implementations and return POSIX errors as SCE kernel errors. Co-Authored-By: Claude Opus 5.5 --- core/libs/CMakeLists.txt | 8 +- .../prx/libkernel_sys/Export.Discovery.cpp | 74 ++++++++----- core/libs/tests/GuestKernelSysBarrier.cpp | 103 ++++++++++++++++++ docs/dev/debt/libkernel_sys.md | 4 + 4 files changed, 159 insertions(+), 30 deletions(-) create mode 100644 core/libs/tests/GuestKernelSysBarrier.cpp diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 2854a5fa08b..f430d97a013 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -300,12 +300,18 @@ if(BUILD_TESTING) target_link_libraries(guest_kernel_sys_tests PRIVATE libkernel_sys libkernel libc) configure_windows_unwind(guest_kernel_sys_tests) add_test(NAME guest_kernel_sys COMMAND guest_kernel_sys_tests) + add_test_executable(guest_kernel_sys_barrier_tests tests/GuestKernelSysBarrier.cpp) + target_include_directories(guest_kernel_sys_barrier_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_barrier_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_barrier_tests) + add_test(NAME guest_kernel_sys_barrier COMMAND guest_kernel_sys_barrier_tests) + set_tests_properties(guest_kernel_sys_barrier PROPERTIES TIMEOUT 20) if(Python3_Interpreter_FOUND) add_test(NAME nid_verneed COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_verneed.py $) endif() if(WIN32) - set_tests_properties(guest_kernel_sys PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_kernel_sys guest_kernel_sys_barrier PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() if(APPLE) diff --git a/core/libs/prx/libkernel_sys/Export.Discovery.cpp b/core/libs/prx/libkernel_sys/Export.Discovery.cpp index 8b3da583e47..f56381b92b8 100644 --- a/core/libs/prx/libkernel_sys/Export.Discovery.cpp +++ b/core/libs/prx/libkernel_sys/Export.Discovery.cpp @@ -1,5 +1,29 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libkernel/KernelErrors.hpp" +#include + +extern "C" { +int* APS5_VABI __error_nid_postfix(); +int APS5_VABI pthread_barrier_init_nid_postfix(void** barrier, void* const* attr, unsigned count); +int APS5_VABI pthread_barrier_wait_nid_postfix(void** barrier); +int APS5_VABI pthread_barrier_destroy_nid_postfix(void** barrier); +int APS5_VABI pthread_barrierattr_init_nid_postfix(void** attr); +int APS5_VABI pthread_barrierattr_destroy_nid_postfix(void** attr); +int APS5_VABI pthread_barrierattr_getpshared_nid_postfix(void* const* attr, int* pshared); +int APS5_VABI pthread_barrierattr_setpshared_nid_postfix(void** attr, int pshared); +int APS5_VABI pthread_getcpuclockid_nid_postfix(void* thread, int* clockId); +int APS5_VABI pthread_getconcurrency_nid_postfix(); +int APS5_VABI msync_nid_postfix(void* address, std::size_t length, int flags); +} + +namespace { + +int SceResult(int error) { + return error == 0 ? 0 : SceKernelError(error); +} + +} extern "C" { @@ -1738,9 +1762,8 @@ int APS5_VABI sceKernelMlockall(void) { return 0; } -int APS5_VABI sceKernelMsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceKernelMsync(void* address, std::size_t length, int flags) { + return msync_nid_postfix(address, length, flags) == 0 ? 0 : SceKernelError(*__error_nid_postfix()); } int APS5_VABI sceKernelNormalizePath(void) { @@ -2308,39 +2331,34 @@ int APS5_VABI scePthreadAttrSetscope(void) { return 0; } -int APS5_VABI scePthreadBarrierDestroy(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadBarrierDestroy(void** barrier) { + return SceResult(pthread_barrier_destroy_nid_postfix(barrier)); } -int APS5_VABI scePthreadBarrierInit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadBarrierInit(void** barrier, void* const* attr, unsigned count, const char* name) { + (void)name; + return SceResult(pthread_barrier_init_nid_postfix(barrier, attr, count)); } -int APS5_VABI scePthreadBarrierWait(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadBarrierWait(void** barrier) { + const int result = pthread_barrier_wait_nid_postfix(barrier); + return result > 0 ? SceKernelError(result) : result; } -int APS5_VABI scePthreadBarrierattrDestroy(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadBarrierattrDestroy(void** attr) { + return SceResult(pthread_barrierattr_destroy_nid_postfix(attr)); } -int APS5_VABI scePthreadBarrierattrGetpshared(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadBarrierattrGetpshared(void* const* attr, int* pshared) { + return SceResult(pthread_barrierattr_getpshared_nid_postfix(attr, pshared)); } -int APS5_VABI scePthreadBarrierattrInit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadBarrierattrInit(void** attr) { + return SceResult(pthread_barrierattr_init_nid_postfix(attr)); } -int APS5_VABI scePthreadBarrierattrSetpshared(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadBarrierattrSetpshared(void** attr, int pshared) { + return SceResult(pthread_barrierattr_setpshared_nid_postfix(attr, pshared)); } int APS5_VABI scePthreadCondattrGetclock(void) { @@ -2349,13 +2367,11 @@ int APS5_VABI scePthreadCondattrGetclock(void) { } int APS5_VABI scePthreadGetconcurrency(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return pthread_getconcurrency_nid_postfix(); } -int APS5_VABI scePthreadGetcpuclockid(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadGetcpuclockid(void* thread, int* clockId) { + return SceResult(pthread_getcpuclockid_nid_postfix(thread, clockId)); } int APS5_VABI scePthreadMain(void) { diff --git a/core/libs/tests/GuestKernelSysBarrier.cpp b/core/libs/tests/GuestKernelSysBarrier.cpp new file mode 100644 index 00000000000..f5787c511e4 --- /dev/null +++ b/core/libs/tests/GuestKernelSysBarrier.cpp @@ -0,0 +1,103 @@ +#include "SceTypes.hpp" +#include +#include +#include +#include +#include + +struct GuestBarrier; +struct GuestBarrierattr; + +extern "C" { +int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); +int APS5_VABI scePthreadJoin(Pthread thread, void** retval); +Pthread APS5_VABI scePthreadSelf(); +int APS5_VABI scePthreadBarrierattrInit(GuestBarrierattr** attr); +int APS5_VABI scePthreadBarrierattrDestroy(GuestBarrierattr** attr); +int APS5_VABI scePthreadBarrierattrGetpshared(GuestBarrierattr* const* attr, int* pshared); +int APS5_VABI scePthreadBarrierattrSetpshared(GuestBarrierattr** attr, int pshared); +int APS5_VABI scePthreadBarrierInit(GuestBarrier** barrier, GuestBarrierattr* const* attr, unsigned count, const char* name); +int APS5_VABI scePthreadBarrierWait(GuestBarrier** barrier); +int APS5_VABI scePthreadBarrierDestroy(GuestBarrier** barrier); +int APS5_VABI scePthreadGetcpuclockid(Pthread thread, int* clockId); +int APS5_VABI pthread_getcpuclockid_nid_postfix(Pthread thread, int* clockId); +int APS5_VABI scePthreadGetconcurrency(); +int APS5_VABI pthread_setconcurrency_nid_postfix(int level); +int APS5_VABI sceKernelMsync(void* address, std::size_t length, int flags); +} + +static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); +static constexpr int SCE_KERNEL_ERROR_EFAULT = static_cast(0x8002000E); +static constexpr int SCE_KERNEL_ERROR_ENOMEM = static_cast(0x8002000C); +static constexpr int PROCESS_PRIVATE = 0; +static constexpr int PROCESS_SHARED = 1; +static constexpr int BARRIER_SERIAL_THREAD = -1; +static constexpr int MS_ASYNC = 0x1; +static constexpr int MS_INVALIDATE = 0x2; +static constexpr unsigned WAITERS = 4; + +static void Require(bool value) { if (!value) std::abort(); } + +struct Context { + GuestBarrier* barrier = nullptr; + std::atomic serial{0}; + std::atomic released{0}; +}; + +static void* APS5_VABI Waiter(void* arg) { + auto& context = *static_cast(arg); + const int result = scePthreadBarrierWait(&context.barrier); + if (result == BARRIER_SERIAL_THREAD) ++context.serial; + else if (result == 0) ++context.released; + else std::abort(); + return nullptr; +} + +int main() { + GuestBarrierattr* attr = nullptr; + Require(scePthreadBarrierattrInit(nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadBarrierattrInit(&attr) == 0 && attr != nullptr); + int pshared = -1; + Require(scePthreadBarrierattrGetpshared(&attr, &pshared) == 0 && pshared == PROCESS_PRIVATE); + Require(scePthreadBarrierattrSetpshared(&attr, PROCESS_SHARED) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadBarrierattrSetpshared(&attr, PROCESS_PRIVATE) == 0); + + Context context; + Require(scePthreadBarrierInit(&context.barrier, &attr, 0, "zero") == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadBarrierInit(nullptr, &attr, WAITERS, "null") == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadBarrierInit(&context.barrier, &attr, WAITERS, "barrier") == 0 && context.barrier != nullptr); + Require(scePthreadBarrierattrDestroy(&attr) == 0 && attr == nullptr); + Require(scePthreadBarrierattrDestroy(&attr) == SCE_KERNEL_ERROR_EINVAL); + + for (int round = 0; round < 2; ++round) { + context.serial = 0; + context.released = 0; + std::array threads{}; + for (auto& thread : threads) Require(scePthreadCreate(&thread, nullptr, Waiter, &context, nullptr) == 0); + for (auto& thread : threads) Require(scePthreadJoin(thread, nullptr) == 0); + Require(context.serial == 1 && context.released == static_cast(WAITERS) - 1); + } + Require(scePthreadBarrierDestroy(&context.barrier) == 0 && context.barrier == nullptr); + Require(scePthreadBarrierDestroy(&context.barrier) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadBarrierWait(nullptr) == SCE_KERNEL_ERROR_EINVAL); + + GuestBarrier* single = nullptr; + Require(scePthreadBarrierInit(&single, nullptr, 1, nullptr) == 0); + Require(scePthreadBarrierWait(&single) == BARRIER_SERIAL_THREAD); + Require(scePthreadBarrierDestroy(&single) == 0); + + int clock = 0; + int posixClock = 0; + Require(scePthreadGetcpuclockid(scePthreadSelf(), &clock) == 0); + Require(pthread_getcpuclockid_nid_postfix(scePthreadSelf(), &posixClock) == 0 && clock == posixClock); + Require(scePthreadGetcpuclockid(scePthreadSelf(), nullptr) == SCE_KERNEL_ERROR_EFAULT); + Require(scePthreadGetcpuclockid(nullptr, &clock) == SCE_KERNEL_ERROR_EINVAL); + + Require(pthread_setconcurrency_nid_postfix(3) == 0 && scePthreadGetconcurrency() == 3); + Require(pthread_setconcurrency_nid_postfix(0) == 0 && scePthreadGetconcurrency() == 0); + + auto* unmapped = reinterpret_cast(static_cast(0x4000)); + Require(sceKernelMsync(unmapped, 0x4000, 0) == SCE_KERNEL_ERROR_ENOMEM); + Require(sceKernelMsync(unmapped, 0x4000, MS_ASYNC | MS_INVALIDATE) == SCE_KERNEL_ERROR_EINVAL); + return 0; +} diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index 25499ae3d14..57f8251ae50 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -18,3 +18,7 @@ - [fork](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) throws: creating a child process is not supported. PPSA19111 imports it and fails to load without it. Its engine calls `fork` in `Posix_Daemonize`, only with `-daemonize`, and in `Platform_ShellExecute` before `execvp("xdg-open")`, which the main menu's Previews button reaches, so pressing Previews ends in this throw. [setsid](../../../core/libs/prx/libkernel_sys/Export.cpp) fails with `EPERM`, as FreeBSD's does for a process group leader, since the guest is taken to lead its own process group (see `kill`). [umask](../../../core/libs/prx/libkernel_sys/Export.cpp) keeps the guest's mask (initially 022, masked to 07777) and returns the previous one, Linux named shared-memory creation applies it, but ordinary files are still created with the host process's mask - [chroot](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) follows FreeBSD 11.4's [`sys_chroot`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_syscalls.c#L776-L807) and [`pwd_chroot`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L3059-L3085) for a root caller: `EFAULT` for a null path, `ENOENT` for an empty or missing one, `ENAMETOOLONG` for a path of 1024 bytes or more or a component over 255, and `ENOTDIR` for a path that is not a directory or passes through a file. A directory that is the guest root (`/`, `/..`, or `.` after `fchdir` to a descriptor of `/`) succeeds and changes nothing, as the root stays the same directory; the guest is taken never to have changed its root, so open directory descriptors do not make it fail with `EPERM` (`kern.chroot_allow_open_directories` 1). Any other directory throws, naming the path: the guest path resolver has a fixed root and mount aliases (`/app0`, `/savedata0`) that do not move with it. On the console a title starts in a sandbox root, where `pwd_chroot` refuses with `EPERM` while a directory is open; whether that holds for titles, and whether a title holds `PRIV_VFS_CHROOT`, are unverified. Porpoise (PPSA99764) weak-imports it and calls it only as `fchdir` to its saved descriptor of `/` then `chroot(".")`, to put its sandbox root back after a jailbreak daemon replaced it ([`put_root_back`](https://github.com/elripalda/Porpoise-Dolphin-Emulator-for-PS5/blob/2ed1ab5a06995aa198f7bccd4da0f90c3fc29d39/src/porpoise_jailbreak.cpp#L303-L331)) - [fchown](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - ownership is not modelled: the guest's own owner and group (0) and the unchanged value (-1) are accepted, a descriptor that is not open fails with `EBADF`, and any other owner or group throws, naming it. The check reads the descriptor with `fstat`, ignoring the CRT's invalid-parameter handler on Windows as `DescriptorControl` does. The console's ownership rules are unverified. Morrowind (`mshivam019/morrowind-ps5`) imports it + +### Unknown function info + +- [scePthreadBarrier*, scePthreadBarrierattr*, scePthreadGetcpuclockid, scePthreadGetconcurrency and sceKernelMsync](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) call libkernel's `pthread_barrier*`, `pthread_getcpuclockid`, `pthread_getconcurrency` and `msync` and turn a POSIX error into `0x80020000 | errno`; that the console's `sce` forms do the same is not measured. Signatures follow the [Orbital type database](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json); `scePthreadBarrierInit` ignores its name, and `scePthreadBarrierWait` returns -1 to the serial thread, both as in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/kernel/threads/barrier.cpp) (PS4). `scePthreadGetconcurrency` returns the level unchanged From d6b384688af7a6bbc7afa56f20a9d7a505f184bb Mon Sep 17 00:00:00 2001 From: Federico Marras Date: Wed, 21 Oct 2026 15:18:19 +0200 Subject: [PATCH 2/3] feat(libkernel_sys): implement the scePthread scope, atfork and setconcurrency forms --- core/libs/CMakeLists.txt | 7 +- .../prx/libkernel_sys/Export.Discovery.cpp | 24 +++---- core/libs/tests/GuestKernelSysPthread.cpp | 66 +++++++++++++++++++ docs/dev/debt/libkernel_sys.md | 1 + 4 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 core/libs/tests/GuestKernelSysPthread.cpp diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index e5ae4a8cdb1..11721afa82c 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -306,12 +306,17 @@ if(BUILD_TESTING) configure_windows_unwind(guest_kernel_sys_barrier_tests) add_test(NAME guest_kernel_sys_barrier COMMAND guest_kernel_sys_barrier_tests) set_tests_properties(guest_kernel_sys_barrier PROPERTIES TIMEOUT 20) + add_test_executable(guest_kernel_sys_pthread_tests tests/GuestKernelSysPthread.cpp) + target_include_directories(guest_kernel_sys_pthread_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_pthread_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_pthread_tests) + add_test(NAME guest_kernel_sys_pthread COMMAND guest_kernel_sys_pthread_tests) if(Python3_Interpreter_FOUND) add_test(NAME nid_verneed COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_verneed.py $) endif() if(WIN32) - set_tests_properties(guest_kernel_sys guest_kernel_sys_barrier PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_kernel_sys guest_kernel_sys_barrier guest_kernel_sys_pthread PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() if(APPLE) diff --git a/core/libs/prx/libkernel_sys/Export.Discovery.cpp b/core/libs/prx/libkernel_sys/Export.Discovery.cpp index f56381b92b8..b431738db3b 100644 --- a/core/libs/prx/libkernel_sys/Export.Discovery.cpp +++ b/core/libs/prx/libkernel_sys/Export.Discovery.cpp @@ -14,6 +14,10 @@ int APS5_VABI pthread_barrierattr_getpshared_nid_postfix(void* const* attr, int* int APS5_VABI pthread_barrierattr_setpshared_nid_postfix(void** attr, int pshared); int APS5_VABI pthread_getcpuclockid_nid_postfix(void* thread, int* clockId); int APS5_VABI pthread_getconcurrency_nid_postfix(); +int APS5_VABI pthread_setconcurrency_nid_postfix(int level); +int APS5_VABI pthread_attr_getscope_nid_postfix(void* const* attr, int* scope); +int APS5_VABI pthread_attr_setscope_nid_postfix(void** attr, int scope); +int APS5_VABI pthread_atfork_nid_postfix(void (APS5_VABI* prepare)(), void (APS5_VABI* parent)(), void (APS5_VABI* child)()); int APS5_VABI msync_nid_postfix(void* address, std::size_t length, int flags); } @@ -2311,14 +2315,12 @@ int APS5_VABI sceLibcMspaceCreateForMonoMutex(void) { return 0; } -int APS5_VABI scePthreadAtfork(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadAtfork(void (APS5_VABI* prepare)(), void (APS5_VABI* parent)(), void (APS5_VABI* child)()) { + return SceResult(pthread_atfork_nid_postfix(prepare, parent, child)); } -int APS5_VABI scePthreadAttrGetscope(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadAttrGetscope(void* const* attr, int* scope) { + return SceResult(pthread_attr_getscope_nid_postfix(attr, scope)); } int APS5_VABI scePthreadAttrSetcreatesuspend(void) { @@ -2326,9 +2328,8 @@ int APS5_VABI scePthreadAttrSetcreatesuspend(void) { return 0; } -int APS5_VABI scePthreadAttrSetscope(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadAttrSetscope(void** attr, int scope) { + return SceResult(pthread_attr_setscope_nid_postfix(attr, scope)); } int APS5_VABI scePthreadBarrierDestroy(void** barrier) { @@ -2454,9 +2455,8 @@ int APS5_VABI scePthreadSetName(void) { return 0; } -int APS5_VABI scePthreadSetconcurrency(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadSetconcurrency(int level) { + return SceResult(pthread_setconcurrency_nid_postfix(level)); } int APS5_VABI scePthreadSingle(void) { diff --git a/core/libs/tests/GuestKernelSysPthread.cpp b/core/libs/tests/GuestKernelSysPthread.cpp new file mode 100644 index 00000000000..9d352aa66c0 --- /dev/null +++ b/core/libs/tests/GuestKernelSysPthread.cpp @@ -0,0 +1,66 @@ +#include "SceTypes.hpp" +#include "prx/libkernel/Pthread/include/Atfork.hpp" +#include + +extern "C" { +int APS5_VABI scePthreadAttrInit(PthreadAttr* attr); +int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr); +int APS5_VABI scePthreadAttrGetscope(const PthreadAttr* attr, int* scope); +int APS5_VABI scePthreadAttrSetscope(PthreadAttr* attr, int scope); +int APS5_VABI scePthreadAtfork(PthreadAtforkHandler prepare, PthreadAtforkHandler parent, PthreadAtforkHandler child); +int APS5_VABI scePthreadSetconcurrency(int level); +int APS5_VABI scePthreadGetconcurrency(); +} + +static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); +static constexpr int SCOPE_PROCESS = 0; +static constexpr int SCOPE_SYSTEM = 2; + +static void Require(bool value) { if (!value) std::abort(); } + +static void APS5_VABI Prepare() {} +static void APS5_VABI Parent() {} +static void APS5_VABI Child() {} + +static void TestScope() { + PthreadAttr attr{}; + Require(scePthreadAttrInit(&attr) == 0); + int scope = -1; + Require(scePthreadAttrGetscope(&attr, &scope) == 0 && scope == SCOPE_SYSTEM); + Require(scePthreadAttrSetscope(&attr, SCOPE_PROCESS) == 0); + Require(scePthreadAttrGetscope(&attr, &scope) == 0 && scope == SCOPE_PROCESS); + for (const int invalid : {1, 3, -1}) { + Require(scePthreadAttrSetscope(&attr, invalid) == SCE_KERNEL_ERROR_EINVAL); + scope = -1; + Require(scePthreadAttrGetscope(&attr, &scope) == 0 && scope == SCOPE_PROCESS); + } + Require(scePthreadAttrSetscope(&attr, SCOPE_SYSTEM) == 0); + Require(scePthreadAttrGetscope(&attr, &scope) == 0 && scope == SCOPE_SYSTEM); + Require(scePthreadAttrGetscope(&attr, nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadAttrGetscope(nullptr, &scope) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadAttrSetscope(nullptr, SCOPE_PROCESS) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadAttrDestroy(&attr) == 0); +} + +static void TestConcurrency() { + Require(scePthreadSetconcurrency(3) == 0 && scePthreadGetconcurrency() == 3); + Require(scePthreadSetconcurrency(-1) == SCE_KERNEL_ERROR_EINVAL && scePthreadGetconcurrency() == 3); + Require(scePthreadSetconcurrency(0) == 0 && scePthreadGetconcurrency() == 0); +} + +static void TestAtfork() { + const auto before = PthreadAtforkHandlers_nid_no_patch().size(); + Require(scePthreadAtfork(Prepare, Parent, Child) == 0); + Require(scePthreadAtfork(nullptr, nullptr, nullptr) == 0); + const auto handlers = PthreadAtforkHandlers_nid_no_patch(); + Require(handlers.size() == before + 2); + Require(handlers[before].prepare == Prepare && handlers[before].parent == Parent && handlers[before].child == Child); + Require(handlers[before + 1].prepare == nullptr && handlers[before + 1].parent == nullptr && handlers[before + 1].child == nullptr); +} + +int main() { + TestScope(); + TestConcurrency(); + TestAtfork(); + return 0; +} diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index 57f8251ae50..4fad705eef1 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -22,3 +22,4 @@ ### Unknown function info - [scePthreadBarrier*, scePthreadBarrierattr*, scePthreadGetcpuclockid, scePthreadGetconcurrency and sceKernelMsync](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) call libkernel's `pthread_barrier*`, `pthread_getcpuclockid`, `pthread_getconcurrency` and `msync` and turn a POSIX error into `0x80020000 | errno`; that the console's `sce` forms do the same is not measured. Signatures follow the [Orbital type database](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json); `scePthreadBarrierInit` ignores its name, and `scePthreadBarrierWait` returns -1 to the serial thread, both as in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/kernel/threads/barrier.cpp) (PS4). `scePthreadGetconcurrency` returns the level unchanged +- [scePthreadAttrGetscope, scePthreadAttrSetscope, scePthreadAtfork and scePthreadSetconcurrency](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) call libkernel's `pthread_attr_getscope`, `pthread_attr_setscope`, `pthread_atfork` and `pthread_setconcurrency` and turn a POSIX error into `0x80020000 | errno`, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/kernel/threads/pthread_attr.cpp#L392) exports `scePthreadAttrGetscope` as `ORBIS(posix_pthread_attr_getscope)`; shadPS4 has no export for the other three, whose names and NIDs are in the [symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) and whose signatures follow the same POSIX forms. Not measured on a console. As in libkernel, the scope is 0 (process) or 2 (system), and `scePthreadAtfork` handlers are only recorded, as no process forks From f73a4e81335cf26258881c2a46b4fb676abf2b11 Mon Sep 17 00:00:00 2001 From: Federico Marras Date: Wed, 21 Oct 2026 15:18:19 +0200 Subject: [PATCH 3/3] feat(libkernel_sys): implement the scePthread scope, atfork and setconcurrency forms --- core/libs/CMakeLists.txt | 7 +- .../prx/libkernel_sys/Export.Discovery.cpp | 24 +++---- core/libs/tests/GuestKernelSysPthread.cpp | 66 +++++++++++++++++++ docs/dev/debt/libkernel_sys.md | 1 + 4 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 core/libs/tests/GuestKernelSysPthread.cpp diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 16cc1b019b8..ac235edc514 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -321,12 +321,17 @@ if(BUILD_TESTING) configure_windows_unwind(guest_kernel_sys_barrier_tests) add_test(NAME guest_kernel_sys_barrier COMMAND guest_kernel_sys_barrier_tests) set_tests_properties(guest_kernel_sys_barrier PROPERTIES TIMEOUT 20) + add_test_executable(guest_kernel_sys_pthread_tests tests/GuestKernelSysPthread.cpp) + target_include_directories(guest_kernel_sys_pthread_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_pthread_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_pthread_tests) + add_test(NAME guest_kernel_sys_pthread COMMAND guest_kernel_sys_pthread_tests) if(Python3_Interpreter_FOUND) add_test(NAME nid_verneed COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_verneed.py $) endif() if(WIN32) - set_tests_properties(guest_kernel_sys guest_kernel_sys_thread_defaults guest_kernel_sys_barrier PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_kernel_sys guest_kernel_sys_thread_defaults guest_kernel_sys_barrier guest_kernel_sys_pthread PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() if(APPLE) diff --git a/core/libs/prx/libkernel_sys/Export.Discovery.cpp b/core/libs/prx/libkernel_sys/Export.Discovery.cpp index c07286436a3..4fd2ea6eaa7 100644 --- a/core/libs/prx/libkernel_sys/Export.Discovery.cpp +++ b/core/libs/prx/libkernel_sys/Export.Discovery.cpp @@ -14,6 +14,10 @@ int APS5_VABI pthread_barrierattr_getpshared_nid_postfix(void* const* attr, int* int APS5_VABI pthread_barrierattr_setpshared_nid_postfix(void** attr, int pshared); int APS5_VABI pthread_getcpuclockid_nid_postfix(void* thread, int* clockId); int APS5_VABI pthread_getconcurrency_nid_postfix(); +int APS5_VABI pthread_setconcurrency_nid_postfix(int level); +int APS5_VABI pthread_attr_getscope_nid_postfix(void* const* attr, int* scope); +int APS5_VABI pthread_attr_setscope_nid_postfix(void** attr, int scope); +int APS5_VABI pthread_atfork_nid_postfix(void (APS5_VABI* prepare)(), void (APS5_VABI* parent)(), void (APS5_VABI* child)()); int APS5_VABI msync_nid_postfix(void* address, std::size_t length, int flags); } @@ -2306,14 +2310,12 @@ int APS5_VABI sceLibcMspaceCreateForMonoMutex(void) { return 0; } -int APS5_VABI scePthreadAtfork(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadAtfork(void (APS5_VABI* prepare)(), void (APS5_VABI* parent)(), void (APS5_VABI* child)()) { + return SceResult(pthread_atfork_nid_postfix(prepare, parent, child)); } -int APS5_VABI scePthreadAttrGetscope(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadAttrGetscope(void* const* attr, int* scope) { + return SceResult(pthread_attr_getscope_nid_postfix(attr, scope)); } int APS5_VABI scePthreadAttrSetcreatesuspend(void) { @@ -2321,9 +2323,8 @@ int APS5_VABI scePthreadAttrSetcreatesuspend(void) { return 0; } -int APS5_VABI scePthreadAttrSetscope(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadAttrSetscope(void** attr, int scope) { + return SceResult(pthread_attr_setscope_nid_postfix(attr, scope)); } int APS5_VABI scePthreadBarrierDestroy(void** barrier) { @@ -2439,9 +2440,8 @@ int APS5_VABI scePthreadSetBesteffort(void) { return 0; } -int APS5_VABI scePthreadSetconcurrency(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePthreadSetconcurrency(int level) { + return SceResult(pthread_setconcurrency_nid_postfix(level)); } int APS5_VABI scePthreadSingle(void) { diff --git a/core/libs/tests/GuestKernelSysPthread.cpp b/core/libs/tests/GuestKernelSysPthread.cpp new file mode 100644 index 00000000000..9d352aa66c0 --- /dev/null +++ b/core/libs/tests/GuestKernelSysPthread.cpp @@ -0,0 +1,66 @@ +#include "SceTypes.hpp" +#include "prx/libkernel/Pthread/include/Atfork.hpp" +#include + +extern "C" { +int APS5_VABI scePthreadAttrInit(PthreadAttr* attr); +int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr); +int APS5_VABI scePthreadAttrGetscope(const PthreadAttr* attr, int* scope); +int APS5_VABI scePthreadAttrSetscope(PthreadAttr* attr, int scope); +int APS5_VABI scePthreadAtfork(PthreadAtforkHandler prepare, PthreadAtforkHandler parent, PthreadAtforkHandler child); +int APS5_VABI scePthreadSetconcurrency(int level); +int APS5_VABI scePthreadGetconcurrency(); +} + +static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); +static constexpr int SCOPE_PROCESS = 0; +static constexpr int SCOPE_SYSTEM = 2; + +static void Require(bool value) { if (!value) std::abort(); } + +static void APS5_VABI Prepare() {} +static void APS5_VABI Parent() {} +static void APS5_VABI Child() {} + +static void TestScope() { + PthreadAttr attr{}; + Require(scePthreadAttrInit(&attr) == 0); + int scope = -1; + Require(scePthreadAttrGetscope(&attr, &scope) == 0 && scope == SCOPE_SYSTEM); + Require(scePthreadAttrSetscope(&attr, SCOPE_PROCESS) == 0); + Require(scePthreadAttrGetscope(&attr, &scope) == 0 && scope == SCOPE_PROCESS); + for (const int invalid : {1, 3, -1}) { + Require(scePthreadAttrSetscope(&attr, invalid) == SCE_KERNEL_ERROR_EINVAL); + scope = -1; + Require(scePthreadAttrGetscope(&attr, &scope) == 0 && scope == SCOPE_PROCESS); + } + Require(scePthreadAttrSetscope(&attr, SCOPE_SYSTEM) == 0); + Require(scePthreadAttrGetscope(&attr, &scope) == 0 && scope == SCOPE_SYSTEM); + Require(scePthreadAttrGetscope(&attr, nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadAttrGetscope(nullptr, &scope) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadAttrSetscope(nullptr, SCOPE_PROCESS) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadAttrDestroy(&attr) == 0); +} + +static void TestConcurrency() { + Require(scePthreadSetconcurrency(3) == 0 && scePthreadGetconcurrency() == 3); + Require(scePthreadSetconcurrency(-1) == SCE_KERNEL_ERROR_EINVAL && scePthreadGetconcurrency() == 3); + Require(scePthreadSetconcurrency(0) == 0 && scePthreadGetconcurrency() == 0); +} + +static void TestAtfork() { + const auto before = PthreadAtforkHandlers_nid_no_patch().size(); + Require(scePthreadAtfork(Prepare, Parent, Child) == 0); + Require(scePthreadAtfork(nullptr, nullptr, nullptr) == 0); + const auto handlers = PthreadAtforkHandlers_nid_no_patch(); + Require(handlers.size() == before + 2); + Require(handlers[before].prepare == Prepare && handlers[before].parent == Parent && handlers[before].child == Child); + Require(handlers[before + 1].prepare == nullptr && handlers[before + 1].parent == nullptr && handlers[before + 1].child == nullptr); +} + +int main() { + TestScope(); + TestConcurrency(); + TestAtfork(); + return 0; +} diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index 4c333c4b786..af2ddf76fd7 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -23,3 +23,4 @@ ### Unknown function info - [scePthreadBarrier*, scePthreadBarrierattr*, scePthreadGetcpuclockid, scePthreadGetconcurrency and sceKernelMsync](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) call libkernel's `pthread_barrier*`, `pthread_getcpuclockid`, `pthread_getconcurrency` and `msync` and turn a POSIX error into `0x80020000 | errno`; that the console's `sce` forms do the same is not measured. Signatures follow the [Orbital type database](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json); `scePthreadBarrierInit` ignores its name, and `scePthreadBarrierWait` returns -1 to the serial thread, both as in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/kernel/threads/barrier.cpp) (PS4). `scePthreadGetconcurrency` returns the level unchanged +- [scePthreadAttrGetscope, scePthreadAttrSetscope, scePthreadAtfork and scePthreadSetconcurrency](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) call libkernel's `pthread_attr_getscope`, `pthread_attr_setscope`, `pthread_atfork` and `pthread_setconcurrency` and turn a POSIX error into `0x80020000 | errno`, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/kernel/threads/pthread_attr.cpp#L392) exports `scePthreadAttrGetscope` as `ORBIS(posix_pthread_attr_getscope)`; shadPS4 has no export for the other three, whose names and NIDs are in the [symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) and whose signatures follow the same POSIX forms. Not measured on a console. As in libkernel, the scope is 0 (process) or 2 (system), and `scePthreadAtfork` handlers are only recorded, as no process forks