mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-31 12:35:51 +00:00
fix(libkernel): answer unknown AIO ids with ESRCH states as a PS5 does
Measured on firmware 13.60: an id that is not a live request, including one already deleted, makes poll, wait, cancel and delete return 0 and write 0x80020003 for it; delete makes an id unknown, and cancel leaves a finished request's state unchanged. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
2730b6abe5
commit
581a951db8
3 files changed
+63
-55
No files matched your search
@@ -61,6 +61,23 @@ void SetState(std::int32_t id, std::int32_t state) {
|
||||
g_states[id] = state;
|
||||
}
|
||||
|
||||
std::int32_t StateLocked(std::int32_t id) {
|
||||
return ValidId(id) && g_states[id] != 0 ? g_states[id] : SCE_KERNEL_ERROR_ESRCH;
|
||||
}
|
||||
|
||||
std::int32_t CancelLocked(std::int32_t id) {
|
||||
const std::int32_t state = StateLocked(id);
|
||||
if (state != AioProcessing) return state;
|
||||
g_states[id] = AioAborted;
|
||||
return AioAborted;
|
||||
}
|
||||
|
||||
std::int32_t DeleteLocked(std::int32_t id) {
|
||||
if (StateLocked(id) == SCE_KERNEL_ERROR_ESRCH) return SCE_KERNEL_ERROR_ESRCH;
|
||||
g_states[id] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::int64_t NativePread(std::int32_t fd, void* buf, std::size_t nbyte, std::int64_t offset) {
|
||||
const GuestArena::HostWrite destination(buf, nbyte);
|
||||
if (!destination.Open()) {
|
||||
@@ -151,9 +168,6 @@ int SubmitCommandsMultiple(KernelAioRwRequest* req, std::int32_t size, std::int3
|
||||
int ValidateIds(const std::int32_t* id, std::int32_t num, const void* out) {
|
||||
if (id == nullptr || out == nullptr) return SCE_KERNEL_ERROR_EFAULT;
|
||||
if (num <= 0 || num > AioIdNumMax) return SCE_KERNEL_ERROR_EINVAL;
|
||||
for (std::int32_t i = 0; i < num; ++i) {
|
||||
if (!ValidId(id[i])) return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -163,48 +177,31 @@ extern "C" {
|
||||
|
||||
int APS5_VABI sceKernelAioDeleteRequest(int32_t id, int32_t* ret) {
|
||||
if (ret == nullptr) return SCE_KERNEL_ERROR_EFAULT;
|
||||
if (!ValidId(id)) return SCE_KERNEL_ERROR_EINVAL;
|
||||
SetState(id, AioAborted);
|
||||
*ret = 0;
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
*ret = DeleteLocked(id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelAioDeleteRequests(int32_t* id, int32_t num, int32_t* ret) {
|
||||
const int error = ValidateIds(id, num, ret);
|
||||
if (error != 0) return error;
|
||||
for (int32_t i = 0; i < num; ++i) {
|
||||
SetState(id[i], AioAborted);
|
||||
ret[i] = 0;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
for (int32_t i = 0; i < num; ++i) ret[i] = DeleteLocked(id[i]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelAioCancelRequest(int32_t id, int32_t* state) {
|
||||
if (state == nullptr) return SCE_KERNEL_ERROR_EFAULT;
|
||||
if (id == 0) {
|
||||
*state = AioProcessing;
|
||||
return 0;
|
||||
}
|
||||
if (!ValidId(id)) return SCE_KERNEL_ERROR_EINVAL;
|
||||
SetState(id, AioAborted);
|
||||
*state = AioAborted;
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
*state = CancelLocked(id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelAioCancelRequests(int32_t* id, int32_t num, int32_t* state) {
|
||||
if (id == nullptr || state == nullptr) return SCE_KERNEL_ERROR_EFAULT;
|
||||
if (num <= 0 || num > AioIdNumMax) return SCE_KERNEL_ERROR_EINVAL;
|
||||
for (int32_t i = 0; i < num; ++i) {
|
||||
if (id[i] != 0 && !ValidId(id[i])) return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
for (int32_t i = 0; i < num; ++i) {
|
||||
if (id[i] == 0) {
|
||||
state[i] = AioProcessing;
|
||||
continue;
|
||||
}
|
||||
SetState(id[i], AioAborted);
|
||||
state[i] = AioAborted;
|
||||
}
|
||||
const int error = ValidateIds(id, num, state);
|
||||
if (error != 0) return error;
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
for (int32_t i = 0; i < num; ++i) state[i] = CancelLocked(id[i]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -254,9 +251,8 @@ int APS5_VABI sceKernelAioSubmitWriteCommandsMultiple(KernelAioRwRequest* req, i
|
||||
|
||||
int APS5_VABI sceKernelAioPollRequest(int32_t id, int32_t* state) {
|
||||
if (state == nullptr) return SCE_KERNEL_ERROR_EFAULT;
|
||||
if (!ValidId(id)) return SCE_KERNEL_ERROR_EINVAL;
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
*state = g_states[id];
|
||||
*state = StateLocked(id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -271,7 +267,7 @@ int APS5_VABI sceKernelAioWaitRequests(int32_t* id, int32_t num, int32_t* state,
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
for (int32_t i = 0; i < num; ++i) {
|
||||
state[i] = g_states[id[i]];
|
||||
state[i] = StateLocked(id[i]);
|
||||
if (state[i] == AioProcessing) allDone = false;
|
||||
if (state[i] == AioCompleted) anyCompleted = true;
|
||||
}
|
||||
@@ -287,13 +283,12 @@ int APS5_VABI sceKernelAioWaitRequests(int32_t* id, int32_t num, int32_t* state,
|
||||
|
||||
int APS5_VABI sceKernelAioWaitRequest(int32_t id, int32_t* state, uint32_t* usec) {
|
||||
if (state == nullptr) return SCE_KERNEL_ERROR_EFAULT;
|
||||
if (!ValidId(id)) return SCE_KERNEL_ERROR_EINVAL;
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
for (;;) {
|
||||
std::int32_t current;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
current = g_states[id];
|
||||
current = StateLocked(id);
|
||||
}
|
||||
if (current != AioProcessing) {
|
||||
*state = current;
|
||||
@@ -314,7 +309,7 @@ int APS5_VABI sceKernelAioPollRequests(int32_t* id, int32_t num, int32_t* state)
|
||||
const int error = ValidateIds(id, num, state);
|
||||
if (error != 0) return error;
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
for (int32_t i = 0; i < num; ++i) state[i] = g_states[id[i]];
|
||||
for (int32_t i = 0; i < num; ++i) state[i] = StateLocked(id[i]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -239,10 +239,14 @@ int main(int argc, char** argv) {
|
||||
Check(sceKernelAioSubmitReadCommands(nullptr, 1, 1, &id) == SCE_KERNEL_ERROR_EFAULT);
|
||||
Check(sceKernelAioSubmitReadCommands(requests, 0, 1, &id) == SCE_KERNEL_ERROR_EINVAL);
|
||||
Check(sceKernelAioSubmitReadCommands(requests, 1, 1, nullptr) == SCE_KERNEL_ERROR_EFAULT);
|
||||
Check(sceKernelAioWaitRequest(9999, &state, nullptr) == SCE_KERNEL_ERROR_EINVAL);
|
||||
state = 7;
|
||||
Check(sceKernelAioWaitRequest(9999, &state, nullptr) == 0 && state == SCE_KERNEL_ERROR_ESRCH);
|
||||
Check(sceKernelAioWaitRequest(id, nullptr, nullptr) == SCE_KERNEL_ERROR_EFAULT);
|
||||
Check(sceKernelAioPollRequest(id, nullptr) == SCE_KERNEL_ERROR_EFAULT);
|
||||
Check(sceKernelAioPollRequest(0, &polled) == SCE_KERNEL_ERROR_EINVAL);
|
||||
for (const std::int32_t unknown : {0, -1, 511, 512, 100000}) {
|
||||
polled = 7;
|
||||
Check(sceKernelAioPollRequest(unknown, &polled) == 0 && polled == SCE_KERNEL_ERROR_ESRCH);
|
||||
}
|
||||
Check(sceKernelAioDeleteRequest(id, nullptr) == SCE_KERNEL_ERROR_EFAULT);
|
||||
const int batchFd = sceKernelOpen(file.string().c_str(), SCE_KERNEL_O_RDWR, 0);
|
||||
Check(batchFd >= 0);
|
||||
@@ -317,24 +321,31 @@ int main(int argc, char** argv) {
|
||||
Check(readStates[0] == 3 && readStates[1] == 3);
|
||||
std::int32_t cancelled = 0;
|
||||
Check(sceKernelAioCancelRequest(readIds[0], &cancelled) == 0);
|
||||
Check(cancelled == 4);
|
||||
Check(cancelled == 3);
|
||||
Check(sceKernelAioPollRequest(readIds[0], &polled) == 0);
|
||||
Check(polled == 4);
|
||||
Check(polled == 3);
|
||||
Check(sceKernelAioCancelRequest(0, &cancelled) == 0);
|
||||
Check(cancelled == 2);
|
||||
Check(cancelled == SCE_KERNEL_ERROR_ESRCH);
|
||||
std::int32_t cancelIds[2] = {0, readIds[1]};
|
||||
std::int32_t cancelStates[2] = {0, 0};
|
||||
Check(sceKernelAioCancelRequests(cancelIds, 2, cancelStates) == 0);
|
||||
Check(cancelStates[0] == 2 && cancelStates[1] == 4);
|
||||
Check(cancelStates[0] == SCE_KERNEL_ERROR_ESRCH && cancelStates[1] == 3);
|
||||
Check(sceKernelAioPollRequest(readIds[1], &polled) == 0);
|
||||
Check(polled == 4);
|
||||
Check(polled == 3);
|
||||
Check(sceKernelAioPollRequests(readIds, 2, polledStates) == 0);
|
||||
Check(polledStates[0] == 4 && polledStates[1] == 4);
|
||||
Check(polledStates[0] == 3 && polledStates[1] == 3);
|
||||
std::int32_t deletedRets[2] = {-1, -1};
|
||||
Check(sceKernelAioDeleteRequests(writeIds, 2, deletedRets) == 0);
|
||||
Check(deletedRets[0] == 0 && deletedRets[1] == 0);
|
||||
Check(sceKernelAioPollRequest(writeIds[0], &polled) == 0);
|
||||
Check(polled == 4);
|
||||
Check(polled == SCE_KERNEL_ERROR_ESRCH);
|
||||
Check(sceKernelAioDeleteRequests(writeIds, 2, deletedRets) == 0);
|
||||
Check(deletedRets[0] == SCE_KERNEL_ERROR_ESRCH && deletedRets[1] == SCE_KERNEL_ERROR_ESRCH);
|
||||
std::int32_t deletedRet = 7;
|
||||
Check(sceKernelAioDeleteRequest(writeIds[0], &deletedRet) == 0 && deletedRet == SCE_KERNEL_ERROR_ESRCH);
|
||||
std::uint32_t unknownTimeout = 1000;
|
||||
Check(sceKernelAioWaitRequests(writeIds, 2, polledStates, 1, &unknownTimeout) == 0);
|
||||
Check(polledStates[0] == SCE_KERNEL_ERROR_ESRCH && polledStates[1] == SCE_KERNEL_ERROR_ESRCH);
|
||||
Check(sceKernelAioDeleteRequests(writeIds, 0, deletedRets) == SCE_KERNEL_ERROR_EINVAL);
|
||||
Check(sceKernelClose(batchFd) == 0);
|
||||
std::int32_t badIds[2] = {readIds[0], 9999};
|
||||
@@ -355,13 +366,15 @@ int main(int argc, char** argv) {
|
||||
Check(sceKernelAioWaitRequests(readIds, 2, nullptr, 1, nullptr) == SCE_KERNEL_ERROR_EFAULT);
|
||||
Check(sceKernelAioWaitRequests(nullptr, 2, readStates, 1, nullptr) == SCE_KERNEL_ERROR_EFAULT);
|
||||
Check(sceKernelAioWaitRequests(readIds, -1, readStates, 1, nullptr) == SCE_KERNEL_ERROR_EINVAL);
|
||||
Check(sceKernelAioWaitRequests(badIds, 2, untouched, 1, nullptr) == SCE_KERNEL_ERROR_EINVAL);
|
||||
Check(untouched[0] == 7 && untouched[1] == 7);
|
||||
Check(sceKernelAioWaitRequests(badIds, 2, untouched, 1, nullptr) == 0);
|
||||
Check(untouched[0] == 3 && untouched[1] == SCE_KERNEL_ERROR_ESRCH);
|
||||
untouched[0] = untouched[1] = 7;
|
||||
Check(sceKernelAioPollRequests(readIds, 2, nullptr) == SCE_KERNEL_ERROR_EFAULT);
|
||||
Check(sceKernelAioPollRequests(nullptr, 2, readStates) == SCE_KERNEL_ERROR_EFAULT);
|
||||
Check(sceKernelAioPollRequests(readIds, -1, readStates) == SCE_KERNEL_ERROR_EINVAL);
|
||||
Check(sceKernelAioPollRequests(badIds, 2, untouched) == SCE_KERNEL_ERROR_EINVAL);
|
||||
Check(untouched[0] == 7 && untouched[1] == 7);
|
||||
Check(sceKernelAioPollRequests(badIds, 2, untouched) == 0);
|
||||
Check(untouched[0] == 3 && untouched[1] == SCE_KERNEL_ERROR_ESRCH);
|
||||
untouched[0] = untouched[1] = 7;
|
||||
Check(sceKernelAioPollRequests(readIds, 0, untouched) == SCE_KERNEL_ERROR_EINVAL);
|
||||
Check(sceKernelAioWaitRequests(readIds, 0, untouched, 1, nullptr) == SCE_KERNEL_ERROR_EINVAL);
|
||||
Check(sceKernelAioCancelRequests(readIds, 0, untouched) == SCE_KERNEL_ERROR_EINVAL);
|
||||
@@ -381,13 +394,13 @@ int main(int argc, char** argv) {
|
||||
Check(sceKernelAioCancelRequests(manyIds.data(), 129, manyStates.data()) == SCE_KERNEL_ERROR_EINVAL);
|
||||
Check(manyStates[0] == 7);
|
||||
Check(sceKernelAioPollRequests(manyIds.data(), 128, manyStates.data()) == 0 && manyStates[127] == manyStates[0]);
|
||||
Check(sceKernelAioCancelRequest(9999, &cancelled) == SCE_KERNEL_ERROR_EINVAL);
|
||||
Check(sceKernelAioCancelRequest(9999, &cancelled) == 0 && cancelled == SCE_KERNEL_ERROR_ESRCH);
|
||||
Check(sceKernelAioCancelRequest(readIds[0], nullptr) == SCE_KERNEL_ERROR_EFAULT);
|
||||
Check(sceKernelAioCancelRequests(badIds, 2, untouched) == SCE_KERNEL_ERROR_EINVAL);
|
||||
Check(untouched[0] == 7 && untouched[1] == 7);
|
||||
Check(sceKernelAioCancelRequests(badIds, 2, untouched) == 0);
|
||||
Check(untouched[0] == 3 && untouched[1] == SCE_KERNEL_ERROR_ESRCH);
|
||||
Check(sceKernelAioCancelRequests(cancelIds, 2, nullptr) == SCE_KERNEL_ERROR_EFAULT);
|
||||
Check(sceKernelAioDeleteRequests(badIds, 2, untouched) == SCE_KERNEL_ERROR_EINVAL);
|
||||
Check(untouched[0] == 7 && untouched[1] == 7);
|
||||
Check(sceKernelAioDeleteRequests(badIds, 2, untouched) == 0);
|
||||
Check(untouched[0] == 0 && untouched[1] == SCE_KERNEL_ERROR_ESRCH);
|
||||
Check(sceKernelAioDeleteRequests(writeIds, 2, nullptr) == SCE_KERNEL_ERROR_EFAULT);
|
||||
const auto shared = root / "shared.bin";
|
||||
std::string sharedContents;
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
- [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. Cancellation follows the [POSIX condition wait contract](https://pubs.opengroup.org/onlinepubs/9799919799/functions/pthread_cond_clockwait.html): the original mutex and recursive depth are restored before cleanup, and a canceled waiter forwards a wake to the remaining waiters. The host condition state is retained across mutex reacquisition so the guest can destroy and free its condition after notification; cancellation ordering and signal replacement are not measured on a console
|
||||
- [sceKernelSetEventFlag](../../../core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp) (libkernel) - multi-waiter satisfaction follows fpPS4 in-order evaluation: registered waiters are evaluated in FIFO order and each satisfied waiter's clear mode is applied immediately to the pattern before evaluating subsequent waiters; this ordering is unverified on PS5 console hardware
|
||||
- [sceKernelOpenEventFlag](../../../core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp) (libkernel) - returns the handle of an undeleted event flag this process created with the same name, and `SCE_KERNEL_ERROR_ENOENT` otherwise. Flags owned by other processes, such as the shell's `SceShellCoreUtilAppFocus`, do not exist here, and which attribute makes a flag openable on the console is not modelled. The error code for a missing name and the choice between flags sharing a name are unverified.
|
||||
- [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, wait mode 2 returns once one request completed, poll copies every state without waiting). On a PS5 (firmware 13.60) an id that is not a live request (0, -1, 1, 511, 512, 100000) makes every poll, wait, cancel and delete call return 0 and write `0x80020003` as its state; here an id outside 1 to 511 returns `EINVAL` and cancel reports id 0 as processing. The console's ids are not the small numbers used here (it returned 33, 0x2021, 0x4021). Batch sizes, id counts, priorities and wait modes follow the console
|
||||
- [sceKernelAioSubmitReadCommandsMultiple / sceKernelAioSubmitWriteCommandsMultiple / sceKernelAioWaitRequests / sceKernelAioPollRequests / sceKernelAioCancelRequest / sceKernelAioCancelRequests / sceKernelAioDeleteRequests](../../../core/libs/prx/libkernel/Aio/src/Aio.cpp) (libkernel) - one id per request for the Multiple submits and wait mode 2 returning once one request completed follow shadPS4. Requests run synchronously on submit, so cancel never finds one processing. On a PS5 (firmware 13.60) a state read after a wait has reported it carries bit `0x10000` (`0x10003` for completed); that bit is not set here. The console's ids are not the small numbers used here (it returned 33, 0x2021, 0x4021)
|
||||
- [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. `scePthreadAttrGetschedpolicy` and `scePthreadAttrGetinheritsched` were not measured and follow [FreeBSD 11.4 libthr](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libthr/thread/thr_attr.c#L216-L262); a null output of `scePthreadAttrGetinheritsched` throws, since libthr writes through it unchecked
|
||||
- [scePthreadAttrGet](../../../core/libs/prx/libkernel/Pthread/src/Attr.cpp) and [pthread_attr_get_np](../../../core/libs/prx/libkernel/Pthread/Posix/Attr.cpp) (libkernel) - the returned scheduling policy is unmeasured on a console. Both report the recorded guest policy, following [FreeBSD 11.4 libthr](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libthr/thread/thr_attr.c#L134-L164)
|
||||
- [pthread_getprio](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) is unmeasured on the console. NID `aO+WS2xWuo4` is named in the firmware 3.20 [export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libkernel.c). Its return and errno contract follows FreeBSD 11 [thr_getprio.c](https://github.com/freebsd/freebsd-src/blob/671fb7df97631020c42ce55527dc49fffe0656b7/lib/libthr/thread/thr_getprio.c): return the recorded guest priority, or -1 with `ESRCH` for an unknown or finished thread. The live-thread registry lock protects the read from a concurrent join. This reports guest scheduler state, not the host scheduling priority.
|
||||
|
||||
Reference in new issue
Block a user