fix(libSceUlt): validate status outputs against firmware observations

This commit is contained in:
StoneDC committed 2026-10-26 22:06:56 +08:00
1 parent e6e2b47989
commit 3198588572
7 files changed
+230 -42

No files matched your search

+2 -2
View File
@@ -54,9 +54,9 @@ if(BUILD_TESTING)
add_test_executable(guest_ult_sync_tests ${LIBS_INCLUDE_DIR}/tests/GuestUltSync.cpp)
target_include_directories(guest_ult_sync_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_ult_sync_tests PRIVATE libSceUlt libc)
target_link_libraries(guest_ult_sync_tests PRIVATE libSceUlt libc libkernel)
configure_windows_unwind(guest_ult_sync_tests)
foreach(testCase IN ITEMS mutex-trylock opt-params rwlock-validation rwlock-readers rwlock-writer rwlock-pool rwlock-finalize ulthreads get-info queue-waiters)
foreach(testCase IN ITEMS mutex-trylock opt-params rwlock-validation rwlock-readers rwlock-writer rwlock-pool rwlock-finalize ulthreads get-info queue-waiters info-waiters)
add_test(NAME guest_ult_sync_${testCase} COMMAND guest_ult_sync_tests ${testCase})
set_tests_properties(guest_ult_sync_${testCase} PROPERTIES TIMEOUT 20)
if(WIN32)
+24 -22
View File
@@ -133,6 +133,14 @@ void* APS5_VABI ulthreadRunner(void* arg) {
}
UltThreadIdentity UltCurrentThreadIdentity_nid_no_patch() {
if (gCurrentUlthread != nullptr) return {};
const auto self = scePthreadSelf();
std::uintptr_t thread;
std::memcpy(&thread, self, sizeof(thread));
return {reinterpret_cast<void*>(thread), 1};
}
bool UltWaitingPoolExists(void* pool) {
std::lock_guard<std::mutex> lock(gMutex);
return gResourcePools.contains(pool);
@@ -323,6 +331,7 @@ int APS5_VABI sceUltMutexLock(void* mutex) {
if (!state->_alive) {
return ULT_ERROR_STATE;
}
state->_ownerIdentity = UltCurrentThreadIdentity_nid_no_patch();
state->_owner = self;
state->_lockCount = 1;
return ULT_OK;
@@ -350,6 +359,7 @@ int APS5_VABI sceUltMutexTryLock(void* mutex) {
if (state->_lockCount != 0) {
return ULT_ERROR_BUSY;
}
state->_ownerIdentity = UltCurrentThreadIdentity_nid_no_patch();
state->_owner = self;
state->_lockCount = 1;
return ULT_OK;
@@ -376,6 +386,7 @@ int APS5_VABI sceUltMutexUnlock(void* mutex) {
--state->_lockCount;
if (state->_lockCount == 0) {
state->_owner = {};
state->_ownerIdentity = {};
state->_available.notify_one();
}
return ULT_OK;
@@ -451,12 +462,14 @@ int APS5_VABI sceUltConditionVariableWait(void* condition) {
std::unique_lock lock(held->_mutex);
if (!state->_alive || !held->_alive || held->_owner != owner || held->_lockCount == 0) return ULT_ERROR_STATE;
if (held->_lockCount != 1) throw std::runtime_error("ULT condition wait with a recursively held mutex is not implemented");
const auto ownerIdentity = held->_ownerIdentity;
UltConditionWaiter waiter;
state->_queue.push_back(&waiter);
++state->_waiters;
++held->_conditionWaiters;
held->_lockCount = 0;
held->_owner = {};
held->_ownerIdentity = {};
held->_available.notify_one();
try {
waiter._available.wait(lock, [&] { return waiter._signaled || !state->_alive || !held->_alive; });
@@ -481,6 +494,7 @@ int APS5_VABI sceUltConditionVariableWait(void* condition) {
--held->_conditionWaiters;
if (!held->_alive || !state->_alive) return ULT_ERROR_STATE;
held->_owner = owner;
held->_ownerIdentity = ownerIdentity;
held->_lockCount = 1;
return ULT_OK;
}
@@ -1029,17 +1043,13 @@ int APS5_VABI sceUltGetConditionVariableInfo(void* object, UltConditionVariableI
}
std::lock_guard stateLock(state->_mutex->_mutex);
if (!state->_alive) return ULT_ERROR_STATE;
if (capacity != 0 && !state->_queue.empty()) NotImplemented_nid_no_patch(__func__);
std::memset(info, 0, sizeof(*info));
info->size = sizeof(UltConditionVariableInfo);
info->object = object;
std::memcpy(info->name, state->_name, sizeof(info->name));
info->mutex = state->_mutexObject;
info->waitingThreads = static_cast<std::uint32_t>(state->_queue.size());
const auto toCopy = std::min(capacity, info->waitingThreads);
for (std::uint32_t i = 0; i < toCopy; ++i) {
std::memset(&waitingThreads[i], 0, sizeof(UltWaitingThreadInfo));
waitingThreads[i].size = sizeof(UltWaitingThreadInfo);
}
return ULT_OK;
}
@@ -1055,19 +1065,20 @@ int APS5_VABI sceUltGetMutexInfo(void* object, UltMutexInfo* info, UltWaitingThr
}
std::lock_guard stateLock(state->_mutex);
if (!state->_alive) return ULT_ERROR_STATE;
for (std::uint32_t index = 0; index < capacity; ++index) {
if (waitingThreads[index].size != sizeof(UltWaitingThreadInfo)) return ULT_ERROR_INVALID;
}
if (capacity != 0 && state->_waiters != 0) NotImplemented_nid_no_patch(__func__);
if (state->_lockCount != 0 && (state->_attribute != 0 || state->_lockCount > 1 || state->_ownerIdentity.type != 1)) NotImplemented_nid_no_patch(__func__);
std::memset(info, 0, sizeof(*info));
info->size = sizeof(UltMutexInfo);
info->object = object;
std::memcpy(info->name, state->_name, sizeof(info->name));
info->waitingQueuePool = state->_waitingPool;
info->owner = state->_ownerIdentity.thread;
info->ownerType = state->_ownerIdentity.type;
info->attributes = state->_attribute;
info->lockCount = state->_lockCount;
info->waitingThreads = state->_waiters;
const auto toCopy = std::min(capacity, info->waitingThreads);
for (std::uint32_t i = 0; i < toCopy; ++i) {
std::memset(&waitingThreads[i], 0, sizeof(UltWaitingThreadInfo));
waitingThreads[i].size = sizeof(UltWaitingThreadInfo);
}
return ULT_OK;
}
@@ -1111,6 +1122,7 @@ int APS5_VABI sceUltGetQueueInfo(void* object, UltQueueInfo* info, UltWaitingThr
auto& pool = *state->_pool;
std::lock_guard poolLock(pool._mutex);
if (!state->_alive) return ULT_ERROR_STATE;
if (capacity != 0 && (state->_waitingPushers != 0 || state->_waitingPoppers != 0)) NotImplemented_nid_no_patch(__func__);
std::memset(info, 0, sizeof(*info));
info->size = sizeof(UltQueueInfo);
info->object = object;
@@ -1121,12 +1133,6 @@ int APS5_VABI sceUltGetQueueInfo(void* object, UltQueueInfo* info, UltWaitingThr
info->queuedItems = state->_queuedItems;
info->waitingPushers = state->_waitingPushers;
info->waitingPoppers = state->_waitingPoppers;
const auto waitingCount = info->waitingPushers + info->waitingPoppers;
const auto toCopy = std::min(capacity, waitingCount);
for (std::uint32_t i = 0; i < toCopy; ++i) {
std::memset(&waitingThreads[i], 0, sizeof(UltWaitingThreadInfo));
waitingThreads[i].size = sizeof(UltWaitingThreadInfo);
}
return ULT_OK;
}
@@ -1142,6 +1148,7 @@ int APS5_VABI sceUltGetSemaphoreInfo(void* object, UltSemaphoreInfo* info, UltWa
}
std::lock_guard stateLock(state->_mutex);
if (!state->_alive) return ULT_ERROR_STATE;
if (capacity != 0 && state->_waiters != 0) NotImplemented_nid_no_patch(__func__);
std::memset(info, 0, sizeof(*info));
info->size = sizeof(UltSemaphoreInfo);
info->object = object;
@@ -1149,11 +1156,6 @@ int APS5_VABI sceUltGetSemaphoreInfo(void* object, UltSemaphoreInfo* info, UltWa
info->waitingQueuePool = state->_waitingPool;
info->resources = static_cast<std::uint32_t>(state->_resources);
info->waitingThreads = state->_waiters;
const auto toCopy = std::min(capacity, info->waitingThreads);
for (std::uint32_t i = 0; i < toCopy; ++i) {
std::memset(&waitingThreads[i], 0, sizeof(UltWaitingThreadInfo));
waitingThreads[i].size = sizeof(UltWaitingThreadInfo);
}
return ULT_OK;
}
+6
View File
@@ -265,10 +265,16 @@ static_assert(offsetof(UltUlthreadInfo, runtime) == 56);
static_assert(offsetof(UltUlthreadInfo, fiber) == 64);
static_assert(offsetof(UltUlthreadInfo, reserved) == 192);
struct UltThreadIdentity {
void* thread = nullptr;
std::uint32_t type = 0;
};
struct UltMutexState {
std::mutex _mutex;
std::condition_variable _available;
std::thread::id _owner{};
UltThreadIdentity _ownerIdentity;
std::uint32_t _lockCount = 0;
std::uint32_t _waiters = 0;
std::uint32_t _conditions = 0;
@@ -1,6 +1,7 @@
#include "prx/libSceUlt/include/Api.hpp"
#include "prx/libSceUlt/src/ReaderWriterLock.hpp"
#include "prx/libSceUlt/SceUltTypes.hpp"
#include "prx/libc/include/General.hpp"
#include <algorithm>
#include <condition_variable>
#include <cstdint>
@@ -18,6 +19,7 @@ struct UltReaderWriterLockState {
std::uint32_t _readers = 0;
bool _writing = false;
std::thread::id _writer{};
UltThreadIdentity _ownerIdentity;
std::uint32_t _waiters = 0;
bool _alive = true;
void* _waitingPool = nullptr;
@@ -170,6 +172,7 @@ int APS5_VABI sceUltReaderWriterLockLockWrite(void* rwlock) {
return ULT_ERROR_STATE;
}
if (const int waited = Wait(*state, lock, true); waited != ULT_OK) return waited;
state->_ownerIdentity = UltCurrentThreadIdentity_nid_no_patch();
state->_writing = true;
state->_writer = std::this_thread::get_id();
return ULT_OK;
@@ -185,6 +188,7 @@ int APS5_VABI sceUltReaderWriterLockTryLockWrite(void* rwlock) {
if (state->_writing || state->_readers != 0) {
return ULT_ERROR_BUSY;
}
state->_ownerIdentity = UltCurrentThreadIdentity_nid_no_patch();
state->_writing = true;
state->_writer = std::this_thread::get_id();
return ULT_OK;
@@ -199,6 +203,7 @@ int APS5_VABI sceUltReaderWriterLockUnlockWrite(void* rwlock) {
}
state->_writing = false;
state->_writer = {};
state->_ownerIdentity = {};
state->_released.notify_all();
return ULT_OK;
}
@@ -210,6 +215,11 @@ int APS5_VABI sceUltGetReaderWriterLockInfo(void* object, UltReaderWriterLockInf
if (const int found = FindLock(object, state); found != ULT_OK) return found;
std::lock_guard<std::mutex> lock(state->_mutex);
if (!state->_alive) return ULT_ERROR_STATE;
for (std::uint32_t index = 0; index < capacity; ++index) {
if (waitingThreads[index].size != sizeof(UltWaitingThreadInfo)) return ULT_ERROR_INVALID;
}
if (capacity != 0 && state->_waiters != 0) NotImplemented_nid_no_patch(__func__);
if (state->_writing && state->_ownerIdentity.type != 1) NotImplemented_nid_no_patch(__func__);
std::memset(info, 0, sizeof(*info));
info->size = sizeof(UltReaderWriterLockInfo);
info->object = object;
@@ -217,12 +227,9 @@ int APS5_VABI sceUltGetReaderWriterLockInfo(void* object, UltReaderWriterLockInf
info->waitingQueuePool = state->_waitingPool;
info->readers = state->_readers;
info->waitingThreads = state->_waiters;
info->lockType = state->_writing ? 1 : (state->_readers > 0 ? 2 : 0);
const auto toCopy = std::min(capacity, info->waitingThreads);
for (std::uint32_t i = 0; i < toCopy; ++i) {
std::memset(&waitingThreads[i], 0, sizeof(UltWaitingThreadInfo));
waitingThreads[i].size = sizeof(UltWaitingThreadInfo);
}
info->lockType = state->_writing ? 2 : (state->_readers > 0 ? 1 : 0);
info->owner = state->_ownerIdentity.thread;
info->ownerType = state->_ownerIdentity.type;
return ULT_OK;
}
@@ -1,6 +1,9 @@
#ifndef CORE_LIBS_PRX_LIBSCEULT_SRC_READERWRITERLOCK_HPP
#define CORE_LIBS_PRX_LIBSCEULT_SRC_READERWRITERLOCK_HPP
struct UltThreadIdentity;
UltThreadIdentity UltCurrentThreadIdentity_nid_no_patch();
bool UltWaitingPoolExists(void* pool);
bool UltReaderWriterLocksWaitOn(void* pool);
void UltFinalizeReaderWriterLocks();
+180 -11
View File
@@ -1,6 +1,6 @@
#include "prx/libSceUlt/include/Api.hpp"
#include "prx/libkernel/include/Api.hpp"
#include "SceTypes.hpp"
#include "SceUltTypes.hpp"
#include <atomic>
#include <chrono>
#include <cstdint>
@@ -271,6 +271,60 @@ void Ulthreads() {
CHECK(sceUltUlthreadRuntimeDestroy(&runtime) == Ok);
}
void* CurrentThreadId() {
std::uintptr_t id;
const auto self = scePthreadSelf();
std::memcpy(&id, self, sizeof(id));
return reinterpret_cast<void*>(id);
}
template<typename TInfo, typename TQuery>
void CheckUnsupportedInfo(TQuery query, void* object, std::uint32_t capacity = 4) {
TInfo info;
std::memset(&info, 0xEE, sizeof(info));
UltWaitingThreadInfo waiters[4];
std::memset(waiters, 0xAA, sizeof(waiters));
for (auto& waiter : waiters) waiter.size = sizeof(UltWaitingThreadInfo);
std::uint8_t savedWaiters[sizeof(waiters)];
std::memcpy(savedWaiters, waiters, sizeof(waiters));
bool threw = false;
try {
query(object, &info, waiters, capacity);
} catch (const std::runtime_error&) {
threw = true;
}
CHECK(threw);
const auto* bytes = reinterpret_cast<const std::uint8_t*>(&info);
for (std::size_t index = 0; index < sizeof(info); ++index) CHECK(bytes[index] == 0xEE);
CHECK(std::memcmp(waiters, savedWaiters, sizeof(waiters)) == 0);
}
template<typename TInfo, typename TQuery>
void CheckWaitingRecordSizes(TQuery query, void* object) {
TInfo info;
UltWaitingThreadInfo waiters[4];
for (std::size_t invalid = 0; invalid < 4; ++invalid) {
for (const std::uint64_t size : {std::uint64_t{0}, std::uint64_t{63}, std::uint64_t{65}, UINT64_MAX}) {
std::memset(&info, 0xEE, sizeof(info));
std::memset(waiters, 0xAA, sizeof(waiters));
for (auto& waiter : waiters) waiter.size = sizeof(UltWaitingThreadInfo);
waiters[invalid].size = size;
std::uint8_t savedWaiters[sizeof(waiters)];
std::memcpy(savedWaiters, waiters, sizeof(waiters));
CHECK(query(object, &info, waiters, 4) == Invalid);
const auto* bytes = reinterpret_cast<const std::uint8_t*>(&info);
for (std::size_t index = 0; index < sizeof(info); ++index) CHECK(bytes[index] == 0xEE);
CHECK(std::memcmp(waiters, savedWaiters, sizeof(waiters)) == 0);
}
}
CHECK(query(object, &info, waiters, 0) == Ok);
for (auto& waiter : waiters) waiter.size = sizeof(UltWaitingThreadInfo);
std::uint8_t savedWaiters[sizeof(waiters)];
std::memcpy(savedWaiters, waiters, sizeof(waiters));
CHECK(query(object, &info, waiters, 4) == Ok);
CHECK(std::memcmp(waiters, savedWaiters, sizeof(waiters)) == 0);
}
void GetInfoTests() {
alignas(8) std::uint8_t dummy[512]{};
alignas(8) std::uint8_t poolObj[256]{};
@@ -292,6 +346,7 @@ void GetInfoTests() {
UltUlthreadRuntimeInfo rtInfo{};
UltUlthreadInfo threadInfo{};
UltWaitingThreadInfo waitThreads[4]{};
for (auto& waiter : waitThreads) waiter.size = sizeof(UltWaitingThreadInfo);
CHECK(sceUltGetMutexInfo(nullptr, nullptr, nullptr, 0) == Null);
CHECK(sceUltGetMutexInfo(mutexObj, nullptr, nullptr, 0) == Null);
@@ -334,11 +389,32 @@ void GetInfoTests() {
CHECK(mutexInfo.waitingQueuePool == poolObj);
CHECK(mutexInfo.lockCount == 0);
CHECK(mutexInfo.waitingThreads == 0);
CHECK(mutexInfo.owner == nullptr && mutexInfo.ownerType == 0);
CheckWaitingRecordSizes<UltMutexInfo>(sceUltGetMutexInfo, mutexObj);
CHECK(sceUltMutexLock(mutexObj) == Ok);
CHECK(sceUltGetMutexInfo(mutexObj, &mutexInfo, nullptr, 0) == Ok);
CHECK(mutexInfo.lockCount == 1);
CHECK(mutexInfo.lockCount == 0);
const auto owner = CurrentThreadId();
CHECK(owner != nullptr && mutexInfo.owner == owner && mutexInfo.ownerType == 1);
std::thread([&] {
CHECK(CurrentThreadId() != owner);
UltMutexInfo held{};
CHECK(sceUltGetMutexInfo(mutexObj, &held, nullptr, 0) == Ok);
CHECK(held.owner == owner && held.ownerType == 1 && held.lockCount == 0);
}).join();
CHECK(sceUltMutexUnlock(mutexObj) == Ok);
CHECK(sceUltGetMutexInfo(mutexObj, &mutexInfo, nullptr, 0) == Ok);
CHECK(mutexInfo.owner == nullptr && mutexInfo.ownerType == 0 && mutexInfo.lockCount == 0);
Object recursiveMutex{};
UltMutexOptParam recursiveOptions{};
recursiveOptions.attribute = 1;
CHECK(sceUltMutexCreate(&recursiveMutex, "recursive-info", poolObj, &recursiveOptions, 0) == Ok);
CHECK(sceUltMutexLock(&recursiveMutex) == Ok);
CheckUnsupportedInfo<UltMutexInfo>(sceUltGetMutexInfo, &recursiveMutex, 0);
CHECK(sceUltMutexUnlock(&recursiveMutex) == Ok);
CHECK(sceUltMutexDestroy(&recursiveMutex) == Ok);
CHECK(sceUltConditionVariableCreate(condObj, "test-cnd", mutexObj, nullptr, 0) == Ok);
CHECK(sceUltGetConditionVariableInfo(condObj, &condInfo, waitThreads, 4) == Ok);
@@ -364,12 +440,32 @@ void GetInfoTests() {
CHECK(rwInfo.waitingQueuePool == poolObj);
CHECK(rwInfo.readers == 0);
CHECK(rwInfo.lockType == 0);
CHECK(rwInfo.owner == nullptr && rwInfo.ownerType == 0);
CheckWaitingRecordSizes<UltReaderWriterLockInfo>(sceUltGetReaderWriterLockInfo, rwObj);
CHECK(sceUltReaderWriterLockLockRead(rwObj) == Ok);
CHECK(sceUltGetReaderWriterLockInfo(rwObj, &rwInfo, nullptr, 0) == Ok);
CHECK(rwInfo.readers == 1);
CHECK(rwInfo.lockType == 2);
CHECK(rwInfo.lockType == 1);
CHECK(rwInfo.owner == nullptr && rwInfo.ownerType == 0);
CHECK(sceUltReaderWriterLockLockRead(rwObj) == Ok);
CHECK(sceUltGetReaderWriterLockInfo(rwObj, &rwInfo, nullptr, 0) == Ok);
CHECK(rwInfo.lockType == 1 && rwInfo.readers == 2 && rwInfo.owner == nullptr && rwInfo.ownerType == 0);
CHECK(sceUltReaderWriterLockUnlockRead(rwObj) == Ok);
CHECK(sceUltReaderWriterLockUnlockRead(rwObj) == Ok);
CHECK(sceUltGetReaderWriterLockInfo(rwObj, &rwInfo, nullptr, 0) == Ok);
CHECK(rwInfo.lockType == 0 && rwInfo.readers == 0 && rwInfo.owner == nullptr && rwInfo.ownerType == 0);
for (const auto writeLock : {sceUltReaderWriterLockLockWrite, sceUltReaderWriterLockTryLockWrite}) {
CHECK(writeLock(rwObj) == Ok);
std::thread([&] {
UltReaderWriterLockInfo held{};
CHECK(sceUltGetReaderWriterLockInfo(rwObj, &held, nullptr, 0) == Ok);
CHECK(held.lockType == 2 && held.readers == 0 && held.owner == owner && held.ownerType == 1);
}).join();
CHECK(sceUltReaderWriterLockUnlockWrite(rwObj) == Ok);
CHECK(sceUltGetReaderWriterLockInfo(rwObj, &rwInfo, nullptr, 0) == Ok);
CHECK(rwInfo.lockType == 0 && rwInfo.owner == nullptr && rwInfo.ownerType == 0);
}
CHECK(sceUltQueueDataResourcePoolCreate(dataPoolObj, "test-datapool", 8, 64, 2, poolObj, nullptr, nullptr, 0) == Ok);
CHECK(sceUltGetQueueDataResourcePoolInfo(dataPoolObj, &dataPoolInfo) == Ok);
@@ -465,14 +561,11 @@ void QueueWaiters() {
WaitForQueueWaiters(queueObj, &info, 0, 1);
CHECK(info.queuedItems == 0);
std::memset(waiters, 0xAA, sizeof(waiters));
CHECK(sceUltGetQueueInfo(queueObj, &info, waiters, 4) == Ok);
CHECK(info.waitingPushers == 0);
CHECK(info.waitingPoppers == 1);
CHECK(waiters[0].size == sizeof(UltWaitingThreadInfo));
for (std::size_t i = 1; i < sizeof(waiters) / sizeof(waiters[0]); ++i) {
const auto* bytes = reinterpret_cast<const std::uint8_t*>(&waiters[i]);
for (std::size_t j = 0; j < sizeof(UltWaitingThreadInfo); ++j) CHECK(bytes[j] == 0xAA);
}
CheckUnsupportedInfo<UltQueueInfo>(sceUltGetQueueInfo, queueObj, 1);
CheckUnsupportedInfo<UltQueueInfo>(sceUltGetQueueInfo, queueObj);
CHECK(sceUltGetQueueInfo(queueObj, &info, waiters, 0) == Ok);
const auto* waiterBytes = reinterpret_cast<const std::uint8_t*>(waiters);
for (std::size_t index = 0; index < sizeof(waiters); ++index) CHECK(waiterBytes[index] == 0xAA);
CHECK(sceUltQueueDestroy(queueObj) == State);
CHECK(sceUltQueuePush(queueObj, item) == Ok);
popper.join();
@@ -487,6 +580,8 @@ void QueueWaiters() {
std::thread pusher([&] { pushResult.store(sceUltQueuePush(queueObj, item)); });
WaitForQueueWaiters(queueObj, &info, 1, 0);
CHECK(info.queuedItems == 2);
CheckUnsupportedInfo<UltQueueInfo>(sceUltGetQueueInfo, queueObj, 1);
CheckUnsupportedInfo<UltQueueInfo>(sceUltGetQueueInfo, queueObj);
CHECK(sceUltGetQueueInfo(queueObj, &info, waiters, 0) == Ok);
CHECK(info.waitingPushers == 1);
CHECK(info.waitingPoppers == 0);
@@ -503,6 +598,79 @@ void QueueWaiters() {
CHECK(sceUltWaitingQueueResourcePoolDestroy(poolObj) == Ok);
}
template<typename TInfo, typename TQuery>
void WaitForInfoWaiters(TQuery query, void* object) {
for (int attempt = 0; attempt < 4000; ++attempt) {
TInfo info{};
CHECK(query(object, &info, nullptr, 0) == Ok);
if (info.waitingThreads == 1) return;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
CHECK(false);
}
void InfoWaiters() {
Object pool{};
Object mutex{};
Object condition{};
Object semaphore{};
Object rwlock{};
CHECK(sceUltWaitingQueueResourcePoolCreate(&pool, "info-pool", 4, 4, nullptr, nullptr, 0) == Ok);
CHECK(sceUltMutexCreate(&mutex, "info-mutex", &pool, nullptr, 0) == Ok);
CHECK(sceUltConditionVariableCreate(&condition, "info-condition", &mutex, nullptr, 0) == Ok);
CHECK(sceUltSemaphoreCreate(&semaphore, "info-semaphore", 0, &pool, nullptr, 0) == Ok);
CHECK(sceUltReaderWriterLockCreate(&rwlock, "info-rwlock", &pool, nullptr, 0) == Ok);
CHECK(sceUltMutexLock(&mutex) == Ok);
std::thread mutexWaiter([&] {
CHECK(sceUltMutexLock(&mutex) == Ok);
CHECK(sceUltMutexUnlock(&mutex) == Ok);
});
WaitForInfoWaiters<UltMutexInfo>(sceUltGetMutexInfo, &mutex);
CheckUnsupportedInfo<UltMutexInfo>(sceUltGetMutexInfo, &mutex, 1);
CheckUnsupportedInfo<UltMutexInfo>(sceUltGetMutexInfo, &mutex);
CHECK(sceUltMutexUnlock(&mutex) == Ok);
mutexWaiter.join();
std::thread conditionWaiter([&] {
CHECK(sceUltMutexLock(&mutex) == Ok);
CHECK(sceUltConditionVariableWait(&condition) == Ok);
UltMutexInfo info{};
CHECK(sceUltGetMutexInfo(&mutex, &info, nullptr, 0) == Ok);
CHECK(info.owner == CurrentThreadId() && info.ownerType == 1);
CHECK(sceUltMutexUnlock(&mutex) == Ok);
});
WaitForInfoWaiters<UltConditionVariableInfo>(sceUltGetConditionVariableInfo, &condition);
CheckUnsupportedInfo<UltConditionVariableInfo>(sceUltGetConditionVariableInfo, &condition, 1);
CheckUnsupportedInfo<UltConditionVariableInfo>(sceUltGetConditionVariableInfo, &condition);
CHECK(sceUltConditionVariableSignal(&condition) == Ok);
conditionWaiter.join();
std::thread semaphoreWaiter([&] { CHECK(sceUltSemaphoreAcquire(&semaphore, 1) == Ok); });
WaitForInfoWaiters<UltSemaphoreInfo>(sceUltGetSemaphoreInfo, &semaphore);
CheckUnsupportedInfo<UltSemaphoreInfo>(sceUltGetSemaphoreInfo, &semaphore, 1);
CheckUnsupportedInfo<UltSemaphoreInfo>(sceUltGetSemaphoreInfo, &semaphore);
CHECK(sceUltSemaphoreRelease(&semaphore, 1) == Ok);
semaphoreWaiter.join();
CHECK(sceUltReaderWriterLockLockWrite(&rwlock) == Ok);
std::thread reader([&] {
CHECK(sceUltReaderWriterLockLockRead(&rwlock) == Ok);
CHECK(sceUltReaderWriterLockUnlockRead(&rwlock) == Ok);
});
WaitForInfoWaiters<UltReaderWriterLockInfo>(sceUltGetReaderWriterLockInfo, &rwlock);
CheckUnsupportedInfo<UltReaderWriterLockInfo>(sceUltGetReaderWriterLockInfo, &rwlock, 1);
CheckUnsupportedInfo<UltReaderWriterLockInfo>(sceUltGetReaderWriterLockInfo, &rwlock);
CHECK(sceUltReaderWriterLockUnlockWrite(&rwlock) == Ok);
reader.join();
CHECK(sceUltReaderWriterLockDestroy(&rwlock) == Ok);
CHECK(sceUltSemaphoreDestroy(&semaphore) == Ok);
CHECK(sceUltConditionVariableDestroy(&condition) == Ok);
CHECK(sceUltMutexDestroy(&mutex) == Ok);
CHECK(sceUltWaitingQueueResourcePoolDestroy(&pool) == Ok);
}
}
int main(int argc, char** argv) {
@@ -518,6 +686,7 @@ int main(int argc, char** argv) {
else if (std::strcmp(argv[1], "ulthreads") == 0) Ulthreads();
else if (std::strcmp(argv[1], "get-info") == 0) GetInfoTests();
else if (std::strcmp(argv[1], "queue-waiters") == 0) QueueWaiters();
else if (std::strcmp(argv[1], "info-waiters") == 0) InfoWaiters();
else return 2;
CHECK(sceUltFinalize() == Ok);
std::puts("ULT sync tests passed");
+2 -1
View File
@@ -7,7 +7,8 @@
- [libSceUlt ulthreads](../../../core/libs/prx/libSceUlt/Export.cpp) - Runtime option defaults still omit the native header, stack size, worker affinity and priority fields; affinity depends on unresolved process configuration queries. Concurrent join ownership is not modeled: the native runtime distinguishes an ordinary running thread from one with another joiner. Host threads do not use the guest context or worker scheduler.
- [libSceUlt queues](../../../core/libs/prx/libSceUlt/Export.cpp) - Queue-data pool destruction and shared slot limits still require comparison with the native node allocator.
- [`sceUltConditionVariableCreate` and `sceUltConditionVariableOptParamInitialize`](../../../core/libs/prx/libSceUlt/Export.cpp) (libSceUlt) are absent from the console export list, where only the underscored variants `_sceUltConditionVariableCreate` and `_sceUltConditionVariableOptParamInitialize` appear; their non-underscored signatures are assumed. Condition variable option parameter initialization only zeroes 128 bytes, and its layout without the common header remains unverified on hardware.
- [Status query exports](../../../core/libs/prx/libSceUlt/Export.cpp) (`sceUltGetConditionVariableInfo`, `sceUltGetMutexInfo`, `sceUltGetQueueDataResourcePoolInfo`, `sceUltGetQueueInfo`, `sceUltGetReaderWriterLockInfo`, `sceUltGetSemaphoreInfo`, `sceUltGetUlthreadInfo`, `sceUltGetUlthreadRuntimeInfo` and `sceUltGetWaitingQueueResourcePoolInfo`) populate recovered structure layouts and return `ULT_ERROR_NULL` or `ULT_ERROR_STATE` on absent objects. `sceUltGetQueueInfo` counts blocked pushers and poppers in their separate fields, but the waiter records it copies only carry their size: host waiters are not enumerated, so no thread identity, wait type or wait value is reported. `sceUltGetUlthreadRuntimeInfo` throws when the caller supplies a non-empty worker thread array, because this runtime starts no worker threads and has no handles to return. Waiting thread arrays and hardware priority accounting remain unverified on PS5 hardware.
- [Status query exports](../../../core/libs/prx/libSceUlt/Export.cpp) lack waiter identities, types and wait values. All five synchronization queries throw before writing output when capacity is nonzero and waiters exist; capacity-zero queries still report counts. `sceUltGetUlthreadRuntimeInfo` throws for a non-empty worker array because no worker threads are started. Recovered layouts, waiting records, worker scheduling and priority accounting lack full console validation.
- [Mutex and reader-writer status queries](../../../core/libs/prx/libSceUlt/src/ReaderWriterLock.cpp) throw for held recursive mutexes and ulthread-owned mutexes or write locks: recursive counts and ulthread owner encoding remain unverified. [Firmware 13.42 observations](https://github.com/boykopovar/AnyPS5/pull/9975#issuecomment-6417685562), from native x86-64 module runs with stubbed imports rather than PS5 hardware, supply `lockType` 1/2/0 for read/write/free, pthread `owner` from the thread block's first 8 bytes, `ownerType` 1 and default mutex `lockCount` 0 when held once. Both queries reject any supplied waiting record whose `size` is not 64 with `ULT_ERROR_INVALID` before writing output, as observed without waiters. Validation precedence and record-size checks for the other queries remain unverified.
- [Ult factory and creation exports](../../../core/libs/prx/libSceUlt/Export.cpp) without a leading underscore are absent from the supplied module; their signatures remain unresolved. Diagnostic signatures and synchronization argument lists are recovered.
- [sceUltMutexTryLock, the reader-writer lock and sceUltUlthreadGetSelf, sceUltUlthreadYield and sceUltUlthreadExit](../../../core/libs/prx/libSceUlt/src/ReaderWriterLock.cpp) (libSceUlt) - the signatures follow the PS Vita libult in [RPCS3](https://github.com/RPCS3/rpcs3/blob/13aa88c3c4d66ea8799e987f7d762d90943ab17b/rpcs3/Emu/PSP2/Modules/sceUlt.cpp) (a 256-byte lock and 128-byte option structures), not a PS5 binary. The rules are assumed: readers may take the lock while a writer waits, a thread that holds the write lock gets `ULT_ERROR_STATE` for any further lock instead of waiting on itself, a reader asking for the write lock waits on its own read lock, and the try forms return `ULT_ERROR_BUSY`. Outside an ulthread, `sceUltUlthreadGetSelf`, `sceUltUlthreadYield` and `sceUltUlthreadExit` return `ULT_ERROR_STATE`; inside one they act on the guest thread it runs on, since ulthreads are not scheduled cooperatively here