fix(libc): save the random state on a short initstate and take any setstate rear as measured on a PS5

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
orezraeyandClaude Opus 5.5 committed 2026-10-25 18:47:19 -03:00
1 parent 9c6d153dec
commit 61ae4c52df
3 files changed
+17 -7

No files matched your search

+2 -3
View File
@@ -194,11 +194,11 @@ void APS5_VABI srandomdev_nid_postfix() {
}
char* APS5_VABI initstate_nid_postfix(std::uint64_t seed, char* state, std::int64_t size) {
std::lock_guard lock(g_randomLock);
SaveTypeWordLocked();
if (size < MinimumStateBytes[0]) return nullptr;
if (state == nullptr) throw std::invalid_argument("initstate: null state");
std::lock_guard lock(g_randomLock);
char* previous = g_random.table;
SaveTypeWordLocked();
int type = MaxTypes - 1;
while (size < MinimumStateBytes[static_cast<std::size_t>(type)]) --type;
g_random = {state, type, Degrees[static_cast<std::size_t>(type)], Separations[static_cast<std::size_t>(type)], 0, 0};
@@ -214,7 +214,6 @@ char* APS5_VABI setstate_nid_postfix(char* state) {
const int type = static_cast<int>(word % MaxTypes);
const std::uint32_t rear = word / MaxTypes;
const int degree = Degrees[static_cast<std::size_t>(type)];
if (type != LinearCongruentialType && rear >= static_cast<std::uint32_t>(degree)) return nullptr;
char* previous = g_random.table;
SaveTypeWordLocked();
const int separation = Separations[static_cast<std::size_t>(type)];
+14 -3
View File
@@ -176,11 +176,22 @@ int main() {
initstate_nid_postfix(7, small.data(), static_cast<std::int64_t>(small.size()));
random_nid_postfix();
alignas(4) std::array<char, 32> garbled{};
alignas(4) std::array<char, 128> garbled{};
const std::uint32_t garbledWord = 5 * 7 + 1;
std::memcpy(garbled.data(), &garbledWord, sizeof(garbledWord));
Require(setstate_nid_postfix(garbled.data()) == nullptr, "setstate must reject a rear position past the degree");
Require(random_nid_postfix() == seed7Type1[1], "a rejected setstate must keep the current state");
Require(setstate_nid_postfix(garbled.data()) == small.data(), "setstate must take a rear position past the degree as the console does");
random_nid_postfix();
Require(setstate_nid_postfix(small.data()) == garbled.data(), "setstate back from the garbled state");
Require(random_nid_postfix() == seed7Type1[1], "returning to the earlier state must resume its sequence");
initstate_nid_postfix(7, small.data(), static_cast<std::int64_t>(small.size()));
for (int draw = 0; draw < 3; ++draw) random_nid_postfix();
alignas(4) std::array<char, 8> refused{};
Require(initstate_nid_postfix(1, refused.data(), 4) == nullptr, "initstate below 8 bytes returns null");
Require(Word(small.data(), 0) == 3 * 5 + 1, "initstate below 8 bytes still saves the current state's rear position");
initstate_nid_postfix(7, small.data(), static_cast<std::int64_t>(small.size()));
random_nid_postfix();
random_nid_postfix();
*__error_nid_postfix() = 1234;
std::array<char, 8> tiny{};
+1 -1
View File
@@ -40,7 +40,7 @@
- [Dinkumware mutexes and conditions](../../../core/libs/prx/libc/src/Threads.cpp) (libc) use pointer-to-handle arguments, matching the console aerolib signatures reported in [#3079](https://github.com/boykopovar/AnyPS5/pull/3079). The title's `_Mtx_lock` maps `0` to `_Thrd_success` (0), `0x8002000b` to `_Thrd_busy` (3), and other failures to `_Thrd_error` (4); its internal mutex init recognizes recursive flag `0x100`, while every other type uses default attributes. The observed relock failure for a plain mutex suggests those defaults are error-checking, but that has not been confirmed on hardware. The title's `_Mtx_unlock` and `_Cnd_wait` return success regardless of their internal result. The host implementation treats invalid ownership preconditions as no-ops, and throws for invalid handles, host wait errors and destroying a locked mutex as debug guards; those cases are not confirmed against the console.
- [_Mtx_timedlock](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `hPzYSd5Nasc`) follows the console's `libc.prx` at `0x5d50` (PPSA02530): it takes an absolute xtime and returns `_Thrd_timedout` (2) on expiry (`0x8002003c`) or 5 on other failures. On non-Windows hosts the native mutex and condition are `std::timed_mutex` and `std::condition_variable_any`; Windows keeps the `CRITICAL_SECTION`/`CONDITION_VARIABLE` pair because `std::condition_variable_any` failed `guest_libc_cnd_timed_wait` on the Windows runner, and its `_Mtx_timedlock` polls `TryEnterCriticalSection` until the deadline. Windows timing is not measured on the console.
- [Floating-point compiler-rt builtins](../../../core/libs/prx/libc/src/FloatBuiltins.cpp) (libc) follow the `libc.prx` bundled with PPSA08427 (disassembly, and that binary run side by side on random inputs) where it differs from current compiler-rt: the soft-float `__add`, `__sub`, `__mul` and `__div` (`sf3`, `df3`) round to nearest, raise no exception flags and ignore MXCSR, here done with SSE under a default MXCSR that is restored afterwards; division flushes a quotient below the smallest normal to a signed zero; `__extendsfdf2` and `__truncdfsf2` copy NaN payloads unshifted; float-to-integer conversions do not saturate but shift the significand by a count masked to the shift width. `__mulsc3`, `__muldc3`, `__divsc3` and `__divdc3` fuse the same products as the console with `fma` and scale through the x87 like its `scalbn`, so their values match, but NaN payloads and exception flags can differ, as the host `fma` and `logb` are not the console's VEX FMA code and libm
- [random, srandom, initstate, setstate](../../../core/libs/prx/libc/src/Random.cpp) (libc) - not measured: the state `setstate` returns and its null return for a rear position past the degree, `errno` after `initstate` below 8 bytes, a null state (throws here), an `initstate` seed other than 1, concurrent callers (one lock serialises every call here) and a title's own libc. Measured in a payload process on a PS5 (firmware 12.70): a fresh process draws the `srandom(1)` sequence; `srandom` seeds an additive feedback generator through a `good_rand` that reads its argument as an int32, turns 0 into 123459876 and applies `x = 16807 * (x % 127773) - 2836 * (x / 127773)`, adding 0x7fffffff to a negative result, which reproduces seeds 0, 1, 2, 12345, 0xffffffff and 0x80000000; `initstate` returns null at 0 and 7 bytes and selects types 0 to 4 by size as measured at 8, 15, 31, 32, 63, 64, 127, 128, 129, 255, 256, 257 and 1000 bytes.
- [random, srandom, initstate, setstate](../../../core/libs/prx/libc/src/Random.cpp) (libc) - a null state throws where the system `libSceLibcInternal` of PS5 firmware 13.60 faults, `initstate` below 8 bytes skips that library's "not enough state" warning, and concurrent callers (one lock here) and a title's own libc are not measured. Otherwise it follows payload processes on PS5 firmware 12.70 and 13.60: a fresh process draws the `srandom(1)` sequence; `srandom` seeds through `good_rand` (int32 argument, 0 becomes 123459876, `16807 * (x % 127773) - 2836 * (x / 127773)` plus 0x7fffffff when negative); `initstate` picks types 0 to 4 by size, returns null below 8 bytes without touching `errno` but still saves the current rear position; both calls return the previous state; `setstate` takes a rear position past the degree unchecked
- [srandomdev](../../../core/libs/prx/libc/src/Random.cpp) (libSceLibcInternal, implemented in libc) is not verified against the console. The firmware 3.20 [export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibcInternal.c) names NID `a2MOZf++Wjg`; it follows FreeBSD 11 [random.c](https://github.com/freebsd/freebsd-src/blob/4ab2e064d7950be84256d671a7ae93f87cc6aa36/lib/libc/stdlib/random.c), filling every active generator word from host OS entropy instead of `KERN_ARND`, then resetting additive feedback positions without warming up. An entropy failure throws rather than leaving `sysctl`'s failure unchecked. Calls share the generator lock.
- [Integer compiler-rt builtins](../../../core/libs/prx/libc/src/IntegerBuiltins.cpp) (libc) - the console traps in `__divsi3`, `__modsi3` and the 64- and 128-bit divisions on a zero divisor (`div`) and in `__negv*`, `__absv*` and `__mulv*` on overflow (an abort helper, then `ud2`); these throw instead. As in the `libc.prx` bundled with PPSA08427 (disassembly), `__addv*` and `__subv*` wrap without an overflow check, and `__udivsi3`, `__umodsi3` and `__udivmodsi4` divide in software, giving a quotient of 0 and the dividend as remainder for a zero divisor; `__clzdi2` and `__ctzdi2` of 0 return 32 and `__clzti2` and `__ctzti2` of 0 return 64
- [qsort](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) follows FreeBSD 11 [qsort.c](https://github.com/freebsd/freebsd-src/blob/5ed7eb0d97ba4436218e810f61bd059acba984c2/lib/libc/stdlib/qsort.c): Bentley and McIlroy's quicksort, with insertion sort below 7 elements and after a partition pass that swaps nothing, so elements that compare equal end in FreeBSD's order and the comparator is called in the same sequence. That the console's libc uses this FreeBSD code is unverified