Merge pull request #4153 from hugodearaujo/fix/pthread-attr-errors

fix(libkernel): return the console's errors from the scePthreadAttr calls
This commit is contained in:
Dean 2026-10-12 21:41:31 +13:00 committed by GitHub
commit 5ccf78e4e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 118 additions and 17 deletions

View File

@ -369,6 +369,16 @@ if(BUILD_TESTING)
set_tests_properties(guest_pthread_start PROPERTIES TIMEOUT 60 ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_pthread_attr_errors_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadAttrErrors.cpp)
target_include_directories(guest_pthread_attr_errors_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_pthread_attr_errors_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_pthread_attr_errors_tests)
add_test(NAME guest_pthread_attr_errors COMMAND guest_pthread_attr_errors_tests)
set_tests_properties(guest_pthread_attr_errors PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_pthread_attr_errors PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_pthread_stack_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadStack.cpp)
target_include_directories(guest_pthread_stack_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_pthread_stack_tests PRIVATE libkernel libc)

View File

@ -33,29 +33,31 @@ int APS5_VABI scePthreadAttrInit(PthreadAttr* attr) {
}
int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr) {
if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr");
if (!attr || !*attr) return SCE_KERNEL_ERROR_EINVAL;
delete *attr;
*attr = nullptr;
return SCE_OK;
}
int APS5_VABI scePthreadAttrSetdetachstate(PthreadAttr* attr, int detachstate) {
if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr");
if (detachstate != DETACH_JOINABLE && detachstate != DETACH_DETACHED)
throw std::runtime_error(std::string(__func__) + ": invalid state");
if (!attr || !*attr) return SCE_KERNEL_ERROR_EINVAL;
if (detachstate != DETACH_JOINABLE && detachstate != DETACH_DETACHED) return SCE_KERNEL_ERROR_EINVAL;
(*attr)->_detachstate = detachstate;
return SCE_OK;
}
int APS5_VABI scePthreadAttrSetschedparam(PthreadAttr* attr, const KernelSchedParam* param) {
if (!attr || !*attr || !param)
throw std::runtime_error(std::string(__func__) + ": null arg");
if (!attr || !*attr) return SCE_KERNEL_ERROR_EINVAL;
constexpr int HighestPriority = 256;
constexpr int LowestPriority = 767;
if (!param || param->sched_priority < HighestPriority || param->sched_priority > LowestPriority)
return SCE_KERNEL_ERROR_EOPNOTSUPP;
(*attr)->_schedpriority = param->sched_priority;
return SCE_OK;
}
int APS5_VABI scePthreadAttrSetstacksize(PthreadAttr* attr, std::size_t stacksize) {
if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr");
if (!attr || !*attr) return SCE_KERNEL_ERROR_EINVAL;
if (stacksize < MIN_STACK_SIZE) return SCE_KERNEL_ERROR_EINVAL;
#ifdef _WIN32
if (stacksize > std::numeric_limits<unsigned>::max() - 0xffffu)
@ -89,14 +91,14 @@ int APS5_VABI scePthreadAttrGet(Pthread thread, PthreadAttr* attr) {
}
int APS5_VABI scePthreadAttrGetaffinity(const PthreadAttr* attr, KernelCpumask* mask) {
if (!attr || !*attr || !mask) throw std::runtime_error("scePthreadAttrGetaffinity: null argument");
if (!attr || !*attr) return SCE_KERNEL_ERROR_EINVAL;
if (!mask) throw std::runtime_error("scePthreadAttrGetaffinity: null argument");
*mask = (*attr)->_affinity;
return SCE_OK;
}
int APS5_VABI scePthreadAttrGetdetachstate(const PthreadAttr* attr, int* state) {
if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr");
if (!state) throw std::runtime_error(std::string(__func__) + ": null output");
if (!attr || !*attr || !state) return SCE_KERNEL_ERROR_EINVAL;
*state = (*attr)->_detachstate;
return SCE_OK;
}
@ -109,8 +111,7 @@ int APS5_VABI scePthreadAttrGetguardsize(const PthreadAttr* attr, size_t* guard_
}
int APS5_VABI scePthreadAttrGetschedparam(const PthreadAttr* attr, KernelSchedParam* param) {
if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr");
if (!param) throw std::runtime_error(std::string(__func__) + ": null output");
if (!attr || !*attr || !param) return SCE_KERNEL_ERROR_EINVAL;
param->sched_priority = (*attr)->_schedpriority;
return SCE_OK;
}
@ -130,14 +131,13 @@ int APS5_VABI scePthreadAttrGetstackaddr(const PthreadAttr* attr, void** stack_a
}
int APS5_VABI scePthreadAttrGetstacksize(const PthreadAttr* attr, size_t* stack_size) {
if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr");
if (!stack_size) throw std::runtime_error(std::string(__func__) + ": null output");
if (!attr || !*attr || !stack_size) return SCE_KERNEL_ERROR_EINVAL;
*stack_size = (*attr)->_stacksize;
return SCE_OK;
}
int APS5_VABI scePthreadAttrSetaffinity(PthreadAttr* attr, KernelCpumask mask) {
if (!attr || !*attr) throw std::runtime_error("scePthreadAttrSetaffinity: null attr");
if (!attr || !*attr) return SCE_KERNEL_ERROR_EINVAL;
(*attr)->_affinity = mask;
(*attr)->_affinityExplicit = true;
return SCE_OK;
@ -150,13 +150,18 @@ int APS5_VABI scePthreadAttrSetguardsize(PthreadAttr* attr, size_t guard_size) {
}
int APS5_VABI scePthreadAttrSetinheritsched(PthreadAttr* attr, int inherit_sched) {
if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr");
if (!attr || !*attr) return SCE_KERNEL_ERROR_EINVAL;
constexpr int ExplicitSched = 0;
constexpr int InheritSched = 4;
if (inherit_sched != ExplicitSched && inherit_sched != InheritSched) return SCE_KERNEL_ERROR_EOPNOTSUPP;
(*attr)->_inheritsched = inherit_sched;
return SCE_OK;
}
int APS5_VABI scePthreadAttrSetschedpolicy(PthreadAttr* attr, int policy) {
if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr");
if (!attr || !*attr) return SCE_KERNEL_ERROR_EINVAL;
constexpr int SchedRr = 3;
if (policy != SCHED_FIFO_PS5 && policy != SchedRr) return SCE_KERNEL_ERROR_EOPNOTSUPP;
(*attr)->_schedpolicy = policy;
return SCE_OK;
}

View File

@ -0,0 +1,85 @@
#include "SceTypes.hpp"
#include <cstddef>
#include <cstdlib>
#include <initializer_list>
extern "C" {
int APS5_VABI scePthreadAttrInit(PthreadAttr* attr);
int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr);
int APS5_VABI scePthreadAttrSetstacksize(PthreadAttr* attr, std::size_t stackSize);
int APS5_VABI scePthreadAttrGetstacksize(const PthreadAttr* attr, std::size_t* stackSize);
int APS5_VABI scePthreadAttrSetdetachstate(PthreadAttr* attr, int state);
int APS5_VABI scePthreadAttrGetdetachstate(const PthreadAttr* attr, int* state);
int APS5_VABI scePthreadAttrSetschedpolicy(PthreadAttr* attr, int policy);
int APS5_VABI scePthreadAttrSetinheritsched(PthreadAttr* attr, int inheritSched);
int APS5_VABI scePthreadAttrSetschedparam(PthreadAttr* attr, const KernelSchedParam* param);
int APS5_VABI scePthreadAttrGetschedparam(const PthreadAttr* attr, KernelSchedParam* param);
int APS5_VABI scePthreadAttrSetaffinity(PthreadAttr* attr, KernelCpumask mask);
int APS5_VABI scePthreadAttrGetaffinity(const PthreadAttr* attr, KernelCpumask* mask);
}
static constexpr int SCE_OK = 0;
static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast<int>(0x80020016u);
static constexpr int SCE_KERNEL_ERROR_EOPNOTSUPP = static_cast<int>(0x8002002Du);
static void Require(bool value) { if (!value) std::abort(); }
int main() {
PthreadAttr attr = nullptr;
Require(scePthreadAttrInit(&attr) == SCE_OK);
for (std::size_t size : {std::size_t{0}, std::size_t{1}, std::size_t{16383}})
Require(scePthreadAttrSetstacksize(&attr, size) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadAttrSetstacksize(&attr, 16384) == SCE_OK);
for (int state : {-1, 2, 3, 100}) Require(scePthreadAttrSetdetachstate(&attr, state) == SCE_KERNEL_ERROR_EINVAL);
for (int state : {0, 1}) Require(scePthreadAttrSetdetachstate(&attr, state) == SCE_OK);
for (int policy : {-1, 0, 2, 4}) Require(scePthreadAttrSetschedpolicy(&attr, policy) == SCE_KERNEL_ERROR_EOPNOTSUPP);
for (int policy : {1, 3}) Require(scePthreadAttrSetschedpolicy(&attr, policy) == SCE_OK);
for (int inherit : {-1, 1, 2, 8}) Require(scePthreadAttrSetinheritsched(&attr, inherit) == SCE_KERNEL_ERROR_EOPNOTSUPP);
for (int inherit : {0, 4}) Require(scePthreadAttrSetinheritsched(&attr, inherit) == SCE_OK);
KernelSchedParam param{};
for (int priority : {-1, 0, 255, 768, 900, 960}) {
param.sched_priority = priority;
Require(scePthreadAttrSetschedparam(&attr, &param) == SCE_KERNEL_ERROR_EOPNOTSUPP);
}
Require(scePthreadAttrSetschedparam(&attr, nullptr) == SCE_KERNEL_ERROR_EOPNOTSUPP);
for (int priority : {256, 767}) {
param.sched_priority = priority;
Require(scePthreadAttrSetschedparam(&attr, &param) == SCE_OK);
KernelSchedParam read{};
Require(scePthreadAttrGetschedparam(&attr, &read) == SCE_OK && read.sched_priority == priority);
}
for (KernelCpumask mask : {KernelCpumask{0}, KernelCpumask{1}, ~KernelCpumask{0}})
Require(scePthreadAttrSetaffinity(&attr, mask) == SCE_OK);
std::size_t size = 0;
int state = 0;
KernelCpumask mask = 0;
Require(scePthreadAttrGetstacksize(&attr, nullptr) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadAttrGetdetachstate(&attr, nullptr) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadAttrGetschedparam(&attr, nullptr) == SCE_KERNEL_ERROR_EINVAL);
for (PthreadAttr* missing : {static_cast<PthreadAttr*>(nullptr), static_cast<PthreadAttr*>(&attr)}) {
PthreadAttr unset = nullptr;
PthreadAttr* target = missing ? &unset : nullptr;
Require(scePthreadAttrSetstacksize(target, 16384) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadAttrSetdetachstate(target, 0) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadAttrSetschedpolicy(target, 1) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadAttrSetinheritsched(target, 0) == SCE_KERNEL_ERROR_EINVAL);
param.sched_priority = 700;
Require(scePthreadAttrSetschedparam(target, &param) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadAttrSetaffinity(target, 1) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadAttrGetstacksize(target, &size) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadAttrGetdetachstate(target, &state) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadAttrGetschedparam(target, &param) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadAttrGetaffinity(target, &mask) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadAttrDestroy(target) == SCE_KERNEL_ERROR_EINVAL);
}
Require(scePthreadAttrDestroy(&attr) == SCE_OK);
Require(scePthreadAttrDestroy(&attr) == SCE_KERNEL_ERROR_EINVAL);
}

View File

@ -202,6 +202,7 @@ Throughout the project, every function at every stage either **does exactly what
- [scePthreadRwlockDestroy, pthread_rwlock_destroy](../../core/libs/prx/libkernel/Pthread/src/Rwlock.cpp) (libkernel) - a rwlock held for reading or writing returns `EBUSY` and keeps its handle, as [KytyPS5](https://github.com/KytyPS5/KytyPS5) does; FreeBSD's [libthr](https://github.com/freebsd/freebsd-src/blob/main/lib/libthr/thread/thr_rwlock.c) and shadPS4 free it without checking, and the console's answer is unverified. Destroying a rwlock while another thread is acquiring it is not guarded
- [scePthreadCondWait / scePthreadCondTimedwait](../../core/libs/prx/libkernel/Pthread/src/Cond.cpp) (libkernel) - before waiting, FreeBSD libthr's [`cond_wait_common`](https://github.com/freebsd/freebsd-src/blob/main/lib/libthr/thread/thr_cond.c#L1992-L2029) checks ownership through [`_mutex_owned`](https://github.com/freebsd/freebsd-src/blob/main/lib/libthr/thread/thr_mutex.c#L1166-L1178): an unowned mutex returns `EPERM`, while its destroyed marker returns `EINVAL`. An untouched adaptive static initializer (slot value 1) therefore returns `EPERM`; the test covers this, an uninitialized slot, a mutex owned by another thread, and the destroyed marker for both timed and untimed sce and POSIX waits. A recursive mutex locked several times is released completely for the wait and relocked to the same depth afterwards, as FreeBSD libthr does (`_mutex_cv_unlock`, `_mutex_cv_lock`). These FreeBSD-derived behaviors remain unverified on console hardware
- [sceKernelAioSubmitReadCommandsMultiple / sceKernelAioSubmitWriteCommandsMultiple / sceKernelAioWaitRequests / sceKernelAioPollRequests / sceKernelAioCancelRequest / sceKernelAioCancelRequests / sceKernelAioDeleteRequests](../../core/libs/prx/libkernel/Aio/src/Aio.cpp) (libkernel) - behaviour from shadPS4 (one id per request for the Multiple submits, cancel turns any request aborted and id 0 reports processing, wait mode 2 returns once one request completed, poll copies every state without waiting); the 128 requests or ids per batch limit is from SharpProspero, with an unknown error, so larger batches throw; wait modes other than 1 (and) and 2 (or) throw; null pointers, negative counts and invalid ids return the codes the single-request siblings use, checked before any state is written
- [scePthreadAttr setters and getters](../../core/libs/prx/libkernel/Pthread/src/Attr.cpp) (libkernel) - measured on a PS5 (firmware 13.60): a null or uninitialized attribute returns `EINVAL`, as does a null output of the stack size, detach state and scheduling parameter getters; a policy other than 1 and 3, an inheritance other than 0 and 4, or a priority outside 256 to 767 (or a null parameter) returns `ENOTSUP` (`0x8002002D`); a detach state other than 0 and 1 and a stack size below 16384 return `EINVAL`; any affinity mask is accepted. A null attribute pointer to `scePthreadAttrInit` and a null output of `scePthreadAttrGetaffinity` crashed the payload, so they still throw. A new attribute holds a 64 KiB stack size on the console and 1 MiB here, which the host code that runs on guest threads needs
- [sceKernelGetAvailableCpumask](../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) - returns the default thread affinity, 0x1FFF (CPUs 0 to 12), as KytyPS5 does. Titles pin threads within it (PPSA02664 passes 0x1FFB, Hades II PPSA36082 0x3 and 0x3F); whether CPU 13 is available to a title is not confirmed
- [sceKernelGetCurrentCpu](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - guest affinities are not applied to host threads, so the host CPU index is mapped into the calling thread's affinity clipped to `sceKernelGetAvailableCpumask` (the available mask when they do not overlap): host CPU n reports the (n modulo the CPU count)-th CPU of that mask, so a thread pinned to one CPU reports that CPU, as a FreeBSD `cpuset` guarantees on the console. Which CPU the console reports for a thread allowed on several is not known; shadPS4 returns 0
- [pthread_attr_setsolosched_np](../../core/libs/prx/libkernel/Pthread/Posix/Attr.cpp) (libkernel) takes `(attr, int)` and stores the value through `scePthreadAttrSetsolosched`, returning `EINVAL` (22) for a null or uninitialized attribute like the other POSIX attribute setters. Only the name is public (aerolib `2+pVfgiEd7A`); the signature and behaviour follow the SCE twin, which [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/947627d10d4ef4b68ba3c0189aab6c368e70a8d2/src/kernel/pthread.cpp#L2188-L2199) and [sharpemu](https://github.com/sharpemu/sharpemu/blob/a2983692930564815a77f81e74c151268f5b935f/src/SharpEmu.Libs/Kernel/KernelPthreadExtendedCompatExports.cs#L891-L913) store as an `int` without a range check, and [PS5PCEM](https://github.com/iStark/PS5PCEM/blob/71de7681f06c5db59a1120a3bb554c86780c3f5f/src/hle/libs/kernel_threading.zig#L1396-L1398) implements this alias over it. On a PS5 (firmware 13.60) only 0 and 16 are accepted; every other value returns `ENOTSUP` (45, `0x8002002D` from the SCE call) and keeps the stored one, and the SCE calls return `EINVAL` for a null or uninitialized attribute. PPSA15878 passes 16. Threads created from the attribute are not scheduled differently; what solo scheduling does on the console is unknown. Returnal (PPSA01285 v03.000.005) stopped on it with `what(): pthread_attr_setsolosched_np_nid_postfix not implemented` (#2872, #1155); LEGO Horizon Adventures (PPSA14632, #734) and Palworld (PPSA23126, #2598) import it