Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,16 +158,18 @@ int UmtxDeadline(const void* sizeArgument, const void* timeArgument, std::option
if (timeArgument == nullptr) {
return 0;
}
if (sizeArgument == nullptr) {
const auto usecs = *static_cast<const std::uint32_t*>(timeArgument);
deadlineNanos = TimedWait::DeadlineNanos(usecs);
return 0;
}
UmtxTime time{{0, 0}, 0, GUEST_CLOCK_REALTIME};
if (reinterpret_cast<std::uintptr_t>(sizeArgument) <= sizeof(KernelTimespec)) {
time.timeout = *static_cast<const KernelTimespec*>(timeArgument);
} else {
time = *static_cast<const UmtxTime*>(timeArgument);
}
if (time.timeout.tv_sec < 0 || time.timeout.tv_nsec < 0 || time.timeout.tv_nsec >= NANOS_PER_SECOND) {
if (sizeArgument == nullptr) {
NotImplemented_nid_no_patch("_umtx_op timeout with size argument 0 that is not a valid timespec");
}
return GUEST_EINVAL;
}

Expand Down
33 changes: 17 additions & 16 deletions core/libs/tests/GuestUmtxOp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,19 +123,20 @@ static void TimesOutWhileTheValueMatches() {
static void RejectsAnInvalidTimeout() {
std::uint32_t word = 1;
KernelTimespec negative{-1, 0};
Require(FailsWith(Umtx(&word, UMTX_OP_WAIT_UINT, 1, SizeOf(sizeof(negative)), &negative), GUEST_EINVAL));
KernelTimespec tooManyNanos{0, 1000000000};
Require(FailsWith(Umtx(&word, UMTX_OP_WAIT_UINT, 1, SizeOf(sizeof(tooManyNanos)), &tooManyNanos), GUEST_EINVAL));
for (const int operation : {UMTX_OP_WAIT, UMTX_OP_WAIT_UINT}) {
bool threw = false;
try { Umtx(&word, operation, 1, nullptr, &tooManyNanos); }
catch (const std::runtime_error&) { threw = true; }
Require(threw);
threw = false;
try { Umtx(&word, operation, 1, nullptr, &negative); }
catch (const std::runtime_error&) { threw = true; }
Require(threw);
Require(FailsWith(Umtx(&word, operation, 1, SizeOf(sizeof(negative)), &negative), GUEST_EINVAL));
Require(FailsWith(Umtx(&word, operation, 1, SizeOf(sizeof(tooManyNanos)), &tooManyNanos), GUEST_EINVAL));
}
struct {
std::uint32_t usecs = 20000;
std::uint32_t zero32 = 0;
std::int64_t zero64 = 0;
} zeroNeighbours;
auto start = std::chrono::steady_clock::now();
Require(FailsWith(Umtx(&word, UMTX_OP_WAIT_UINT, 1, nullptr, &zeroNeighbours), GUEST_ETIMEDOUT));
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15));
Require(std::chrono::steady_clock::now() - start < std::chrono::seconds(1));
UmtxTime negativeNanos{{0, -1}, 0, CLOCK_MONOTONIC_ID};
Require(FailsWith(Umtx(&word, UMTX_OP_WAIT_UINT, 1, SizeOf(sizeof(negativeNanos)), &negativeNanos), GUEST_EINVAL));
}
Expand All @@ -146,7 +147,7 @@ static void WakeReleasesAWaiterWithoutAValueChange() {
int result = -2;
std::thread waiter([&] {
KernelTimespec failsafe = FailsafeTimeout();
result = Umtx(&value, UMTX_OP_WAIT, 1, nullptr, &failsafe);
result = Umtx(&value, UMTX_OP_WAIT, 1, SizeOf(sizeof(failsafe)), &failsafe);
++finished;
});
while (finished.load() == 0) {
Expand All @@ -171,14 +172,14 @@ static void WakeHonoursTheCountAndTheAddress() {
waiters.emplace_back([&, i] {
KernelTimespec failsafe = FailsafeTimeout();
++started;
results[i] = Umtx(&word, UMTX_OP_WAIT_UINT_PRIVATE, 1, nullptr, &failsafe);
results[i] = Umtx(&word, UMTX_OP_WAIT_UINT_PRIVATE, 1, SizeOf(sizeof(failsafe)), &failsafe);
++finished;
});
}
std::thread otherWaiter([&] {
KernelTimespec failsafe = FailsafeTimeout();
++started;
otherResult = Umtx(&other, UMTX_OP_WAIT_UINT, 1, nullptr, &failsafe);
otherResult = Umtx(&other, UMTX_OP_WAIT_UINT, 1, SizeOf(sizeof(failsafe)), &failsafe);
++otherFinished;
});
AwaitCount(started, 5);
Expand Down Expand Up @@ -227,20 +228,20 @@ static void NwakeWakesEveryWaiterOnEachListedAddress() {
waiters.emplace_back([&, i] {
KernelTimespec failsafe = FailsafeTimeout();
++started;
results[i] = Umtx(&first, UMTX_OP_WAIT_UINT_PRIVATE, 1, nullptr, &failsafe);
results[i] = Umtx(&first, UMTX_OP_WAIT_UINT_PRIVATE, 1, SizeOf(sizeof(failsafe)), &failsafe);
++finished;
});
}
waiters.emplace_back([&] {
KernelTimespec failsafe = FailsafeTimeout();
++started;
results[3] = Umtx(&second, UMTX_OP_WAIT, 1, nullptr, &failsafe);
results[3] = Umtx(&second, UMTX_OP_WAIT, 1, SizeOf(sizeof(failsafe)), &failsafe);
++finished;
});
std::thread unlistedWaiter([&] {
KernelTimespec failsafe = FailsafeTimeout();
++started;
unlistedResult = Umtx(&unlisted, UMTX_OP_WAIT_UINT, 1, nullptr, &failsafe);
unlistedResult = Umtx(&unlisted, UMTX_OP_WAIT_UINT, 1, SizeOf(sizeof(failsafe)), &failsafe);
++unlistedFinished;
});
AwaitCount(started, 5);
Expand Down
2 changes: 1 addition & 1 deletion docs/dev/debt/libkernel.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
- [sceKernelAvailableFlexibleMemorySize](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - the segments of the title's executable and of its `.guest.prx` modules count against the configured flexible-memory budget (448 MiB by default) while loaded, for this size and for flexible maps. A title may declare a larger budget through `kernel.flexibleMemorySize` in `param.json`; this does not reduce the 13.5 GiB direct-memory size. Rejection of zero or a value not aligned to 16 KiB has not been measured on a console. This segment-accounting model follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/module.cpp#L165), which maps every segment of a module outside its system module directory as flexible memory on the PS4. [Counted](../../../core/libs/prx/libkernel/Module/src/ModuleInfo.cpp) are the segments the title's ELF gives permissions, each rounded to 16 KiB pages; not counted are segments without permissions, the relinker's own blocks (on Linux both are the read/write/execute `PT_LOAD`s, plus the header block at file offset 0; on Windows everything outside the `.elf` sections) and the host system libraries. The Linux image carries no marker for the relinker's blocks, so they are told apart by those flags and that offset only: a title segment that is writable and executable, or starts at file offset 0, is not counted. A module whose segments exceed what is left still loads, and the available size then reads 0; shadPS4 fails the segment map instead ([memory.cpp](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/memory.cpp#L519-L524) returns `EINVAL`, [module.cpp](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/module.cpp#L168-L169) asserts). Which segments the PS5 charges, its rounding, whether system modules use the title's budget and when an unload releases the charge are not measured. PPSA15508 subtracts the reported size and a 10 MiB module allowance from its own 448 MiB budget at start-up and stops when the result exceeds its 146 MiB code and data limit ([#2883](https://github.com/boykopovar/AnyPS5/issues/2883))
- [Memory protection bits 0x100 and 0x200](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) - taken as audio coprocessor (ACP) read and write, as in the [KytyPS5](https://github.com/KytyPS5/KytyPS5) and [etaHEN](https://github.com/etaHEN/etaHEN) headers; the coprocessor work runs on the host CPU, so they grant host read and read/write access like the GPU bits
- [Foreign guest module image protection on Windows](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) - `sceKernelMprotect` on Windows allows protection mutations on mapped PE images containing a `.elf0` section (as relinked guest modules produce, such as NVIDIA Real-time Denoisers `libNRD.prx` bundled in Dying Light 2: Stay Human PPSA02262), registering their sections into `GuestAllocations`. Host libraries and system DLLs (such as `libkernel.prx` and `kernel32.dll`) without a `.elf0` section continue to be rejected
- [_umtx_op](../../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - `UMTX_OP_WAIT`, `UMTX_OP_WAKE`, `UMTX_OP_WAIT_UINT`, `UMTX_OP_WAIT_UINT_PRIVATE`, `UMTX_OP_WAKE_PRIVATE` and `UMTX_OP_NWAKE_PRIVATE` follow FreeBSD 11's [kern_umtx.c](https://github.com/freebsd/freebsd-src/blob/stable/11/sys/kern/kern_umtx.c): a timeout whose size argument is at most 16 is a relative `timespec`, a larger one an `_umtx_time`, and a wake of 0 or fewer wakes one waiter. `UMTX_OP_NWAKE_PRIVATE` reads the value as an `int` count of pointers at the object and wakes every waiter on each one; a count of 0 or fewer reads nothing, and a null list fails with `EFAULT`, where FreeBSD's `copyin` would also fail for any other unreadable list. Waiters share the queue of `sceKernelSyncOnAddressWait`. Operation 0 fails with `EOPNOTSUPP` and operations above 26 with `EINVAL`, as in FreeBSD 11; `UMTX_OP_WAIT_UINT` with a value that does not fit in 32 bits returns 0 at once, as FreeBSD's zero-extended compare does. Operation 1 throws, since PPSA12544's call_once uses it in a way FreeBSD rejects; the other operations throw, and so does a misaligned object. PPSA12544's `libc.prx` passes `UMTX_OP_WAIT` and `UMTX_OP_WAIT_UINT` a pointer to a 4-byte microsecond timeout with size argument 0, which FreeBSD reads as a `timespec`, and waits for a running call_once with operation 1; its `PS5Util.prx` passes a `timespec`. Whether the console reads a microsecond timeout there, and what operation 1 does on it, is unverified. A libc call cannot be told apart from a `timespec` call by the size argument alone, so a timeout with size argument 0 is read as a `timespec` and only throws when it is not a valid one (negative seconds, nanoseconds outside [0, 1e9)), which is where FreeBSD would return `EINVAL`; a microsecond value that happens to read as a valid `timespec` is waited on as one
- [_umtx_op](../../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - `UMTX_OP_WAIT`, `UMTX_OP_WAKE`, `UMTX_OP_WAIT_UINT`, `UMTX_OP_WAIT_UINT_PRIVATE`, `UMTX_OP_WAKE_PRIVATE` and `UMTX_OP_NWAKE_PRIVATE` follow FreeBSD 11's [kern_umtx.c](https://github.com/freebsd/freebsd-src/blob/stable/11/sys/kern/kern_umtx.c): a timeout whose size argument is at most 16 is a relative `timespec`, a larger one an `_umtx_time`, and a wake of 0 or fewer wakes one waiter. `UMTX_OP_NWAKE_PRIVATE` reads the value as an `int` count of pointers at the object and wakes every waiter on each one; a count of 0 or fewer reads nothing, and a null list fails with `EFAULT`, where FreeBSD's `copyin` would also fail for any other unreadable list. Waiters share the queue of `sceKernelSyncOnAddressWait`. Operation 0 fails with `EOPNOTSUPP` and operations above 26 with `EINVAL`, as in FreeBSD 11; `UMTX_OP_WAIT_UINT` with a value that does not fit in 32 bits returns 0 at once, as FreeBSD's zero-extended compare does. Operation 1 throws, since PPSA12544's call_once uses it in a way FreeBSD rejects; the other operations throw, and so does a misaligned object. PPSA12544's `libc.prx` and Unity's `PS5Util.prx` (PPSA18194) pass `UMTX_OP_WAIT` and `UMTX_OP_WAIT_UINT` a pointer to a 4-byte microsecond timeout with size argument 0, which FreeBSD reads as a `timespec`, and wait for a running call_once with operation 1. Whether the console reads a microsecond timeout there in all cases, and what operation 1 does on it, is unverified. A call with size argument 0 is read unconditionally as a 4-byte microsecond timeout, matching callers like `libc.prx` and `PS5Util.prx`'s `zlqfTyrQSPk` wrapper which pass size 0 with a 32-bit microsecond count
- [sceKernelSyncOnAddressWait](../../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - the only known caller passes a null timeout and a name string as the fourth argument; the timeout is assumed to point to microseconds like the other kernel waits, and the name is ignored
- [sceKernelSyncOnAddressWait8 / sceKernelSyncOnAddressWait16 / sceKernelSyncOnAddressWait32 / sceKernelSyncOnAddressWait64](../../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - libc's `__std_atomic_wait_direct_8/16/32/64` pass a microsecond timeout or null and ignore the result; the by-value comparand, return values and the 2- and 8-byte alignment of Wait16 and Wait64 are assumed
- [scePthreadRwlockDestroy, pthread_rwlock_destroy](../../../core/libs/prx/libkernel/Pthread/src/Rwlock.cpp) (libkernel) - measured on a PS5 (firmware 13.60): destroying a rwlock returns 0 whether it is free or held for reading or writing, and leaves 1 in the handle; every call on that handle then returns `EINVAL` until it is initialized again, and a zero handle is initialized on first use by the `scePthreadRwlock` calls as by the POSIX ones. A rwlock destroyed while held is not freed, so its memory leaks. Destroying a rwlock while another thread is acquiring it is not guarded
Expand Down
Loading