diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index f01f14114..b622ce55b 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -359,6 +359,16 @@ if(BUILD_TESTING) set_tests_properties(guest_pthread_mutexattr PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_pthread_mutex_errors_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadMutexErrors.cpp) + target_include_directories(guest_pthread_mutex_errors_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_pthread_mutex_errors_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_pthread_mutex_errors_tests) + add_test(NAME guest_pthread_mutex_errors COMMAND guest_pthread_mutex_errors_tests) + set_tests_properties(guest_pthread_mutex_errors PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_pthread_mutex_errors PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_pthread_start_tests ${LIBS_INCLUDE_DIR}/tests/PthreadStart.cpp) target_include_directories(guest_pthread_start_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_pthread_start_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libkernel/Pthread/src/Mutex.cpp b/core/libs/prx/libkernel/Pthread/src/Mutex.cpp index 605874cd0..4cf22db6e 100644 --- a/core/libs/prx/libkernel/Pthread/src/Mutex.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Mutex.cpp @@ -111,7 +111,7 @@ int APS5_VABI scePthreadMutexattrInit(PthreadMutexattr* attr) { int APS5_VABI scePthreadMutexattrDestroy(PthreadMutexattr* attr) { if (!attr || !*attr) - throw std::invalid_argument("Mutex attributes are not initialized"); + return SCE_KERNEL_ERROR_EINVAL; delete *attr; *attr = nullptr; return 0; @@ -119,7 +119,7 @@ int APS5_VABI scePthreadMutexattrDestroy(PthreadMutexattr* attr) { int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr* attr, int type) { if (!attr || !*attr) - throw std::invalid_argument("Mutex attributes are not initialized"); + return SCE_KERNEL_ERROR_EINVAL; switch (type) { case 1: (*attr)->type = MutexType::ErrorCheck; break; case 2: (*attr)->type = MutexType::Recursive; break; @@ -132,21 +132,19 @@ int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr* attr, int type) { int APS5_VABI scePthreadMutexattrSetprotocol(PthreadMutexattr* attr, int protocol) { if (!attr || !*attr) - throw std::invalid_argument("Mutex attributes are not initialized"); + return SCE_KERNEL_ERROR_EINVAL; constexpr int PrioNone = 0; - constexpr int PrioInherit = 1; - if (protocol != PrioNone && protocol != PrioInherit) - throw std::invalid_argument("Mutex priority protection is unsupported"); + constexpr int PrioProtect = 2; + if (protocol < PrioNone || protocol > PrioProtect) + return SCE_KERNEL_ERROR_EINVAL; return 0; } int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char*) { if (!mutex) throw std::invalid_argument("Mutex pointer is null"); - if (attr && !*attr) - throw std::invalid_argument("Mutex attributes are not initialized"); auto replacement = std::make_unique(); - if (attr) + if (attr && *attr) replacement->_type = (*attr)->type; std::lock_guard lock(initializationMutex); std::atomic_ref(*mutex).store(replacement.release(), std::memory_order_release); @@ -158,7 +156,7 @@ int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex) { throw std::invalid_argument("Mutex pointer is null"); std::lock_guard lock(initializationMutex); if (*mutex == destroyedMutex()) - throw std::runtime_error("Mutex has already been destroyed"); + return SCE_KERNEL_ERROR_EINVAL; if (*mutex == adaptiveInitializer()) { std::atomic_ref(*mutex).store(destroyedMutex(), std::memory_order_release); return 0; diff --git a/core/libs/tests/GuestKernelErrors.cpp b/core/libs/tests/GuestKernelErrors.cpp index 5c14f7fca..6a76e356e 100644 --- a/core/libs/tests/GuestKernelErrors.cpp +++ b/core/libs/tests/GuestKernelErrors.cpp @@ -90,13 +90,7 @@ int main() { Require(scePthreadMutexattrSettype(&attr, 5) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadMutexattrSetprotocol(&attr, PRIO_NONE) == SCE_OK); Require(scePthreadMutexattrSetprotocol(&attr, PRIO_INHERIT) == SCE_OK); - bool protectionRejected = false; - try { - scePthreadMutexattrSetprotocol(&attr, PRIO_PROTECT); - } catch (const std::invalid_argument&) { - protectionRejected = true; - } - Require(protectionRejected); + Require(scePthreadMutexattrSetprotocol(&attr, PRIO_PROTECT) == SCE_OK); PthreadMutex mutex = nullptr; Require(scePthreadMutexInit(&mutex, &attr, nullptr) == SCE_OK); Require(scePthreadMutexattrDestroy(&attr) == SCE_OK); diff --git a/core/libs/tests/GuestPthreadMutexErrors.cpp b/core/libs/tests/GuestPthreadMutexErrors.cpp new file mode 100644 index 000000000..064469d42 --- /dev/null +++ b/core/libs/tests/GuestPthreadMutexErrors.cpp @@ -0,0 +1,50 @@ +#include "SceTypes.hpp" +#include +#include +#include + +extern "C" { +int APS5_VABI scePthreadMutexattrInit(PthreadMutexattr* attr); +int APS5_VABI scePthreadMutexattrDestroy(PthreadMutexattr* attr); +int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr* attr, int type); +int APS5_VABI scePthreadMutexattrSetprotocol(PthreadMutexattr* attr, int protocol); +int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name); +int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex); +int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex); +int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex); +} + +static constexpr int SCE_OK = 0; +static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016u); +static constexpr int SCE_KERNEL_ERROR_EBUSY = static_cast(0x80020010u); + +static void Require(bool value) { if (!value) std::abort(); } + +int main() { + PthreadMutexattr attr = nullptr; + Require(scePthreadMutexattrInit(&attr) == SCE_OK); + for (int type : {-1, 0, 5}) Require(scePthreadMutexattrSettype(&attr, type) == SCE_KERNEL_ERROR_EINVAL); + for (int type : {1, 2, 3, 4}) Require(scePthreadMutexattrSettype(&attr, type) == SCE_OK); + for (int protocol : {-1, 3}) Require(scePthreadMutexattrSetprotocol(&attr, protocol) == SCE_KERNEL_ERROR_EINVAL); + for (int protocol : {0, 1, 2}) Require(scePthreadMutexattrSetprotocol(&attr, protocol) == SCE_OK); + + PthreadMutexattr unset = nullptr; + Require(scePthreadMutexattrSettype(&unset, 1) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrSetprotocol(&unset, 0) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrDestroy(&unset) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrSettype(nullptr, 1) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrDestroy(nullptr) == SCE_KERNEL_ERROR_EINVAL); + + PthreadMutex mutex = nullptr; + Require(scePthreadMutexInit(&mutex, &unset, "unset") == SCE_OK); + Require(scePthreadMutexLock(&mutex) == SCE_OK); + Require(scePthreadMutexDestroy(&mutex) == SCE_KERNEL_ERROR_EBUSY); + Require(scePthreadMutexUnlock(&mutex) == SCE_OK); + Require(scePthreadMutexDestroy(&mutex) == SCE_OK); + Require(mutex == reinterpret_cast(std::uintptr_t{2})); + Require(scePthreadMutexDestroy(&mutex) == SCE_KERNEL_ERROR_EINVAL); + + Require(scePthreadMutexInit(&mutex, &attr, "attr") == SCE_OK); + Require(scePthreadMutexDestroy(&mutex) == SCE_OK); + Require(scePthreadMutexattrDestroy(&attr) == SCE_OK); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 485ef3a57..908359bcd 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -19,6 +19,7 @@ Throughout the project, every function at every stage either **does exactly what - [libSceLoginDialog](../../core/libs/prx/libSceLoginDialog/Export.cpp) - the login dialog runs the common-dialog state machine but shows nothing and reports the user as having cancelled - [libSceCdlgG2P](../../core/libs/prx/libSceCdlgG2P/Export.cpp) - the G2P dialog runs the common-dialog state machine but shows nothing and reports the user as having cancelled. The signatures (`Open` and `GetResult` take a pointer, the others none) and the leading `int32` result are assumed from the other common dialogs, not verified for G2P; PPSA01289 imports it - [libSceVrSetupDialog](../../core/libs/prx/libSceVrSetupDialog/Export.cpp) - the VR setup dialog runs the common-dialog state machine but shows nothing and reports the user as having cancelled, as no headset is supported (see libSceHmd2). The signatures are assumed from the other common dialogs; PPSA12544 imports it +- [scePthreadMutexattrSetprotocol](../../core/libs/prx/libkernel/Pthread/src/Mutex.cpp) (libkernel) - accepts 0 to 2 as a PS5 (firmware 13.60) does and returns `EINVAL` otherwise, but the protocol is not stored or applied: priority inheritance and protection are not modelled. On the console a null or uninitialized mutex attribute returns `EINVAL` from the SCE attribute calls, `scePthreadMutexInit` takes an uninitialized attribute as the default one, and a destroyed mutex returns `EINVAL` from `scePthreadMutexDestroy`; a null mutex pointer crashed the payload and still throws - [libSceHmd](../../core/libs/prx/libSceHmd/Export.cpp) implements only the disconnected-headset path: initialization succeeds, device queries report `NotDetected`, and opening a device returns `DeviceDisconnected`. Headset support, tracking, rendering and additional HMD exports are not implemented; SDK-level ABI compatibility and in-game behaviour remain unverified. - [pthread_mutexattr_setprotocol, pthread_mutexattr_setprioceiling, pthread_mutexattr_getprioceiling](../../core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp) (libkernel) - the protocol and the priority ceiling are stored and reported as in FreeBSD's [libthr](https://github.com/freebsd/freebsd-src/blob/main/lib/libthr/thread/thr_mutexattr.c) (the ceiling is set or read only under `PTHREAD_PRIO_PROTECT`, and setting a protocol resets it to the highest `SCHED_RR` priority), but mutexes do not apply them: a priority-inherit or priority-protect mutex locks like a normal one and does not change its owner's priority - [libSceHmd2](../../core/libs/prx/libSceHmd2/Export.cpp) has no PS VR2 runtime: `sceHmd2Initialize` fails, and every other export it has (and every [libSceVrTracker2](../../core/libs/prx/libSceVrTracker2/Export.cpp) export) throws. PPSA01317 skips its whole VR set-up when initialization fails