Merge pull request #4158 from hugodearaujo/fix/mutex-errors

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

View File

@ -359,6 +359,16 @@ if(BUILD_TESTING)
set_tests_properties(guest_pthread_mutexattr PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
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:$<TARGET_FILE_DIR:libkernel>")
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)

View File

@ -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<PthreadMutexPrivate>();
if (attr)
if (attr && *attr)
replacement->_type = (*attr)->type;
std::lock_guard lock(initializationMutex);
std::atomic_ref<PthreadMutex>(*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<PthreadMutex>(*mutex).store(destroyedMutex(), std::memory_order_release);
return 0;

View File

@ -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);

View File

@ -0,0 +1,50 @@
#include "SceTypes.hpp"
#include <cstdint>
#include <cstdlib>
#include <initializer_list>
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<int>(0x80020016u);
static constexpr int SCE_KERNEL_ERROR_EBUSY = static_cast<int>(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<PthreadMutex>(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);
}

View File

@ -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