31 KiB
31 KiB
libc technical debt
Unknown function info
- _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. - _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 guestCLOCK_REALTIME, normalizes the nanoseconds and sleeps throughnanosleep, retrying onEINTR, so it is a cancellation point; a null xtime or one whose normalization overflows throws. The console's_Thrd_sleep(disassembled from a title'slibc.prx) also takes an optionalxtime*of remaining time and returnsint; here the sleep is never cut short, so it returns 0 and leaves the remaining time untouched. - Guest arena (libc) - the application map area is assumed to end at 0xFC_0000_0000, as
SCE_KERNEL_APP_MAP_AREA_END_ADDRdoes 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 thelibc.prxASTRO BOT (PPSA21564 01.007.000) bundles: its four calls (0x6801, 0x7F46, 0xE75E, 0xEE02) are in themalloc_statsandsceLibcMspaceMallocStatspaths, 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 samelibc.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 - 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_sizein KytyPS5'sMallocReplaceorder) and throws when the table leaves it null, likemalloc_stats_fast - reallocf (libSceLibcInternal, implemented in libc) - follows FreeBSD 11
lib/libc/stdlib/reallocf.con top of this libc'srealloc, not measured on the console: size 0 frees the block and returns null asreallocdoes here, so the block is not freed twice; whenreallocfails because the application allocator returns null or the default heap rejects the size, the block is freed through the same allocator,errnoisENOMEMand null is returned. Other failures (an exhausted guest arena, an unknown pointer) throw, as they do forrealloc - 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 setserrnotoEDOMforacosloutside [-1, 1] where msun only raises the floating-point exception.long doubleis the x87 80-bit format, as Sony'sfrexpluses it (fldt 0x8(%rsp), result inst(0)) - atanl, sinl, cosl, tanl, sinhl, coshl, tanhl, expl, exp2l, logl, log10l and powl (libc) - computed by the host's math library, as
acoslis, not by the console's FreeBSD msun ld80 code: results can differ from the console's in the last bit (glibc'slog10l(1000)is not exactly 3), and on Linux glibc setserrnotoEDOMorERANGEwhere msun only raises the floating-point exception.ldexplandtrunclare exact operations and give the console's result;logbandremaindergive the console's value too, but on Linux glibc setserrno(ERANGEforlogb(0),EDOMforremainderby zero or of an infinity) where msun only raises the floating-point exception - std_execute_once (libc) - assumed to be the plain-named twin of
std::_Execute_once(_ZSt13_Execute_onceRSt9once_flagPFiPvS1_PS1_ES1_, the callee of Dinkumwarecall_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 - Dinkumware mutexes and conditions (libc) use pointer-to-handle arguments, matching the console aerolib signatures reported in #3079. The title's
_Mtx_lockmaps0to_Thrd_success(0),0x8002000bto_Thrd_busy(3), and other failures to_Thrd_error(4); its internal mutex init recognizes recursive flag0x100, 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_unlockand_Cnd_waitreturn 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. - random, srandom, initstate, setstate (libc) follow FreeBSD 11 random.c: the additive feedback generator on a default 128-byte
TYPE_3table,good_randseeding, state sizes rounded down to 8, 32, 64, 128 or 256 bytes, andsetstatereturning the previous state or null for a rear position past the degree.initstatebelow 8 bytes returns null without setting errno, as that file and its manual do (glibc setsEINVAL). 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'slibSceLibcInternaluses this FreeBSD code and not another generator is unverified - 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 and memmove_s (libc) use the C11 K.3.7.1.1 and K.3.7.1.2 size constraints, with RSIZE_MAX taken as SIZE_MAX >> 1 as in memset_s. 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_MAXassumed to beSIZE_MAX >> 1. A runtime-constraint violation only returns a negative value:set_constraint_handler_sis not exported and the console's default handler is unknown. An invalid multibyte%sargument is copied byte by byte instead of being reported as an encoding error, as invswprintf - vsnprintf_s and vsscanf_s (libc) -
vsnprintf_sforwards tovsnprintfsemantics: the C11 K.3.7.1.5 runtime constraints (null buffer,size == 0,%n, null%s) are not checked, sosize == 0returns the would-be length instead of a negative value, and no constraint handler is invoked.vsscanf_sreturns the count of items converted before an undersized%s/%c/%[destination (0 when it is the first), not EOF. Neither is confirmed on a PS5 title - localeconv (libc, re-exported by libSceAmpr) - the 0x90-byte Dinkumware struct (empty monetary strings,
CHAR_MAXfor the 14charfields,decimal_point".",_False/_True"false"/"true") is read from thelibc.prxthat Astro's Playroom ships. It is a fixed C-locale table: whether the console changes it withsetlocaleis not known, and_Frac_grouping,_Frac_sep,_No,_Yesare 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.
ctime_sandasctime_sfollow the Dinkumware 26-byte format withEINVALfor a buffer under 26 bytes, a null pointer or an out-of-range weekday/month;ctime_suses the guest time zone oflocaltime_s, and the console's exact checks are not verified - __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
Functional
- getc_unlocked, putc_unlocked (libc) - byte I/O uses the host unlocked operations; guest EOF/error flags and macro counters follow
FileStream. Linux status queries usefeof_unlocked/ferror_unlockedbecause glibcfeofandferrorcan acquire the stream lock. Windows uses the CRT no-lock byte operations andfeof/ferror, verified under a held stream lock on UCRT. Console behavior and other host CRTs remain unverified. - ftrylockfile (libc) - tries the host CRT stream lock that
flockfile,funlockfileand each locking host stdio call on the stream take, so it returns -1 while another thread holds the stream throughflockfileor is inside such a call, and 0 when it acquires the lock or already owns it (recursively, released by as manyfunlockfilecalls), as FreeBSD 11_ftrylockfiledoes. A libc export holds the lock only for each host call it makes, not across several host calls or while it updates the guestFILEprefix. Linux uses glibcftrylockfile. UCRT has no try variant of_lock_file, so Windows callsTryEnterCriticalSectionon theCRITICAL_SECTIONthat_lock_fileenters, found through the private UCRT stream layout (__crt_stdio_stream_datainucrt/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 - 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.wcsrtombsuses the guest's single-byte C locale throughwcrtomb; other encodings and shift states are not supported - realpath (libc) follows FreeBSD 11's
realpath.cexcept 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, asResolvedoes for every guest path. Through a host linklinkdir -> sub/deep,linkdir/../xgivesxbeside the link, not beside its target. - nan and nanf (libc) return fixed quiet NaN values (
0x7fc00000and0x7ff8000000000000), ignoring the tag argument as observed in the console'slibc.prx(both exports load a global constant viamov rax,[rip+..]; vmovsd xmm0,[rax]; ret); VLC (PPSA85300) stops on nanf at start-up after strsep. - lgamma (libc) returns the host's
lgammabut does not store the sign of Γ(x) in the guest'ssigngam, as FreeBSD 11'se_lgamma.cdoes through__ieee754_lgamma_r, because no guestsigngamis exported. PPSA99004 imports it. Console behaviour is unverified - sinhf (libc) follows the
sinhfof thelibc.prxbundled with PPSA28319 (disassembly): FreeBSD msun'se_sinhf.cfrom before__ldexp_expf, which squaresexpf(|x| / 2)betweenlogf(FLT_MAX)and the overflow threshold, calling msun'sexpm1fandexpf, whose paths for the arguments it passes are inlined here. Past the threshold Sony's error helper setserrnotoERANGEbefore returningx * 1e37; msun itself sets noerrno. 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 systemlibSceLibcInternal, which Ruffle (PPSA68091) and RetroArch (PPSA99169) import while bundling a zero-returnlibc.prx, has the same code is assumed - asinl (libc) follows the
asinlof thelibc.prxbundled with PPSA28319 (disassembly), FreeBSD msun'se_asinl.cwith the ld80invtrig.ccoefficients, which the binary holds as the same 80-bit constants. For |x| > 1, the infinities and NaN, Sony's error helper raisesFE_INVALIDand setserrnotoEDOMbefore returning(x - x) / (x - x), so a NaN argument also setsEDOM; msun itself sets noerrno. 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 word0x37f), but guest threads do not set it: on Windows a thread started by_beginthreadexruns at 53 bits (0x27f), whereasinl(0.75)returns mantissa0xd91a98ae3406e000instead of0xd91a98ae3406e041. The systemlibSceLibcInternalthat RetroArch (PPSA99169) imports is assumed to have the same code - 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
userfaultfdwrite-protect orMEM_WRITE_WATCH. The protection is real, so:- a system call that writes into a protected page fails with
EFAULTinstead of faulting.GuestArena::HostWritelifts the protection for the calls it brackets (file reads and socket receives); a host call that writes guest memory without it (clock_gettimeand 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 askGuestWriteWatchProtectionfirst, 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.
- a system call that writes into a protected page fails with
- 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 bysceKernelSync - 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
FlushFileBuffersneeds 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 fopenstreams (libc) on Windows share delete access, like the descriptorssceKernelOpenopens, so a file the guest holds open can be removed or renamed. A stream reopened withfreopenstill goes through_wfreopen, which does not share delete access- The
_Unwind_Contextthat_Unwind_Backtracepasses to its callback starts with a table laid out like LLVM libunwind'sAbstractUnwindCursorvirtual 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, withUNW_REG_IPandUNW_REG_SP),validFloatReg(always false) andisSignalFrameare answered; the other slots throw. The layout follows libunwind'sUnwindCursor.hpp, not the console's ownlibSceLibcInternal, which is not checked - The Windows libc exception unwinder steps through frames of modules without the project's
.ehmetaand.ehframtables (system DLLs, and WinLibs GCC 15.2'slibstdc++-6.dllandlibwinpthread-1.dll, whose.eh_framesections are empty) with their PE unwind data throughRtlVirtualUnwind, 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_seh0inlibstdc++-6.dllor a system DLL function likeqsortinucrtbase.dll(Windows 10 22H2), and the exception is then reported as unhandled - The libc SSE4a trap emulation on Windows is superseded by
--to-inteland remains only until the relinked title has been verified without it. - getifaddrs (libc) lists the host's IPv4 and IPv6 addresses in FreeBSD's
ifaddrsand sockaddr layouts. FreeBSD's per-interface AF_LINK entries (MAC, index, MTU) and theirif_dataare not produced, so interfaces without an address are omitted. Names are the host's (Linuxlo,eth0; Windows NDIS names such asethernet_32769, orif<index>when one does not fit 15 bytes), not the console's. A Linux point-to-point address without a peer losesIFF_POINTOPOINT, and an IPv4 broadcast address the host omits is computed from the netmask. On Windows the flags are onlyIFF_UP,IFF_RUNNING,IFF_LOOPBACK,IFF_BROADCASTandIFF_MULTICAST, derived from the adapter: PPP and tunnel adapters get neitherIFF_BROADCASTnorIFF_POINTOPOINTbecause their peer address is not reported, and an address whose prefix length the host reports as unspecified gets a host mask. - gethostname (libc) reports the fixed guest name
AnyPS5without querying the host machine name. Buffer copying, null queries andENAMETOOLONGfollow FreeBSD gethostname; the console's configurable hostname and its integration withsysctland name resolution are unverified and not implemented. - 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 tostderrbefore returning, which is not done here. Conversion uses the C locale ofwcrtomb(MB_CUR_MAX1), so the path where a multibyte character does not fit the remaining space is not reached - 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 or%m, so a narrow%pand a NaN there still follow the host (glibc prints a null pointer as(nil), where FreeBSD prints0x0, and a NaN with its sign,-nan, and with the+and space flags, where FreeBSD printsnan); argument positions above 99 (FreeBSD'sNL_ARGMAX) throw when a format requires the guest formatter - collate::_Getcat (libc) builds the facet for the C locale only and throws for any other locale name. The console's
libc.prxstores 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_transformthrows for input with embedded null characters, which the console drops depending on the output buffer size. Its result capacity (length | 15) and the over-aligned storage of results of 4080 characters or more follow the console'slibc.prxcode and are not checked on hardware. - The scanf family (libc), the
_sforms included, on Linux throws for the 16-bit wide conversions (%ls,%lc,%l[,%S,%C): glibc would store a 32-bitwchar_tper character into the guest's 16-bit buffer. Windows hands%ls,%lcand%l[to the host, whosewchar_tis 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 returnsEOFthere. On Windows the host stops a%x,%X,%ior%pafter thexof a0xthat no hex digit follows (or that the width cuts off), and a float conversion (%e,%f,%g,%a) after theeof an exponent with no digits (1e), where C17 and FreeBSD stop before thexor thee - Guest path resolution falls back to ASCII case-insensitive component lookup, following shadPS4's host filesystem. PPSA03747 requests
Skyrim.INI,Data/Videoand 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. - localtime, mktime (libc) on Windows convert through the CRT's
localtime_sandmktime, 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.gmtimeconverts every time whose year fits aninton both hosts. - strftime (libc) expands FreeBSD's
%k,%l,%s,%vand%+itself, ignoring a leadingEorOas FreeBSD does. Unknown conversions, repeated modifiers and incomplete conversions emit literals as in FreeBSD 11; recognized conversions otherwise go to the host'sstrftime. On Windows the%-,%_and%0padding flags still produce nothing, including on the five expanded conversions (%-k,%_kand%0kgo to the host), and a standalone%zor%Zuses the host's zone instead of the broken-down time'stm_zone.%sfollows FreeBSD 9 and 11 and is the local-timemktimeof the broken-down time (current FreeBSD usestm_gmtoff), which is -1 on Windows outside the range above.%+is the C locale's%a %b %e %H:%M:%S %Z %Ywith%Ztaken fromtm_zone, or fromtzname[tm_isdst != 0]when it is null. - syslog (libc) writes each message to
stderras[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'svsyslog:%mexpands to the guest errno's message, unknown bits are reported (as priority 0x23, since FreeBSD passesLOG_ERR|LOG_CONS|LOG_PERROR|LOG_PIDas the priority) and masked, a missing facility becomesLOG_USER, and errno is preserved.setlogmaskfollows 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.openlogandcloselogare not implemented, so their identity and options are not applied - 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
daemonoption is set (libvlc.c), and the title starts libVLC with--ignore-configand without--daemon(player.cc) - _WStoul, _Iswctype and swscanf_s (libc) -
_WStouliswcstouland throws for a base other than 0 or 2 to 36._Iswctypetakes the class numbers of Dinkumware's xwcc.h (1alnumto 11xdigit, 12blank) and throws for any other value, including the FreeBSD maskswctypereturns; the numbering is not confirmed on the console'slibc.prx.swscanf_sfollows C11 7.29.2.2 and K.3.9.1.5, but reads the size argument asunsigned intlikesscanf_swhere K.3.9.1.5 specifiesrsize_t; the console's width is unverified. A runtime-constraint violation (null buffer, format or receiving pointer) only returnsEOF, and a too small array gets a null first element as insscanf_s. A literal mismatch is a matching failure and a field that is only a prefix, such as0xor1e, does not convert, as C11 requires; FreeBSD'svfwscanfreports an input failure for the first and converts the second. Integers out of range are truncated fromstrtoll/strtoull. It throws for%p,%S,%C, unknown length modifiers, scanset ranges,%nwith suppression or width, and narrow%s,%cor%[input that the C-localewcrtombcannot convert - 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 host's LockFileEx locks also restrict ordinary reads and writes through other handles, unlike FreeBSD's advisory locks, and do not restrict mapped views. Console behavior, signal interruption and concurrent closure of a descriptor while its lock call is pending are unverified.
- std::_Throw_Cpp_error (libSceAmpr) follows Dinkumware's
thread0.cppas shipped with Visual C++ 2013 and in microsoft/STL, without console verification._Cpp_errorcodes 0 to 6 throw a gueststd::system_errorwhoseerror_codeholds the FreeBSD errno of theerrcvalue (EBUSY 16, EINVAL 22, ESRCH 3, ENOMEM 12, EPERM 1, EDEADLK 11, EAGAIN 35) and the generic category, and whosewhat()is the table message,": "and the generic category's message for that errno, taken to be the same text as in VC12'ssyserror.cpp; another code throwsstd::out_of_rangeon the host instead of reading past the table. The guestsystem_erroris taken to be VC12's: the 16-byteruntime_errorfollowed byerror_code {int, const error_category*}, 32 bytes, so its copy constructor and assignment copy the code too. The generic category is a static libc object with VC12'serror_categoryvirtual order (destructors,name,message,default_error_condition, the twoequivalent), not the console's_Error_objects<int>::_Generic_object, and nogeneric_category()export returns it, so it compares unequal to a title's owngeneric_category(). Itsmessagethrows, as the gueststd::stringlayout is not modelled, and so does its deleting destructor