Files
Adrià 59776f2e6a Merge pull request #7841 from hypermoist/feat/libc-annex-k-remaining
feat(libc): implement the remaining C11 Annex K bounds-checking functions
2026-10-21 13:09:59 +02:00

83 KiB

libc technical debt

Unknown function info

  • strtonum (libc) is unverified on the console. Its signature, decimal parsing, inclusive bounds, error strings, error precedence and errno preservation follow FreeBSD 11.4 strtonum.c. Parsing uses the host strtoll with base 10.

  • sceLibcBacktraceGetBufferSize, sceLibcBacktraceSelf (libc) - signatures and frame record layout inferred from a title (next pointer, u32 module offset, module name at +0x10); the u32 at +0xC is unknown and written as 0. Module names are host file names without the .guest.prx suffix, so their case may differ from the PS5 module name. On Windows, unwinding uses _Unwind_Backtrace and stops at a frame without .ehmeta or .ehfram tables whose PE unwind data has an exception or termination handler, leaving the trace empty or cut short while still returning 0. Implemented only on Linux and Windows; other platforms throw

  • ApplicationGuestHeapApi (libc) - on Linux, HLE application allocation uses a loaded guest SDK's ten allocator exports when the title's replacement table has an allocator entry and the exports belong to one native module. With an empty table it keeps the default heap, since a bundled libc.prx may export the allocator NIDs while forwarding them to HLE. Its CRT owns application allocator initialization and finalization; HLE retains the module until process exit. Partial or mixed providers throw. This avoids reinitializing an allocator already used by SDK startup; console behavior and other host platforms remain unverified.

  • fdopen (libc) validates descriptor access modes as FreeBSD 11.4 fdopen.c does: a requested mode incompatible with a descriptor not opened for both reading and writing returns null with EINVAL, leaving the descriptor open. Windows reads the handle's granted access through the same host helper as fcntl(F_GETFL); a closed descriptor returns null with EBADF. Console behavior is unverified.

  • scriptingGetMem, scriptingFreeMem (libc) - runtime allocator hooks for Unity titles. The allocation signature (alignment, size), minimum alignment 16 and null result for a non-power-of-two alignment follow KytyPS5; the free hook's one-pointer signature follows Raeen's recorded contract. Both use libc's own guest heap, not the title's registered allocator. A title that ships its own libc.prx (Metal Gear Solid Master Collection, PPSA18194) has already initialized that allocator, and reaching it from here would run its initializer a second time. That title's IL2CPP runtime looks up only scriptingGetMem, by name from Il2cppUserAssemblies.prx, and asks it for 256 KiB blocks aligned to 4 KiB. Which console library exports the hooks is unknown, and there is no console measurement. Allocation failure and sizes that overflow return null.

  • _Mtx_trylock (libc, NID k6pGNMwJB08) - returns busy for a non-recursive mutex already owned by the caller, assumed to match _Mtx_lock's ownership handling; exact console behavior is unverified.

  • nearbyintf (libc, NID c+4r-T-tEIc) uses the host rounding mode, not the console's; console rounding mode state remains unverified.

  • __std_find_trivial_unsized_N (libc) - matches the libc.prx bundled with PPSA08427 (disassembly, and that binary run side by side on random inputs): when nothing matches between first and the end of its 32-byte-aligned block, the console rescans that whole block without masking and so can return a match up to 31 bytes before first

  • _Thrd_sleep (libc, NID jfRI3snge3o) follows the Dinkumware threads library shipped with Visual C++ 2013 (xtime.c) and C11 Annex K, without console verification. It reads a 16-byte xtime (64-bit seconds and nanoseconds) on the guest CLOCK_REALTIME, normalizes the nanoseconds and sleeps through nanosleep on POSIX, retrying on EINTR, or high-resolution waitable timers on Windows; a null xtime or one whose normalization overflows throws. Guest thread cancellation is not integrated with this wait. The console's _Thrd_sleep (disassembled from a title's libc.prx) also takes an optional xtime* of remaining time and returns int; here the sleep is never cut short, so it returns 0 and leaves the remaining time untouched.

  • _Mtx_init_with_name (libc, NID tgioGpKtmbE) forwards to _Mtx_init and ignores the mutex name parameter.

  • _Cnd_timedwait (libc, NID McaImWKXong) follows the console's _Cnd_timedwait (disassembled from a title's libc.prx), where xtime is an absolute deadline converted against the current time: it returns _Thrd_timedout (2) on expiration and _Thrd_success (0) on notification. The console returns 4 for other internal pthread errors while this implementation throws.

  • fegetround, fesetround, fetestexcept, feclearexcept, fegetexceptflag and fesetexceptflag (libSceLibcInternal, implemented in libc) follow FreeBSD 11.4 amd64 fenv.h: rounding modes use x87 control bits 10-11, exception flags include denormals (0x3f), and fexcept_t is 16 bits. They access the current host thread's x87 and MXCSR state. This console ABI is assumed, as for fegetenv, not measured on hardware. Null flag pointers throw instead of faulting.

  • fegetenv (libSceLibcInternal, implemented in libc, NID psx0YiAKm7k) follows FreeBSD 11 amd64 fenv.c with the 32-byte fenv_t of x86/fenv.h: fnstenv fills the 28-byte x87 part, MXCSR follows at 0x1c, and the x87 control word is reloaded because fnstenv masks every x87 exception. That the console's libSceLibcInternal has the same code and layout is assumed, not read from its binary. A null pointer throws instead of faulting

  • Guest arena (libc) - the application map area is assumed to end at 0xFC_0000_0000, as SCE_KERNEL_APP_MAP_AREA_END_ADDR does on the PS4. On Windows, host allocations made in the arena before libc loads stay, and a fixed guest mapping over one throws

  • sceLibcInternalBacktraceForGame (libSceLibcInternal, implemented in libc) - (const char* heapName) and its return follow the libc.prx ASTRO BOT (PPSA21564 01.007.000) bundles: its four calls (0x6801, 0x7F46, 0xE75E, 0xEE02) are in the malloc_stats and sceLibcMspaceMallocStats paths, each after a [%s] ... line printed with the same heap name buffer, and the caller ignores the result and continues. The console's backtrace output is not reproduced: the heap name and the guest return address are written to the host standard error

  • sceLibcInternalHeapErrorReportForGame (libSceLibcInternal, implemented in libc) - (void* heap, void* block, uint32 error) follows the same libc.prx (call at 0xF634): it passes the heap, the block and the nonzero result of its block check, and then calls a function that never returns, so the report only has to return. The meaning of the error values is unknown; the three values and the guest return address are written to the host standard error

  • sceLibcPafMspaceCreate, Destroy, Malloc, Memalign, PosixMemalign, Free, Calloc, Realloc, Reallocalign, MallocStats and MallocStatsFast - PAF exports delegate to sceLibcMspace without malloc hooks; Memalign and PosixMemalign forward to their Mspace counterparts with owner ignored and are unverified on console. Create's options block (version, type 1 or 2 at +2, field at +0x10) throws when non-null as its meaning is unknown. Block footers are not modelled, so owners are ignored: on PS5 firmware 13.60 mismatched owners in Reallocalign or in-place Realloc kill the process and MallocStats returns 1 if non-heap-owned blocks exist, which is not reproduced. MallocUsableSize, Trim, IsHeapEmpty, CheckMemoryBounds, ReportMemoryBlocks and GetFooterValue are not implemented.

  • malloc_usable_size (libc) - with the default allocator it returns the size the block was requested with (the range registered for it), not the rounded-up chunk size Sony's allocator reports; both satisfy "at least the requested size", and bytes past the request would not be write-watched. With an application allocator it calls entry 9 of the replacement table (malloc_usable_size in KytyPS5's MallocReplace order) and throws when the table leaves it null, like malloc_stats_fast

  • malloc_stats_fast (libc) - with the default allocator it fills the 0x28-byte managed-size block as sceLibcMspaceMallocStatsFast does, from the default heap's own accounting, not Sony's allocator: the system sizes count the 1 MiB spans of its small-block classes and its committed large blocks, cached free ones included, and the in-use sizes count each handed-out block at its size-class or 64 KiB-granule size, header included. A block size below 0x28 or a null pointer returns 22, as in the mspace version; the version field is not checked. Ship of Harkinian (PPSA99320) calls it about 10 s after start-up.

  • malloc, calloc, realloc, memalign, aligned_alloc and reallocalign (libc) - follow FreeBSD 11.4 contrib/jemalloc/src/jemalloc.c for failures, not measured on the console: a size the default heap rejects or the guest arena cannot place (including a calloc product that overflows) returns null with errno ENOMEM, and memalign and aligned_alloc return null with EINVAL for an alignment that is zero or not a power of two. FreeBSD's libc has no memalign, so setting errno there is assumed. A host commit failure for a block that fits in the arena (Windows ERROR_NOT_ENOUGH_MEMORY, ERROR_OUTOFMEMORY or ERROR_COMMITMENT_LIMIT, Linux ENOMEM) resets and releases the reserved range and returns null with ENOMEM; any other commit failure releases the range and throws. reallocalign returns null with EINVAL for an invalid alignment and ENOMEM like the others, which is an assumption as it is not in FreeBSD. realloc(ptr, 0) frees the block and returns null, where FreeBSD 11.4's jemalloc 4.5 resizes it to one byte and returns a block

  • reallocf (libSceLibcInternal, implemented in libc) - follows FreeBSD 11 lib/libc/stdlib/reallocf.c on top of this libc's realloc, not measured on the console: size 0 frees the block and returns null as realloc does here, so the block is not freed twice; when realloc fails because the application allocator returns null or the default heap rejects the size, the block is freed through the same allocator, errno is ENOMEM and null is returned. Other failures (a commit failure that is not out of memory, an unknown pointer) throw, as they do for realloc

  • sceLibcMspaceReallocalign (libSceLibcInternal, implemented in libc) takes (void* heap, void* block, size_t alignment, size_t size), the order of the console's libSceLibcInternal.sprx (SDK 4.5x build of J03950001, 0x13880, whose internal routine at 0x12390 treats the third argument as the alignment and, for a null block, passes it to the routine sceLibcMspaceMemalign uses). Measured on a PS5 (firmware 13.60) from a homebrew payload: a null block with alignment 4096 and size 100 returns a 4096-aligned block; an alignment of 0 or 3 succeeds and 100 gives a 128-aligned block, as the routine raises it to at least 32 and to the next power of two; an alignment of 2^63 or more returns null, keeps the block and leaves errno alone; and a size of 0 returns a block (the same one when it is already suitable) instead of freeing it. A block that is aligned and large enough is kept where it is, without giving back the bytes past the new size as the console's in-place resize does

  • acosl (libc) - computed by the host's math library, like the other libc math functions here, not by the console's (FreeBSD msun ld80 code in Sony's libc.prx): results can differ from the console's in the last bit, and on Linux glibc sets errno to EDOM for acosl outside [-1, 1] where msun only raises the floating-point exception. long double is the x87 80-bit format, as Sony's frexpl uses it (fldt 0x8(%rsp), result in st(0))

  • atanl, sinl, cosl, tanl, sinhl, coshl, tanhl, expl, exp2l, logl, log10l and powl (libc) - computed by the host's math library, as acosl is, not by the console's FreeBSD msun ld80 code: results can differ from the console's in the last bit (glibc's log10l(1000) is not exactly 3), and on Linux glibc sets errno to EDOM or ERANGE where msun only raises the floating-point exception. ldexpl and truncl are exact operations and give the console's result; logb and remainder give the console's value too, but on Linux glibc sets errno (ERANGE for logb(0), EDOM for remainder by zero or of an infinity) where msun only raises the floating-point exception

  • Dinkumware atomics (libc) - every operation is sequentially consistent whatever memory order it is given, which is never weaker than the console. Matching the libc.prx bundled with PPSA08427 (that binary run side by side on every order from 0 to 7), _Atomic_load_8 returns 0 for a release, acq_rel or invalid order and the 8-byte exchange and fetch operations return 0 without touching memory for an order above memory_order_seq_cst; the generic _Atomic_copy, _Atomic_exchange and _Atomic_compare_exchange_* lock the byte flag passed first, as the console does

  • std_execute_once (libc) - assumed to be the plain-named twin of std::_Execute_once (_ZSt13_Execute_onceRSt9once_flagPFiPvS1_PS1_ES1_, the callee of Dinkumware call_once): it delegates there and gets the [thread.once.callonce] semantics already verified for that symbol (one invocation per flag, concurrent callers block until it completes, and a failing or throwing invocation leaves the flag unready so the next caller runs it). The name equivalence is not confirmed on a title, and the callback convention (called with (null, arg, null), non-zero return meaning success) is the twin's, unverified for this symbol

  • __atomic_load, __atomic_store, __atomic_exchange and __atomic_compare_exchange (libc) - the generic, size-parameterised libatomic entry points are a single ud2 in the libc.prx bundled with PPSA08427 (disassembly), so the console traps on any call; these throw instead

  • Dinkumware mutexes and conditions (libc) use pointer-to-handle arguments, matching the console aerolib signatures reported in #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.

  • Floating-point compiler-rt builtins (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 (libc) follow FreeBSD 11 random.c: the additive feedback generator on a default 128-byte TYPE_3 table, good_rand seeding, state sizes rounded down to 8, 32, 64, 128 or 256 bytes, and setstate returning the previous state or null for a rear position past the degree. initstate below 8 bytes returns null without setting errno, as that file and its manual do (glibc sets EINVAL). FreeBSD does not lock the generator; here every call is serialised by one lock, so concurrent callers draw the serial sequence between them. A null state throws, where FreeBSD faults. That the console's libSceLibcInternal uses this FreeBSD code and not another generator is unverified

  • Integer compiler-rt builtins (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 (libc) follows FreeBSD 11 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

  • memcpy_s, memmove_s, strcpy_s, strncpy_s, strcat_s, strncat_s, wmemcpy_s, wmemmove_s, wcsncpy_s, wcscat_s, wcsncat_s (libc) use the C11 Annex K destination size constraints, with RSIZE_MAX taken as (SIZE_MAX >> 1) / sizeof(char16_t) for wide operations and SIZE_MAX >> 1 for byte operations. An oversized destination size returns ERANGE (34) without touching the destination; the console's RSIZE_MAX and exact error value are unverified. Constraint violations return an error without invoking the registered constraint handler, as before.

  • snwprintf_s (libc) - follows C11 K.3.9.1.3 with RSIZE_MAX assumed to be SIZE_MAX >> 1. A runtime-constraint violation only returns a negative value: set_constraint_handler_s is not exported and the console's default handler is unknown. An invalid multibyte %s argument is copied byte by byte instead of being reported as an encoding error, as in vswprintf

  • swprintf_s and vswprintf_s (libc) - follow C11 K.3.9.1.4 with RSIZE_MAX assumed to be SIZE_MAX >> 1: the complete output, including the terminating null, must fit in the buffer, otherwise it is a runtime-constraint violation that sets buffer[0] to zero and returns a negative value, and the buffer is not partially written. Argument and encoding checks match snwprintf_s

  • strerror_s and strerrorlen_s (libc) follow C11 K.3.7.4.2 and K.3.7.4.3, using the existing FreeBSD error messages and unknown-error formatting. Truncation writes an ellipsis when capacity exceeds 3; constraint failures leave the destination untouched and invoke the registered guest-ABI handler. The console's RSIZE_MAX (SIZE_MAX >> 1 here), nonzero returns (EINVAL 22 for constraints, ERANGE 34 for truncation), handler arguments and default abort behavior are unverified. Messages remain in the C locale, and guest errno is preserved.

  • getw and putw (libc) follow FreeBSD 11.4 getw.c and putw.c: one 32-bit little-endian word, getw returns -1 for EOF or an error, and putw returns 0 on success or -1 on error while holding the stream lock. They use the existing host-backed streams, so buffering and error timing follow the host CRT. Null streams throw and closed streams return -1 with EBADF, as existing byte I/O does. Console behavior is unverified.

  • vsnwprintf_s (libc) - va_list counterpart of snwprintf_s (C11 K.3.9.1.3): truncates to the buffer and returns the full length, with the same constraints and argument checks

  • strndup and strdup (libc) return null with errno set to EINVAL (22) when source is null, preventing a host segfault on invalid guest input. POSIX.1-2008 and FreeBSD 11.4 libc leave strndup(NULL) and strdup(NULL) undefined (faulting); exact console behavior on hardware is unverified.

  • snprintf_s and vsnprintf_s (libc) - follow C11 K.3.5.3.5 and K.3.5.3.12 with RSIZE_MAX assumed to be SIZE_MAX >> 1, as snwprintf_s does: a null buffer or format, size of 0 or above RSIZE_MAX, any %n and a null %s, %ls or %S argument return a negative value and set buffer[0] to zero when the buffer and size allow it; otherwise the output is truncated to the buffer and the full length is returned. A violation only returns: the constraint handler is not invoked, and the console's error value is unverified. Not confirmed on a PS5 title

  • vsscanf_s (libc) - returns the count of items converted before an undersized %s/%c/%[ destination (0 when it is the first), not EOF. Not confirmed on a PS5 title

  • localeconv (libc, re-exported by libSceAmpr) - the 0x90-byte Dinkumware struct (empty monetary strings, CHAR_MAX for the 14 char fields, decimal_point ".", _False/_True "false"/"true") is read from the libc.prx that Astro's Playroom ships. It is a fixed C-locale table: whether the console changes it with setlocale is not known, and _Frac_grouping, _Frac_sep, _No, _Yes are empty strings as in that dump

  • set_constraint_handler_s (libc, re-exported by libSceAmpr) - the default handler is assumed to abort; each module keeps its own handler state, so a handler set through one library is not seen by the other. Within a module every _s function calls the installed handler, with the guest's calling convention. ctime_s and asctime_s follow the Dinkumware 26-byte format with EINVAL for a buffer under 26 bytes, a null pointer or an out-of-range weekday/month; ctime_s uses the guest time zone of localtime_s, and the console's exact checks are not verified

  • qsort_s (libc) sorts as qsort does and passes its context as the comparator's third argument, as FreeBSD 13's qsort_s (C11 Annex K) does. It returns EINVAL after calling the constraint handler when n or es exceeds RSIZE_MAX, or when n is not 0 and a or cmp is null; FreeBSD 11, which the rest of libc follows, has no qsort_s. An element size of 0 sorts nothing, where FreeBSD loops over the same address. The console's behaviour is unverified; the Quake homebrew (#6698) imports it from its libOSMesa.so.8

  • __udivti3 (libc, re-exported by libSceAmpr) - the hardware traps on a zero divisor; this throws instead

  • __umodti3 (libc) - the hardware traps on a zero divisor; this throws instead

  • __modti3 (libc) - the hardware traps on a zero divisor; this throws instead

Functional

  • _FSinh and _FCosh (libc) finite results are not verified bit for bit against the console: the scaled product uses the host's binary64 hyperbolic function before rounding to binary32, rather than the float rational and _FExp sequence in PRAGMATA's bundled libc.prx. NaN preference and payloads, signed zeros, infinite operands and the range-error thresholds follow that binary's instructions; the system libSceLibcInternal implementation and nondefault flush-to-zero modes are unverified.

  • sscanf_s and vsscanf_s (libc) return EOF when a reached conversion has a null destination, stopping before further assignments, following C11 K.3.5.3.7 and K.3.5.3.14. Earlier assignments are retained. The constraint handler is not invoked, and console behaviour is unverified.

  • memmem and memccpy (libc) - memmem searches for needle occurrences in memory buffers and memccpy copies buffers until a character match, following FreeBSD and POSIX specifications; behavior is unverified on console.

  • Guest time conversions rewrite the host process TZ when a guest changes its own TZ, so other host consumers of that variable (including libSceRtc local time) shift too; code outside libc does not take the zone lock. Zone syntax follows the host C runtime, not FreeBSD: Windows only accepts the std offset [dst] form, treats an empty guest TZ as UTC0 and reads :Europe/Berlin as an abbreviation (for example rop); only Linux honours zone file names. If the guest accepts a TZ value that the host rejects (on Windows, for example, one longer than the host environment limit), conversions throw std::runtime_error. A tm_zone saved before a TZ change can refer to storage now holding the new zone name, changing %Z output.

  • __isfinitel - classifies the x87 80-bit exponent without narrowing to double or performing floating-point comparisons, following FreeBSD msun. The signature and classification follow FreeBSD; console behaviour is unmeasured. Requires little-endian x87 long double stored in 16 bytes, as the existing guest long-double ABI does.

  • Windows direct-memory backing reserves a paging-file section. A direct memory page mapped at one guest address is private write-watched memory at that address and is not committed in the section; a page mapped at a second address moves its whole resident run into the section (committed there) and every address becomes a view of it, tracked by write protection. An unmapped resident page that is not all zeros is kept in a private reservation until it is mapped again. Committed section contents remain across unmap/remap and are released when the backing and its remaining views are destroyed. A host import alias over part of a resident run is refused. Linux retains its existing backing. Peak gameplay memory and Windows versions other than the tested host remain unverified.

  • Stable Windows resident runs of at least 256 KiB use temporary read-only protection for clean write-watch proofs. The first CPU write restores the logical protection of the whole run; that run remains on native write watching until it is remapped, so repeatedly rewritten runs stay writable. Native kernel writes into guest buffers must hold GuestArena::HostWrite, as shared views already require: an unbracketed WinAPI ReadFile can fail with ERROR_NOACCESS while the resident proof is armed. Guest read, pread and socket receive paths already bracket their destinations. GPU host-import access suppresses resident rearming without inventing shared-page stores. This is a host integration requirement, not a change to guest logical protection.

  • getc_unlocked, putc_unlocked, getchar_unlocked, putchar_unlocked (libc) - byte I/O uses the host unlocked operations; guest EOF/error flags and macro counters follow FileStream. Linux status queries use feof_unlocked/ferror_unlocked because glibc feof and ferror can acquire the stream lock. Windows uses the CRT no-lock byte operations and feof/ferror, verified under a held stream lock on UCRT. Console behavior and other host CRTs remain unverified.

  • feof_unlocked and ferror_unlocked (libSceLibcInternal, implemented in libc) have no console measurement. The firmware 3.20 export inventory names NIDs NuydofHcR1w and yxbGzBQC5xA. They follow FreeBSD 11 feof.c and ferror.c, returning 0 or 1 from the stream status without acquiring the stream lock. They inherit the host CRT limitations of the unlocked byte operations above.

  • fgetws, getwc, getwchar, putwc, putwchar (libc) - wide stream I/O encodes and decodes wide characters as UTF-8 on the underlying byte stream; fgetws reads characters into the destination buffer until newline or EOF and null-terminates, returning null on stream error. Console wide stream buffering and encoding details remain unverified.

  • Closed standard descriptors (libc) - on Linux and macOS, a descriptor 0, 1 or 2 that is closed when libc loads is opened on /dev/null (read-only for 0, write-only for 1 and 2) by a static initializer of libc, which runs before the libraries that depend on libc initialize and before the title starts. Within libc this relies on no static initializer opening a descriptor before it, which guest_closed_standard_descriptors checks. glibc does the same for set-user-ID programs (check_fds.c). Without it the first descriptor a library opens, the write watch's userfaultfd for one, becomes the guest's standard input or output. What the console gives a title as its standard descriptors is not measured, and on Windows a process started without standard handles is not handled.

  • Guest path names (libc) are checked against FreeBSD 11.4's NAME_MAX (255 bytes per component) and MAXPATHLEN (1024 bytes with the terminating NUL) before they are resolved, and a longer name fails with ENAMETOOLONG, as namei does. The host's own limit still applies below that: on Windows a path whose host form exceeds MAX_PATH fails with ENOENT. A backslash counts as a path separator for the component length, because the guest path resolver turns it into / before the host call, while FreeBSD treats it as an ordinary name character, so a 301-byte name that contains one is accepted here. sceNgs2ParseWaveformFile resolves its path without a try block, so a long component ends in an uncaught filesystem_error there where a missing file ended in a runtime_error (both end the title). A path through a regular file (file/x, file/) is not always ENOTDIR on the calls that resolve it. None of this was measured on a console.

  • ftrylockfile (libc) - tries the host CRT stream lock that flockfile, funlockfile and each locking host stdio call on the stream take, so it returns -1 while another thread holds the stream through flockfile or is inside such a call, and 0 when it acquires the lock or already owns it (recursively, released by as many funlockfile calls), as FreeBSD 11 _ftrylockfile does. A libc export holds the lock only for each host call it makes, not across several host calls or while it updates the guest FILE prefix. Linux uses glibc ftrylockfile. UCRT has no try variant of _lock_file, so Windows calls TryEnterCriticalSection on the CRITICAL_SECTION that _lock_file enters, found through the private UCRT stream layout (__crt_stdio_stream_data in ucrt/inc/corecrt_internal_stdio.h, unchanged from Windows SDK 10.0.19041 to 10.0.26100); another CRT or a change of that layout breaks it. A null or closed stream throws where FreeBSD dereferences it

  • fgetln (libSceLibcInternal, implemented in libc) throws: in the console's libSceLibcInternal.sprx (SDK 4.5x build of J03950001, 0x5FE80) the export is a hlt instruction, which faults in user mode and ends the process

  • Wide-character conversions (libc) - The wide wcsto* functions parse the ASCII prefix of the guest string, as the C locale accepts only ASCII white space and digits. wcsrtombs uses the guest's single-byte C locale through wcrtomb; other encodings and shift states are not supported

  • realpath (libc) follows FreeBSD 11's realpath.c except for symlinks. The console's guest filesystem has none, so a host symlink under the guest root is followed for existence and type but not expanded, and .. drops the previous component lexically, as Resolve does for every guest path. Through a host link linkdir -> sub/deep, linkdir/../x gives x beside the link, not beside its target.

  • strtod, strtof, strtold (libc) - strtod and wcstod (and atof, which calls strtod here) read a NAN(...) sequence as FreeBSD 11's gdtoa hexnan does: hexadecimal digits with an optional 0x, blanks starting a new 32-bit word, the first 16 digits kept and the payload cut to 52 bits, a zero payload made 1, any other character giving the default NaN but consuming up to the closing parenthesis, and an unterminated sequence keeping its payload while consuming only nan. The payload goes under the quiet bit with the sign, as in gdtoa's strtod. strtof, wcstof, strtold and wcstold return the positive default quiet NaN (0x7fc00000; exponent 0x7fff, significand 0xc000000000000000) whatever the sign and the sequence, and consume a sequence of letters, digits and underscores up to its closing parenthesis. The console's libSceLibcInternal.sprx, called from a payload process on a PS5 (firmware 12.70), gives these results for the 64 NaN inputs tried through strtod and strtof, 50 of them also through strtold and wcstod, and 18 through wcstof and wcstold, and, through strtod, for nan(1 2) and for nan( followed by NUL and ); that a title's own libc.prx does the same is assumed. In hexnan, a blank after a payload digit, while a 32-bit word of the payload is still free, starts a blank skip that has no NUL check, so when only blanks follow up to the terminating NUL the console's strtod reads past it: nan(1 followed by NUL and ) consumes the ) after the NUL, so the result depends on the bytes after the string, and here that case throws. A blank right after nan( is skipped with a NUL check, and the console stopped at the NUL there

  • remquol (libc) leaves *quo unchanged when the result is NaN; the libc.prx bundled with PPSA08427 writes a value that depends on earlier x87 state (remquol(1, 0) stored 0 on a first call and 4 on later ones)

  • Long double math in MathExact.cpp (libc) was compared with the libc.prx bundled with PPSA08427 only for canonical 80-bit encodings; unnormals, pseudo-denormals and pseudo-infinities passed to arithmetic functions (not the classifiers) follow the host x87 instead of the console

  • rename, sceKernelRename (libc, libkernel) fail a last path component of . or .. with EINVAL, after FreeBSD 11.4's lookup, which refuses . and, through its ISDOTDOT and ISLASTCN flags, .. as the last component for the DELETE and RENAME operations. rmdir("dir/."), unlink("dir/.") and the same calls with .. follow the same lookup rule in FreeBSD but are not changed here: the guest still resolves the dot component away and acts on dir or its parent. Both arguments are checked before any host access, so rename of a missing source onto dir/. answers EINVAL, where FreeBSD's kern_renameat looks the source up first and answers ENOENT. A backslash counts as a path separator here (the guest path resolver turns it into /); FreeBSD treats it as an ordinary name character. None of this was measured on a console.

  • nan and nanf (libc) return fixed quiet NaN values (0x7fc00000 and 0x7ff8000000000000), ignoring the tag argument as observed in the console's libc.prx (both exports load a global constant via mov rax,[rip+..]; vmovsd xmm0,[rax]; ret); VLC (PPSA85300) stops on nanf at start-up after strsep.

  • jrand48 and mrand48 (libSceLibcInternal, implemented in libc) return the high 32 bits of the new 48-bit state zero-extended to 64 bits, as the console's libSceLibcInternal.sprx does when called from a homebrew payload process on a PS5 (firmware 12.70): a result with bit 31 set comes back between 0x80000000 and 0xFFFFFFFF, as FreeBSD 11.0's jrand48 and mrand48 return it; FreeBSD 11.1 and later cast it to int32_t (jrand48, mrand48), which makes it negative. lrand48 and nrand48 matched FreeBSD there. Whether a title's own libc does the same is unverified

  • seed48 and lcong48 (libSceLibcInternal, implemented in libc) follow the console's libSceLibcInternal.sprx (SDK 4.5x build of J03950001, 0x6F460 and 0x6F420): the multiplier and addend are shared by the whole rand48 family, including erand48, nrand48 and jrand48 on a caller's seed; lcong48 sets them with the seed, and srand48 and seed48 restore 0x5DEECE66D and 0xB. Unlike FreeBSD, seed48 returns a static copy of the new seed rather than the previous one. A null argument, which the console dereferences, throws

  • lgamma and signgam (libc) - lgamma returns the host's lgamma and stores the sign of Γ(x) in the exported signgam (NID 5gOOC0kzW0c, starting at 0 as in FreeBSD 11's s_signgam.c) by the rules of e_lgamma_r.c: -1 for -0 and for a negative non-integer whose floor is odd, 1 otherwise, including poles, infinities and NaN. Taken from the FreeBSD source, not measured on the console. The NID is in the aerolib symbol list; no title is known to read signgam. PPSA99004 imports lgamma

  • sinhf (libc) follows the sinhf of the libc.prx bundled with PPSA28319 (disassembly): FreeBSD msun's e_sinhf.c from before __ldexp_expf, which squares expf(|x| / 2) between logf(FLT_MAX) and the overflow threshold, calling msun's expm1f and expf, whose paths for the arguments it passes are inlined here. Past the threshold Sony's error helper sets errno to ERANGE before returning x * 1e37; msun itself sets no errno. That code, run on a Windows host, gives the same bits as this implementation for every finite argument up to the threshold, of both signs; it was not run on Linux. That the system libSceLibcInternal, which Ruffle (PPSA68091) and RetroArch (PPSA99169) import while bundling a zero-return libc.prx, has the same code is assumed

  • acoshl, asinhl, atanhl, atan2l, cbrtl, erfl, erfcl, expm1l, hypotl, log1pl, log2l, lgammal, tgammal, j0f, j1f, jnf, y0f, y1f and ynf (libc) return the host C library's results (the float Bessel functions through its double ones), not those of the console's own code, so the last bits can differ (y0f, y1f, ynf follow the console at +-0). As in the console's libSceLibcInternal.sprx (SDK 4.5x build of J03950001, 0x56410), lgammal does not store the sign in signgam; hypotl returns infinity for an infinite argument even when the other is a NaN, as C11 F.10.4.3 requires. hypot3, hypot3f and hypot3l follow that module (0x51D30, 0x520D0, 0x52420): the first NaN of x, y and z is returned unchanged, any infinity gives positive infinity, a zero argument gives the two-argument hypot of the other two, and otherwise the largest magnitude is combined with the hypot of the other two

  • asinl (libc) follows the asinl of the libc.prx bundled with PPSA28319 (disassembly), FreeBSD msun's e_asinl.c with the ld80 invtrig.c coefficients, which the binary holds as the same 80-bit constants. For |x| > 1, the infinities and NaN, Sony's error helper raises FE_INVALID and sets errno to EDOM before returning (x - x) / (x - x), so a NaN argument also sets EDOM; msun itself sets no errno. 20 million random arguments in (-1, 1) give the same bits as that code run on a Windows host. The result needs the x87 precision control at 64 bits, as FreeBSD amd64 starts threads (control word 0x37f), but guest threads do not set it: on Windows a thread started by _beginthreadex runs at 53 bits (0x27f), where asinl(0.75) returns mantissa 0xd91a98ae3406e000 instead of 0xd91a98ae3406e041. The system libSceLibcInternal that RetroArch (PPSA99169) imports is assumed to have the same code

  • atanhf (libc) follows the atanhf of the libc.prx bundled with PPSA28319 (disassembly): FreeBSD msun's e_atanhf.c calling msun's log1pf, whose paths for the positive arguments it passes are inlined here; the exported log1pf is still the host's. For |x| > 1, the infinities and NaN, Sony's error helper raises FE_INVALID and sets errno to EDOM before returning (x - x) / (x - x), and for |x| = 1 it raises FE_DIVBYZERO and sets errno to ERANGE before returning x / 0; msun itself sets no errno. That code, run on a Windows host, gives the same bits, errno and floating-point exceptions as this implementation for all 2^32 arguments; it was not run on Linux. That the system libSceLibcInternal, which Dusklight (PPSA99305) imports while bundling a zero-return libc.prx, has the same code is assumed

  • j0, j1, jn, y0, y1 and yn (libc) call the host's sin, cos, sincos and log, so the last bits can differ from the console. The rest follows the system libc.prx built as J03093718 (disassembly), which given the console's own sin, cos, sincos and log gave the same bits and exception flags for 90 million arguments: msun's e_j0.c, e_j1.c and e_jn.c with each multiply-add the console fuses done as one FMA. As in that release, before FreeBSD commit 186f620727, y0, y1 and yn return -inf for ±0 and 0x7ff8000000000000 for a negative argument without raising an exception. The console's y0f, y1f and ynf raise none either, unlike ours, which raise FE_DIVBYZERO at ±0. The order wraps at 32 bits as on the console: jn(1 << 30, x) never returns for 2^-29 <= |x| < 2^30

  • getenv, setenv, unsetenv, putenv (libc) keep one guest environment that starts empty, like the environ libkernel exports, and never reads or changes the host's. A title sees only the variables it sets itself; host variables such as HOME and PATH name host paths a guest cannot use (PS5MC, PPSA99420, exits with XBMC_Run returned -1 when it gets the host's HOME). Whether the console starts a title with any variables set is not measured

  • mergesort (libSceLibcInternal, implemented in libc) checks its arguments like the console's libSceLibcInternal.sprx (SDK 4.5x build of J03950001, 0x762D0): an element size under 4 sets errno to EINVAL (22) and returns -1, an empty array returns 0, and a failed malloc of the count times the size plus 8 bytes returns -1. The sort is a stable bottom-up merge, not FreeBSD's natural merge with its insertion sort for short runs, so the comparator is called in a different order and number of times; the result is the same for a consistent comparator. A null base or comparator and a size product that overflows, which the console does not check, throw

  • On macOS the write watch write-protects watched pages and records the first write to each from a SIGBUS/SIGSEGV handler, because macOS has no counterpart to userfaultfd write-protect or MEM_WRITE_WATCH. The protection is real, so:

    • a system call that writes into a protected page fails with EFAULT instead of faulting. GuestArena::HostWrite lifts the protection for the calls it brackets (file reads and socket receives), as Host writes into guest memory requires; a host call that writes guest memory without it (clock_gettime and the like into a watched page) fails;
    • code that changes the protection of watched pages, or maps over them, has to call GuestWriteWatchProtectionChanged, and code that asks the kernel for a page's protection has to ask GuestWriteWatchProtection first, since a watched page is read-only between a collection and the next write. Nothing calls either yet: the macOS memory paths are not in the tree;
    • the handler is installed when libc loads and passes other faults to the handler installed before it. A handler installed later has to do the same.
  • On macOS the guest arena finds host regions with mach_vm_region and reserves the rest with a fixed mach_vm_map, which fails instead of replacing, as macOS has no /proc/self/maps or MAP_FIXED_NOREPLACE. The kernel keeps the range up to 0x70_0000_0000 unmappable for every process (max protection none). It starts at 0x10_0000_0000 on macOS 26.5 (M1 Pro, arm64 and x86_64 under Rosetta) and at 0xF_C000_0000 on macOS 27 (27.0.1 on an M4 Pro, arm64 and x86_64 under Rosetta; 27.2 on an M2 Max, arm64). With dyld and the Rosetta runtime near 0x2_0200_0000 and 0x3_0400_0000, a guest mapping fixed inside those ranges fails where the console places it; mappings without a fixed address are placed around them.

  • sceKernelSync (libkernel) flushes only the host files and directories recorded through RecordWrittenPath_nid_no_patch (the libc, libkernel, save data and app content file functions) and their changed parent directories. A host write in a library that does not record its path is not flushed by sceKernelSync

  • sceKernelSync (libkernel) on Windows skips a recorded file it cannot open for writing (read-only, or held open by a handle that denies write sharing), since FlushFileBuffers needs a writable handle. On Linux, it skips a recorded file it cannot open for reading. Such a file's data may not reach the disk before a host crash; the guest reads the same data either way

  • fopen and freopen (libc) on Windows fail to open a directory in a read mode with EACCES, where FreeBSD opens it; directory streams are not emulated

  • readdir and readdir_r (libc) on Windows report, on a file system whose directory listing carries no file ID (the entry's ID is 0, or the volume refuses FileIdBothDirectoryInfo), a nonzero d_fileno hashed from the entry name and the directory's file index, which differs from lstat's st_ino. Not reproduced: NTFS lists an ID for every entry

  • Writes on an update stream (libc) on Windows go through the UCRT, which drops a write that directly follows a read without fflush, fseek, fsetpos or rewind unless the read reached end of file: fwrite returns 0, and fputc and fprintf report success, without the error indicator. FreeBSD 11.4's __swsetup discards the read buffer and writes at the descriptor's offset, past a read-ahead of st_blksize bytes for a regular file, so where the console writes depends on its block size. Reads directly after writes follow FreeBSD's __srefill

  • fopen of /dev/random and /dev/urandom (libc) in a read mode returns a stream over the host null device whose fread fills the buffer with host OS entropy, as the libkernel random device descriptors do. Only the exact paths are recognised, the stream has no descriptor, setvbuf, feof, ferror, clearerr, flockfile, ftrylockfile, funlockfile and fclose work on it, fflush returns 0 as on a FreeBSD read-only stream, and every other stream function throws, as does opening the paths for writing. freopen onto these paths still fails with ENOENT. ProsperoTV (PPSA99003) reads its phone pairing code this way

  • RemoveTree (libc) removes a host tree itself on Windows instead of calling std::filesystem::remove_all: MinGW libstdc++ up to GCC 16 does not treat a reparse point as a link, so remove_all recurses through it and never terminates, and any tree that holds a symlink hangs. It builds the entry list from GetFileAttributesW, removes a reparse point as the link it is rather than its target, clears the read-only attribute and retries as remove_all does, and reports the first native error. The libstdc++ fix is in GCC 17 (commit 88856ad855ae); other hosts call remove_all.

  • The _Unwind_Context that _Unwind_Backtrace passes to its callback starts with a table laid out like LLVM libunwind's AbstractUnwindCursor virtual table, because homebrew built with the ps5-payload-dev SDK links libunwind statically and reads the frames with its own _Unwind_GetIP (__unw_get_reg), which calls through that table. Only register reads and writes (validReg, getReg, setReg, with UNW_REG_IP and UNW_REG_SP), validFloatReg (always false) and isSignalFrame are answered; the other slots throw. The layout follows libunwind's UnwindCursor.hpp, not the console's own libSceLibcInternal, which is not checked

  • The Windows libc exception unwinder steps through frames of modules without the project's .ehmeta and .ehfram tables (system DLLs, and WinLibs GCC 15.2's libstdc++-6.dll and libwinpthread-1.dll, whose .eh_frame sections are empty) with their PE unwind data through RtlVirtualUnwind, but does not run Windows exception or termination handlers: it stops at a frame whose unwind data has one, such as a C++ cleanup through __gxx_personality_seh0 in libstdc++-6.dll or a system DLL function like qsort in ucrtbase.dll (Windows 10 22H2), and the exception is then reported as unhandled

  • The libc SSE4a trap emulation on Windows is superseded by --to-intel and remains only until the relinked title has been verified without it.

  • getifaddrs (libc) lists host interfaces and IPv4/IPv6 addresses in FreeBSD's layouts. Windows omits per-interface AF_LINK entries and interfaces without an address; its flags are only IFF_UP, IFF_RUNNING, IFF_LOOPBACK, IFF_BROADCAST and IFF_MULTICAST (PPP/tunnel adapters omit broadcast and point-to-point, and an unspecified prefix length gets a host mask). On Linux, AF_LINK if_data maps non-Ethernet/loopback/PPP types to IFT_OTHER (1) and leaves ifi_omcasts, ifi_noproto, ifi_hwassist, ifi_metric, ifi_epoch, ifi_lastchange, unknown baud rates and address-entry ifa_data as 0/null; a point-to-point address without a peer loses IFF_POINTOPOINT, and an omitted IPv4 broadcast is derived from the netmask. Interface names are the host's (if<index> on Windows above 15 bytes).

  • gethostname (libc) reports the fixed guest name AnyPS5 without querying the host machine name. Buffer copying, null queries and ENAMETOOLONG follow FreeBSD gethostname; the console's configurable hostname and its integration with sysctl and name resolution are unverified and not implemented.

  • wctomb_s, wcrtomb_s, mbstowcs_s and mbsrtowcs_s (libc) follow the console's libc.prx, but convert in the single-byte C locale of wctomb, wcrtomb and mbrtowc (MB_CUR_MAX 1): a character above 0xFF is EILSEQ in wctomb_s and wcrtomb_s, and each byte is one wide character in mbstowcs_s and mbsrtowcs_s. The locale of a title's libc.prx was not measured. The wcrtomb_s path where a character does not fit smax and the EILSEQ paths of mbstowcs_s and mbsrtowcs_s are not reached

  • wcsrtombs_s (libc) follows the console's libc.prx, but a runtime-constraint violation only returns its error: the default handler there writes the message and a newline to stderr before returning, which is not done here. Conversion uses the C locale of wcrtomb (MB_CUR_MAX 1), so the path where a multibyte character does not fit the remaining space is not reached

  • c16rtomb, c32rtomb, mbrtoc16 and mbrtoc32 (libSceLibcInternal, implemented in libc) follow the console's libSceLibcInternal.sprx (SDK 4.5x build of J03950001, 0x7D480 to 0x7D920), which converts to and from UTF-8 whatever the locale, with the original sequences of up to six bytes and no check for surrogates or for values above 0x10FFFF when encoding. The conversion state keeps the value in its first eight bytes and the count of bytes still expected (or 0x10 for a pending low surrogate, 1 for a stored high surrogate) as 16 bits at offset 0xA; a null state, which the console dereferences, throws. As on the console, mbrtoc16 does not store the complete code point when it returns a high surrogate, so the low surrogate of the next call is built from the value the state last held, which is the partial value of an earlier incomplete call or whatever the caller initialised it to, c16rtomb returns -1 without setting errno when a high surrogate is not followed by a low one, and mbrtoc16 and mbrtoc32 report EILSEQ as 86. Measured on a PS5 (firmware 13.60) from a homebrew payload, the system libSceLibcInternal's default locale also converts UTF-8 in wcrtomb, wctomb, mbrtowc and mblen (wctomb of U+263A gives E2 98 BA, mblen of a lone 0xE9 gives -1); this libc keeps its single-byte C locale, so wctomb and mblen, which call wcrtomb and mbtowc here as they call the same internal routines on the console, share that difference. Titles bundle their own libc.prx, whose locale was not measured

  • The narrow printf family (libc) converts 16-bit guest wide arguments to UTF-8 on both platforms, independently of the host locale. It does not use the guest's single-byte wcrtomb; the console's treatment of invalid UTF-16 and locale-specific encoding remains unverified. Linux retains host formatting for formats without wide arguments, %m or %p, so a NaN there still follows the host (glibc prints its sign, -nan, and honors the + and space flags, where FreeBSD prints nan); argument positions above 99 (FreeBSD's NL_ARGMAX) throw when a format requires the guest formatter

  • %p and %lp (libc) follow the console's libSceLibcInternal.sprx measured from a homebrew payload on firmware 12.70 (#7549), not FreeBSD: bare lowercase hex with no 0x prefix even under #, 0 for a null pointer, + and the space flag ignored, the width counting only the digits, - padding on the right, 0 padding with zeros when there is no precision, the precision as the minimum number of digits, nothing for a null pointer under %.0p, and l accepted. Only snprintf and swprintf of that module were measured, from a payload process rather than a title process, so a width beside %.0p, the 0 flag beside a precision (which C17 ignores) and %.0p of a non-null pointer follow this formatter's own rules and are not measured; swprintf was not run with %lp, so the wide family still refuses that conversion and returns -1. Linux routes %p and %lp to this formatter because glibc prints the FreeBSD form and (nil) for a null pointer, which means a format that combines %p with a conversion this formatter refuses, such as an argument position above 99, throws there instead of reaching glibc

  • collate and collate<wchar_t> _Getcat (libc) build the facet for the C locale only and throw for any other locale name, the wide entry point following the narrow one it sits next to. The wide facet carries its own vtable, whose compare, transform and hash take the guest's 16-bit characters, so the length of a range is a count of characters: compare orders by 16-bit value (the C locale, as Dinkumware's char_traits<wchar_t>::compare does), transform copies the characters into the result and records size and capacity in 16-bit elements (inline capacity 7, heap capacity length | 7, (capacity + 1) * 2 bytes including the terminator), following Dinkumware's VC12 basic_string, and hash mixes one whole character per round, so wide text below 0x100 hashes like its narrow spelling and a character above 0xff hashes as one value instead of the two bytes it holds. The console's libc.prx exports both _Getcat (NIDs BSVJqITGCyI and 2wz4rthdiy8) with identical 0x19d-byte bodies, believed to build the same 0x20-byte facet; the console bodies themselves are unreadable (obfuscated dynsym, and the text of the relinked libc.prx is zeroed), so the wide entries follow the narrow algorithm and the C locale instead of the console's wide code, and no console run checks the two. The console's libc.prx stores pointers to its collation state tables at offsets 0x10 and 0x18 of the facet; ours leaves them null, as only the facet's own functions read them. do_transform throws for input with embedded null characters, which the console drops depending on the output buffer size. The narrow result capacity (length | 15) and the over-aligned storage of results of 4080 characters or more follow the console's libc.prx code and are not checked on hardware. No module of the PPSA59322, PPSA76677 or PPSA97358 dumps in this tree imports either _Getcat, so neither has been exercised by a title.

  • std::__throw_bad_variant_access (libc) throws std::bad_variant_access with the what() string bad_variant_access, the one LLVM libc++ returns, since the export carries libc++'s __throw_bad_variant_access name; the console's string is unverified. Onimusha: Way of the Sword (PPSA27836 01.000.001) imports it from libc, and its bundled libc.prx does not export it.

  • std::bad_optional_access and std::_Throw_bad_optional_access (libc) provide the exception type, constructor, destructor, and what() exports alongside _Throw_bad_optional_access, returning the what() string bad_optional_access matching Dinkumware and libc++ conventions; the console libc.prx string is unverified. PPSA26578 (issue #7624) and PPSA34015 import _ZSt26_Throw_bad_optional_accessv (KwzEip7m6TM) from libc, and fail to load on main with unresolved import (0xC0000139).

  • The scanf family (libc), the _s forms included, decodes the input of the 16-bit wide conversions (%ls, %lc, %l[, %S, %C) on Linux as UTF-8, mirroring the narrow printf family, where FreeBSD uses the locale's mbrtowc (one byte per character in the C locale); the console's locale encoding is unverified. Input that is not UTF-8, a character outside the BMP, a scanset member outside ASCII, positional arguments next to a wide conversion and wide conversions in fscanf throw. Each character is stored as one 16-bit unit, widths and _s sizes count characters, and a %lc field cut short by the end of the input keeps what it read, as FreeBSD's convert_wchar does. Windows hands %ls, %lc and %l[ to the host, whose wchar_t is 16 bits

  • The scanf family (libc) on Windows returns 0, as C17 and FreeBSD do, when input fails after only suppressed conversions have completed (sscanf("12", "%*d%d", &value)). Linux hands the format to glibc, which returns EOF there. On Windows the host stops a %x, %X, %i or %p after the x of a 0x that no hex digit follows (or that the width cuts off), and a float conversion (%e, %f, %g, %a) after the e of an exponent with no digits (1e), where C17 and FreeBSD stop before the x or the e

  • Guest path resolution falls back to ASCII case-insensitive component lookup, following shadPS4's host filesystem. PPSA03747 requests Skyrim.INI, Data/Video and lowercase archive/plugin names that differ from the dump's filenames. Exact host names take precedence; multiple fallback matches throw instead of selecting an arbitrary file. Existing parent names are resolved when creating a new file, and guest mount aliases use the same ASCII comparison. Results are not cached, so renames and newly created files remain visible. Unicode case folding and PS5 behaviour for case-colliding host entries are unverified; host roots are used as supplied. Explicit native Windows drive and UNC paths bypass guest parent-component validation.. A relinked module's <name>.guest.prx is matched the same way when the original module is not beside it: Borderlands 4 (PPSA01494) loads /app0/prx/libRenoirCore.PS5.prx, which it ships as librenoircore.ps5.prx. A relinked module request that misses both the exact and the case-insensitive <name>.guest.prx takes the sibling whose stem (the name without its last extension, ASCII case ignored) equals the requested stem and that ends with .guest.prx, shortest name first: Unity IL2CPP titles load Media/Modules/<name>.prx and Media/Plugins/<name>.prx, and the relinker names the converted module after the ELF it was built from, <name>.elf.guest.prx. The console's module namespace is assumed to ignore case and the extension; several stem matches pick the shortest name instead of throwing, which is not measured on a console

  • Colons in guest names (libc) - on Windows (NTFS) a guest name containing : is not a plain file name: name:stream opens or creates the alternate data stream stream of the file name. Creating /savedata0/C:file makes an empty file C whose stream file holds the data, and stat of /savedata0/C then reports a regular file. mkdir of a name containing :, such as C: or dir:x, fails with EINVAL. name:stream already behaved this way before drive-letter components were kept under their mount

  • tzname (libc) points at the host's zone names, read at load and again by tzset: the abbreviations a TZ string gives (EST5EDT is EST and EDT on both hosts), but on Windows without TZ the CRT's full names such as GMT Standard Time, which ICU's uprv_tzname (ES-DE in PPSA99169) cannot map to a zone ID. A zone with no daylight-saving time has tzname[1] empty on Windows and equal to tzname[0] on Linux, where FreeBSD leaves three spaces. The console's names are not known

  • Trailing slash on a mount alias (libc) - on Windows a trailing slash after a file name under a mount alias such as /savedata0 is still dropped, so unlink, rename, open and the other path calls act on the file where FreeBSD 11.4's lookup() fails with ENOTDIR; Linux and macOS pass the slash to the host, as for paths outside an alias

  • localtime, mktime (libc) on Windows convert through the CRT's localtime_s and mktime, which accept about 1970 to January 3001 UTC and apply the CRT's daylight-saving rule. A time outside that range is converted a whole number of 400-year Gregorian cycles away, where the calendar repeats, so it gets the rule the CRT applies to that year and not the zone's historical one. On macOS mktime converts through the host's mktime, which refuses every year before 1900. A local time before 1900 is instead solved for directly: the offset localtime_r gives for a candidate time, which follows the zone's history (local mean time before its first transition), is subtracted from the local fields, and the step is repeated until the offset holds. tm_isdst is not consulted there, so a time with tm_isdst set where the zone had no daylight offset is taken as standard time. FreeBSD's time1 would instead shift it by the difference between one of the zone's daylight types and a standard one. gmtime converts every time whose year fits an int on both hosts.

  • strftime and asctime (libc) follow FreeBSD 11 strftime.c and asctime.c with the C locale on every host, not measured on the console; other locales are not implemented. %s follows FreeBSD 9 and 11 and is the local-time mktime of the broken-down time (current FreeBSD uses tm_gmtoff), which is -1 on Windows outside the range above. %Z is tm_zone, or tzname[tm_isdst != 0] when it is null and tm_isdst is not negative. The two-digit year warning FreeBSD writes to stderr when CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS is set is not written

  • wcsftime (libc) follows FreeBSD 11.4's wcsftime.c over this library's wcsrtombs, strftime and mbsrtowcs in the single-byte C locale of none.c, with the guest's 16-bit wchar_t, without console verification. It has the strftime gaps above. A format unit above 255 returns 0 with EILSEQ; an empty result, a result that does not fit and a null buffer return 0 with errno unchanged, discarding the ERANGE or EINVAL that the Windows CRT's strftime sets. A null format or tm throws, where FreeBSD dereferences it (a null tm is read there only when the format has a conversion that needs it)

  • syslog (libc) writes each message to stderr as [syslog:<priority>] <message>, with a newline added when missing, instead of sending it to the console's system log. Priority and facility handling follow FreeBSD's vsyslog: %m expands to the guest errno's message, unknown bits are reported (as priority 0x23, since FreeBSD passes LOG_ERR|LOG_CONS|LOG_PERROR|LOG_PID as the priority) and masked, a missing facility becomes LOG_USER or the facility set by openlog, and errno is preserved. openlog applies its identity and LOG_PID; a null identity retains the previous one, and a valid nonzero facility replaces the default. closelog clears the identity but retains the options and facility, as in FreeBSD 11; when no identity is set, FreeBSD falls back to the program name, which this backend does not emit. LOG_PERROR has no extra effect because stderr is the emulated log sink; LOG_NDELAY/LOG_ODELAY, LOG_NOWAIT and LOG_CONS have no daemon, child or console backend to control. setlogmask follows FreeBSD 11: the process-wide mask defaults to 0xff, zero queries it, and nonzero values replace it; masked messages are skipped before formatting, including the unknown-priority diagnostic. The console's mask behaviour is unverified

  • fwscanf, vfwscanf, wscanf, vwscanf and their _s forms (libc) run the swscanf scanner on characters read with fgetwc, so everything listed for swscanf in this file applies to them. As in the console's libc.prx, a field takes characters while they can still begin a valid item, so a failed field such as 0x for %x stays consumed, and only the character that ended the scan goes back to the stream. That character is pushed back as its UTF-8 bytes with ungetc, because ungetwc takes single bytes only; the Windows C runtime guarantees one byte of push-back, so a non-ASCII lookahead can be lost there. A null stream or format returns EOF; the console checks them only in the _s forms.

  • daemon (libc) throws: forking into the background is not supported. PPSA85300 imports it and fails to load without it. VLC 3.0 calls it only when its daemon option is set (libvlc.c), and the title starts libVLC with --ignore-config and without --daemon (player.cc)

  • _WStoul, _Iswctype, swscanf_s and swscanf (libc) - _WStoul is wcstoul and throws for a base other than 0 or 2 to 36. _Iswctype takes the class numbers of Dinkumware's xwcc.h (1 alnum to 11 xdigit, 12 blank) and throws for any other value, including the FreeBSD masks wctype returns; the numbering is not confirmed on the console's libc.prx. swscanf_s follows C11 7.29.2.2 and K.3.9.1.5, but reads the size argument as unsigned int like sscanf_s where K.3.9.1.5 specifies rsize_t; the console's width is unverified. A runtime-constraint violation (null buffer, format or receiving pointer) only returns EOF, and a too small array gets a null first element as in sscanf_s. A literal mismatch is a matching failure and a field that is only a prefix, such as 0x or 1e, does not convert, as C11 requires; FreeBSD's vfwscanf reports an input failure for the first and converts the second. Integers out of range are truncated from strtoll/strtoull. %p follows FreeBSD 11's vfwscanf.c: a hexadecimal integer with an optional 0x, converted as unsigned and stored as a pointer; a length modifier on it throws, where FreeBSD ignores it. It throws for %S, %C, unknown length modifiers, scanset ranges, %n with suppression or width, and narrow %s, %c or %[ input that the C-locale wcrtomb cannot convert. swscanf runs the same scanner without the size arguments of %s, %c and %[, as FreeBSD's swscanf.c passes its arguments to vswscanf, so everything above except the sizes applies to it; it also keeps the null buffer, format and receiving pointer checks returning EOF, where C leaves those undefined. Neither variant is confirmed on a PS5 title

  • flock (libkernel) on Windows shares one lock state between duplicated descriptors, following FreeBSD 11.4's flock(2). Independent opens have separate states; closing a descriptor or reopening a stream drops only its reference. Lock transitions on one open file description are serialized. The lock is a host LockFileEx lock on one byte at offset 2^63 - 2, which no file reaches, so it excludes other flock calls and leaves reads, writes and mapped views alone, like FreeBSD's advisory locks. Console behavior, signal interruption and concurrent closure of a descriptor while its lock call is pending are unverified.

  • std::_Throw_Cpp_error (libc) follows Dinkumware thread0.cpp (VC12, STL), without console verification. Codes outside 0..6 throw std::out_of_range, the deleting destructor throws, and STREAMS errnos absent from FreeBSD 11.4 errno.h (ENODATA, ENOSR, ENOSTR, ETIME) are omitted. Codes 0..6 throw a 32-byte VC12 system_error with FreeBSD errnos (16, 22, 3, 12, 1, 11, 35), a static generic_category (not _Generic_object), and unverified 40-byte GuestLocale::String messages from syserror.cpp

  • Call tracing (--trace-imports, APS5_TRACE_CALLS) is missing on macOS and records only the entry of a call, not its arguments or result. One process-wide ring keeps the last 65536 calls, so a busy thread pushes the others out; the reporting thread's own calls are listed only while they are still in that ring. Calls are counted per traced target; an import bound to a target already traced under another name is counted under the first name. The thunk saves the integer argument registers and xmm0 to xmm7; wider vector arguments survive only while the recording code avoids AVX, which is untested. Linux has no crash report, so it prints the trace only on SIGABRT (guest or host abort()), not on SIGSEGV; a caller in a module loaded after the last traced import prints without a module name, and a thread aborting while another writes the report waits for it at most 2 seconds

  • _Dtest (libc) follows Dinkumware double classification matching _FDtest: finite values return -1, subnormals -2, zeros 0, infinities 1, and NaNs 2.

  • app0.zar (libc) - a ZArchive beside app0/ is a read-only layer under it: a path missing from app0/ is looked up in the archive, case-insensitively. It is not a console format; titles ship their files in a PFS image instead. Resolving an archive-only path through any call other than the archive-aware libkernel ones throws, so fopen, stat, access, opendir and the rest do not read the archive yet, and opendir of a directory that exists in both places, app0/ itself included, and fdopen of an archived descriptor throw instead of missing its entries or reading the null device. A damaged compressed block throws instead of returning EIO

  • fopen_s (libc) - returns the errno of the failed open, as C11 K.3.5.2.1 leaves the nonzero value open and Microsoft's CRT returns errno; the console's Dinkumware fopen_s is not verified

  • ios_base standard streams (libc) _Addstd queues the stream in the standard stream table of Dinkumware ios.cpp (stdstr, stdopens, _Addstd, _Ios_base_dtor): it takes the first of slots 1 to 7 that is free or already holds the stream, and slot 8 (NSTDSTR) when none is, so slot 8 is shared by every stream past the first seven and registration never fails. It stores the 1-based slot index in the guest IosBase::standardStream (Dinkumware's _Stdstr, where 0 means not a standard stream) and counts the stream objects registered per slot; registering the same stream again reuses its slot and raises its count. Measured on a PS5 (firmware 13.60) from a homebrew payload calling the system libSceLibcInternal export: in a process whose own standard streams already hold slots 1 to 7, twelve distinct streams and a repeated one all get 8, written as a 64-bit value at offset 0x8 with no other byte of the object touched; the assignment of slots 1 to 7 to fresh streams is not observable there and follows VC12. The counts are int, not VC12's char, and the registration and the destructor lock the table where VC12 locks only _Addstd; a slot index past the table throws in the destructor instead of indexing out of bounds. The destructor decrements its slot's count and suppresses the rest of the destruction while the count stays positive, as _Ios_base_dtor does, and the slots of destroyed streams keep their pointer and stay reserved for that stream, as stdstr does. The streams themselves are destroyed by the title's own code as it registers them through __cxa_atexit; when the last reference closes, the unsupported stream storage and callback cleanup is still the throw it was and the locale is released as for a non-standard stream.

  • std::_Pad, _Thrd_join, _Thrd_detach, _Thrd_id (libc) follow Dinkumware V6.00 thr/xthread and thr/threadcall.cpp (crt.vc12 f1d47126), without console verification. _Thrd_id throws because whether the console returns pthread_t or a kernel thread ID and at what width is unknown; _Launch omits _Cnd_do_broadcast_at_thread_exit and calls pthread_create registered by libkernel with default attributes. The 32-byte pad layout (_Go() vtable at 0x0, _Cnd_t at 0x8, _Mtx_t at 0x10, bool _Started at 0x18) is assumed from VC12, unverified. Failing primitives or thread start throw the guest std::system_error; null arguments throw. _Thrd_join returns _Thrd_error (4) on pthread_join failure and truncates the exit code to int; _Thrd_detach returns _Thrd_error (4) on pthread_detach failure and throws when none is registered