Commit Graph
221 Commits
Author SHA1 Message Date
TideOPL b14d844855 perf(libc): remember ranges proven collectable until the mappings change 2026-10-14 09:43:40 +01:00
TideOPL 2af88da01f perf(libc): walk the shared views of a write collect in order 2026-10-14 09:43:40 +01:00
Dean Galvin 8cc20401a2 fix(libc): forget a released section's pages even while views remain on Windows
WindowsMappings keys each physical page's state (moved into the section,
saved bytes of an unmapped resident page) by the section handle value.
sceKernelReleaseDirectMemory frees the backing before it unmaps the views,
so ForgetSection ran while those views still existed and kept their slots.
After the views went, the slots stayed under a handle value that Windows
hands out again, and the next direct allocation whose section got that
value started with the old page state: its first mapping went to the
section instead of private memory, or received the old allocation's bytes.

Views now share ownership of their slot, ForgetSection drops every slot
of the section at once, and JIT shared memory forgets its section before
closing it too.
2026-10-14 21:11:38 +13:00
Dean 6546a33944 Merge pull request #4728 from Ondra97/codex/fix-fdopen-access-mode
fix(libc): reject incompatible fdopen access modes on Windows
2026-10-14 19:06:55 +13:00
Dean 22beaa2e7a Merge pull request #4628 from mugurc/fix/asm-macho-symbols
fix(libs): spell inline assembly symbols for Mach-O hosts
2026-10-14 17:26:02 +13:00
thibautandClaude Opus 5.5 71da166a18 fix(libc): keep resident mappings to direct memory backings on Windows
Map made every singly mapped section page private resident memory. A JIT
or shm_open section is file backed and committed: its bytes live in the
file, and its alias descriptors map duplicated handles, which the physical
page slots do not match, so a resident copy diverged from the file, and
committing its view on a demote failed with access denied
(guest_jit_shared_memory). Only direct memory backings, mapped through the
new GuestArenaMapDirect, now become resident; other sections keep main's
shared views. Shared pages are committed only while their section page is
reserved, as on main.

The socket host write tests map their direct memory a second time, so the
write-protected shared path they check is still the one they exercise.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01V1W4jdNSKTV7xwuS6xQPJV
2026-10-14 02:39:10 +02:00
DotDebianandClaude Opus 5.5 d9852df2a2 fix(libc): track writes to singly mapped direct memory without faults on Windows
A direct memory page mapped at one guest address now lives in private
MEM_WRITE_WATCH memory at that address, and its writes are collected with
GetWriteWatch like flexible memory: no fault is raised. A page mapped at a
second address moves its whole resident run into the section, behind a
read-only fence while it is copied, and every address becomes a view of it,
tracked by write protection. Unmapped resident pages that are not blank are
kept in a private reservation until they are mapped again, not in the
section.

Ported onto main's 4 KiB subpage tracking of shared views: resident runs
keep a per-page collected bit and enter the fresh ranges, so Fresh and
Armed collections select them like shared views; a demoted run carries its
collected bits into the views and reports every page once. MapAlias throws
through refuse() when a resident run extends outside the aliased range, and
the caller keeps main's fallback.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01V1W4jdNSKTV7xwuS6xQPJV
2026-10-14 02:39:10 +02:00
Dean 7a0c838ca4 Merge pull request #4875 from nikowolf123/perf/libc-write-watch-capacity
perf(libc): size each write-watch query to the region it covers
2026-10-14 12:12:10 +13:00
Dean c7601b7971 Merge pull request #4164 from hamb3r/feat/libc-unity-il2cpp-runtime
feat(libc): add missing Unity IL2CPP runtime imports and Dinkumware helpers
2026-10-14 10:45:32 +13:00
Dean 8d98e8851f Merge pull request #4474 from JJenkx/fix/multisample-target-retirement
fix(agc): retire fully unmapped multisample targets
2026-10-14 10:23:12 +13:00
hamb3r 8652dbe35b feat(libc): add missing Unity IL2CPP runtime imports and Dinkumware helpers
Add missing libc.prx symbols required by Unity IL2CPP runtime:
- Implement _Dtest double classification in Math.cpp
- Implement _Inf double constant in Math.cpp
- Implement sceLibcBacktraceGetBufferSize and sceLibcBacktraceSelf in Backtrace.cpp
- Implement _Cnd_signal in Threads.cpp
- Implement _ZNSt8ios_base7_AddstdEPS_ in RuntimeSupport.cpp
- Implement __pointer_type_info and __function_type_info vtables in TypeInfo.cpp
- Remove duplicate stubs from libSceAmpr/Export.cpp
- Add unit tests in GuestMath.cpp and GuestLibcThreads.cpp
- Document unverified behaviors in docs/dev/debt/libc.md
2026-10-13 19:01:30 +03:00
Adrià c496fd530f Merge pull request #4546 from codingreapermaps/feat/libc-call-trace-runtime
feat(libc): record traced import calls on Windows
2026-10-13 16:51:55 +02:00
JJenkx f7fd7569fa fix(agc): retire fully unmapped multisample targets 2026-10-13 10:37:07 -04:00
Adrià 9dac817b19 Merge pull request #4548 from coltken95/fix/libc-printf-nan
fix(libc): print NaN without a sign in the guest printf formatter
2026-10-13 16:31:34 +02:00
codingreapermapsandClaude Opus 5.5 3953a8d49e feat(libc): record traced import calls on Windows
TraceImport_nid_no_patch wraps a function of a library named in
APS5_TRACE_CALLS in a thunk that records the import, the thread and
the caller in a ring and counts the call, then jumps to the function.
One target keeps one thunk, so function pointers compare equal. The
crash and abort reports print the last 64 calls and the 32 most called
imports. Nothing calls TraceImport_nid_no_patch yet.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 16:13:12 +02:00
NikoandClaude Opus 5.5 44767a7b97 perf(libc): size each write-watch query to the region it covers
WindowsMappings::Collect handed GetWriteWatch the whole remaining output
capacity for every private region it walked. The call's cost follows that
capacity, not the size of the range: on Windows 11 (26200) a query of one
16 KiB region took 0.35 us with room for 4 entries and 6.5 us with room
for the 65536 the write tracker passes. Flexible memory is committed in
16 KiB allocations, one region each, so a resetting walk over 33 MiB of it
took 14.7 ms, 5.8 times a memcmp of the same bytes.

Each query now gets room for the pages its region spans, which is the most
it can report, so every walk returns the same pages; the 33 MiB walk takes
1.23 ms. guest_memory checks that a walk over eight 16 KiB allocations
reports every stored page once, in order, with room for 32, 5 and 1 pages.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 13:40:33 +02:00
Dean 9fa50827dd Merge pull request #4532 from DavidSamir/fix/libc-rename-replace-open-destination-windows
fix(libc): replace an open or read-only destination in rename on Windows
2026-10-14 00:38:14 +13:00
Mouad-Scriptz-0andClaude Fable 5.1 df3d027ebe perf(libkernel): find guest allocations and direct mappings by address
sceKernelVirtualQuery, sceKernelMunmap, sceKernelReleaseDirectMemory and the
reservation paths walked a copy of the whole allocation registry or of every
direct mapping under one lock on each call. The registry now answers lookups
from its ordered map, applies range replacements as patches instead of
rebuilding the map, and direct mappings carry an index by physical offset so a
release finds only the mappings it covers. Minecraft (PPSA17221) holds about
7,000 direct mappings and spent its world generation in these walks.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-10-13 23:11:32 +13:00
Dean 2890f95494 Merge pull request #5025 from 0xRickx/fix/libkernel-sys-rename-import
fix(libkernel_sys): reach rename through a _nid_no_patch export
2026-10-13 22:37:36 +13:00
Dean 90285be0af Merge pull request #4367 from MarcZX100/codex/carry-windows-alias-4260
fix(libc): alias non-contiguous shared guest views on Windows
2026-10-13 22:00:55 +13:00
RickandClaude Opus 5.5 9ceb6d144d fix(libkernel_sys): reach rename through a _nid_no_patch export
renameat (2a8e7452) calls rename_nid_postfix when both paths are
AT_FDCWD-relative or absolute. Rename.cpp is built into both libc and
libkernel, and on Windows libkernel_sys bound that import to libkernel,
which comes first on its link line. The NID patcher then rewrote the
import to rename's NID, which only libc exports, as libkernel keeps its
copy under the plain name. So libkernel_sys.prx fails to load with error
127 on Windows, and with it every title that imports it. Linux resolves
the symbol globally and was not affected.

rename's body is now RenameGuestPath_nid_no_patch, which both libraries
export under that name, and renameat calls it, the way libkernel_sys
already reaches libkernel's other helpers. rename_nid_postfix forwards
to it, so its behaviour is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-13 10:48:14 +02:00
Claude 13444c82e9 fix(libc): print NaN without a sign in the guest printf formatter
The guest formatter handed NaN to the host's snprintf, which prints
-nan for a negative NaN and +nan or " nan" with the + and space flags.
FreeBSD 11's vfprintf clears the sign of every NaN and prints nan (NAN
for the upper-case conversions), keeping the width and the - flag and
ignoring 0. The formatter now does the same for a, e, f and g and
their long double forms.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VZWLdTjf1S57iLgn2rcaFb
2026-10-13 08:26:41 +00:00
Dean Galvin 3a9cc63683 fix(libc): hand out the unwind cursor table through a _nid_no_patch export
_Unwind_Context took its cursor table from LibcUnwind::CursorSlots, a C++
data symbol of libc. On Windows LibcUnwind::Frame holds an _Unwind_Context,
so libSceAgcDriver, which builds frames for its worker sampler, imported
_ZN10LibcUnwind11CursorSlotsE from libc.prx. The NID patcher renames that
export in the patched libc and leaves the import alone, so the patched
libSceAgcDriver, libSceAgc and libSceVideoOut failed to load with error 127
and patched_libraries failed. The table is now file-local and the context
reads it through UnwindCursorSlots_nid_no_patch, which keeps its name on
both sides.
2026-10-13 21:09:26 +13:00
Adrià 6d7b873702 Merge pull request #4945 from nicolasfalesy/fix/libc-unwind-backtrace-cursor
fix(libc): let a title's own libunwind read _Unwind_Backtrace frames
2026-10-13 09:36:45 +02:00
Angel21PC 2d1cc82bce fix(libc): alias non-contiguous shared guest views on Windows
On Windows, a host import of shared guest memory goes through a
read-write alias from WindowsMappings::MapAlias. The alias was a single
MapViewOfFile3 of one section, so MapAlias threw when the views of the
range were not one contiguous run of that section. One
sceKernelMapDirectMemory over two adjacent direct memory allocations
gives such a range, as each allocation has its own section, and so do
pages remapped in place to another physical order.

Such ranges are now aliased run by run: a placeholder reservation is
split at each run of consecutive pages of one section, and each run
maps that section at its offset. UnmapAlias unmaps every run and forgets
the alias even when one unmap fails. A contiguous range keeps the single
view, and a range with a page that is not a shared view is still
refused.

guest_memory maps two adjacent direct memory allocations of two pages
and one page with one call, and two pages in reverse physical order.
It reads and writes every page through the alias. The first test also
checks that the alias has one view per run and that unmapping it frees
its pages. A range whose second page is flexible memory is still
refused.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
(cherry picked from commit fc6868d8b6)
2026-10-13 07:31:17 +00:00
Nicolas FalesyandClaude Opus 5.5 db3ec02c54 fix(libc): let a title's own libunwind read _Unwind_Backtrace frames
Homebrew built with the ps5-payload-dev SDK links LLVM libunwind
statically but imports _Unwind_Backtrace from libSceLibcInternal. Its
trace callback then reads each frame with its own _Unwind_GetIP, which
treats the context as a libunwind cursor and calls through the virtual
table in its first word. AnyPS5's context started with the saved RAX, so
the call jumped to a register value and the title crashed on its error
path (vkQuake, PPSA99010, without its game data). The context now starts
with a table in AbstractUnwindCursor's slot order that answers register
reads and writes, validFloatReg and isSignalFrame; other slots throw.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EvDbpwUPTa78oCAJqe7m9K
2026-10-13 01:55:14 -04:00
umangandClaude Opus 5.5 b24e1cc46f perf(agc): return from fresh Windows collection when nothing is fresh
MarkWritten collects the fresh portions of the written blocks before
each GPU stamp. On Windows that walked every shared view of those
blocks twice under the mappings mutex even when all of them had been
collected before. Keep the never-collected portions as address ranges,
as the Linux tracker keeps _fresh, and return after one lookup when
none overlaps the request.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 10:37:27 +05:30
umang b14d087cd4 fix(agc): defer write tracking for fresh Windows shared imports 2026-10-13 10:37:27 +05:30
Dean 52edeeda5d Merge pull request #4698 from Nesyth/perf/libc-shared-lease-snapshot
perf(libc): share one snapshot of the registered guest ranges between leases until the registry changes
2026-10-13 17:53:47 +13:00
Dean 4fe3123627 Merge pull request #4640 from MarcZX100/codex/win-host-standard-handles-4631
fix(libc): preserve host standard handles on Windows redirects
2026-10-13 17:49:02 +13:00
Dean 88f48fddef Merge pull request #4688 from eliyarson/fix/kernel-stat-relinked-prx
fix(libkernel): resolve original .prx names to relinked .guest.prx in stat, open and reachability
2026-10-13 17:48:42 +13:00
Dean cc55e1410a Merge pull request #4661 from fdfawef324/fix/libc-shared-write-pages
fix(libc): track writes to shared guest memory per 4 KiB page on Windows
2026-10-13 17:46:44 +13:00
Ondra97 f2120a6458 fix(libc): reject incompatible fdopen access modes on Windows 2026-10-13 05:30:05 +02:00
David 69b9d186db fix(libc): replace an open or read-only destination in rename on Windows 2026-10-13 05:58:01 +04:00
fdfawef324andClaude Opus 5.5 1eb13fdf02 fix(libc): track writes to shared guest memory per 4 KiB page on Windows
Writes into shared (direct) guest memory on Windows are caught by write
protecting each 16 KiB view and taking the fault. The fault reopened
and invalidated the whole view, so a collect reported all four 4 KiB
pages of a view whenever any byte of it was written, including pages
nobody wrote. A GPU surface that ends or starts inside such a view
then had its edge pages taken for CPU writes whenever the title wrote
the memory next to it, and the storage write-back kept the guest's
stale bytes there instead of the GPU's results.

Each view now keeps a write generation, a seen generation and the
write protection per 4 KiB page. A fault invalidates and reopens only
its page, a host write only the pages it covers, and a collect reports
and re-arms only the pages it covers. VirtualProtect is called once
per contiguous run of pages whose protection changes, so arming or
pinning a view takes one call when those pages are contiguous and two
when they are not. A guest store that fills a whole view takes up to
four faults instead of one.

A collect does not settle a page that it cannot write protect because
a host write is open on one of the page's views; it reports the page
again until a collect can arm it. main settled such a page and relied
on the end of the host write invalidating the whole view, so a guest
store into it during the host write was reported only after the host
write ended. With only the written pages invalidated there, the store
would not be reported at all.

A collect that runs out of room partway through a view now settles the
pages it already reported there and nothing after them; before, it
settled none of them and reported them again at the next collect.

CheckSharedWritePages writes one byte into the second 4 KiB page of a
view and requires a collect of the view's last two pages to report
nothing; main reports both. It also checks a store across two pages
and a file read into one page. CheckSharedWritesDuringHostWrite stores
into a fault-opened page of a view after a collect while a host write
is open on another page of it, and requires every collect of the view
and of its alias to report that page until the host write has ended.
agc_shared_write_partial_collect resets the two interior pages of a
view after writes to all four and requires the outer two to stay
pending. The existing shared-memory checks in guest_memory,
agc_shared_write_partial_collect and guest_socket_host_write (the
recvfrom sender outputs) now expect the pages written instead of the
whole view.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-12 18:03:53 -04:00
marshmll 438d410824 fix(libc): keep host diagnostics on stderr when a guest redirects it on Linux
On Linux a guest stderr redirect also moves the host's: guest
descriptors are host descriptors, and the guest stderr stream wraps the
host stderr FILE. After dup2(fd, 2), or freopen(path, mode, stderr) with
full buffering, the libc terminate report, the host fprintf(stderr)
diagnostics and libstdc++'s terminate message go into the title's pipe
or file, and abort() discards what is still buffered. RetroArch
(PPSA99169) reopens stderr onto app0/trace.txt with full buffering, so
its stop on main left no message anywhere.

Before the first guest redirect of descriptor 2 (libkernel dup2, libc
freopen of a stream on descriptor 2), keep a close-on-exec duplicate of
the host descriptor 2 and point the host stderr at an unbuffered stream
on it. The guest stream keeps its own FILE and follows the redirect.
The libc terminate report writes to the kept descriptor with write()
instead of the stderr stream, and libkernel's sceKernelDebugOutText uses
the same kept descriptor.
2026-10-12 19:00:08 -03:00
David c22654102b fix(libc): keep the host standard handles when a guest replaces descriptor 0, 1 or 2 on Windows 2026-10-12 21:23:00 +00:00
Adrià 28c9dae73d Merge pull request #4569 from pipe1os/feat/libkernel-fdopendir
feat(libSceLibcInternal): implement fdopendir
2026-10-12 23:00:17 +02:00
Felipe Arce f3b8ef7abf feat(libSceLibcInternal): implement fdopendir 2026-10-12 16:23:42 -03:00
AlxcBTWandClaude Opus 5.5 428963d3b3 feat(agc): write the worker sampler's stacks as collapsed stacks
APS5_SAMPLE_WORKER=<file> now also writes <file stem>.collapsed.txt: one
"frame;...;frame count" line per distinct stack of queue worker 0, root
first, frames as module+0xRVA, which speedscope.app and flamegraph.pl
open. Walks go up to 64 frames, and nothing allocates while the worker is
suspended.

The libraries are built with -fno-asynchronous-unwind-tables, so most of
their functions have no .pdata entry, only a DWARF record in .ehfram, and a
walk ended at the first such frame. The worker's walk now steps those
frames through their record, with the CIE/FDE decoding and the call frame
instructions of libc's unwinder, which move from Unwind.cpp into its
header unchanged. A module is indexed after the worker resumes, the first
time a walk meets it, and every register the step restores is read inside
the worker's stack. The process sampler (APS5_SAMPLE_THREADS) keeps its 12
frames through .pdata.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-12 21:23:11 +02:00
Nesyth 78f8df053f perf(libc): share one snapshot of the registered guest ranges between leases until the registry changes 2026-10-12 21:07:13 +02:00
Eli Yarson f80a2f5535 fix(libkernel): resolve original .prx names to relinked .guest.prx in stat, open and reachability
Titles check for a plugin with sceKernelStat before loading it through the engine path. The relinker emits only X.prx.guest.prx, so the check failed and PSNCore started with the raw (0, NULL) arguments. Share the dlopen mapping with sceKernelStat, read-only sceKernelOpen and sceKernelCheckReachability when the original name is absent.
2026-10-12 16:02:14 -03:00
Dean 3b5c96377d Merge pull request #4516 from Ondra97/codex/file-lock-lifetime
fix(libs): share Windows flock state between duplicated descriptors
2026-10-13 07:30:48 +13:00
Dean d4ccf91e03 Merge pull request #4462 from milescoviello/carry/libc-umodti3-4460
feat(libc): implement __umodti3
2026-10-13 07:27:06 +13:00
Dean bde332a59c Merge pull request #4353 from milescoviello/fix/printf-errno-message
fix(libc): print the guest errno message for %m in the printf family
2026-10-13 07:18:29 +13:00
mugurc f818d5e225 fix(libs): spell inline assembly symbols for Mach-O hosts
C symbols in Mach-O carry a leading underscore, and its assembler has no
.type or .size directives. APS5_ASM_SYMBOL names a symbol the way the
assembler of the host expects it, APS5_ASM_FUNCTION_END closes a function
(.size on ELF, nothing elsewhere), and APS5_ASM_FUNCTION defines a global
function on macOS.

The fiber stack switch, the signal mask helpers, getcontext and their tests
use them instead of writing the ELF spelling. The assembly they produce on
Linux and Windows is the same as before.
2026-10-12 19:57:33 +03:00
Ondra97 bd842bc7d2 fix(libs): share Windows flock state between duplicated descriptors 2026-10-12 16:03:47 +02:00
screammandClaude Fable 5.1 23ac439214 feat(libc): implement __umodti3
Kart PS5 (PPSA99611, Phi1ow/mkwii-ps5 v1.0.0-rc1) stops on it right
after __divti3. The related open PR #4037 reports: "with this change it
passes `__divti3` and stops at `ELSr5qm4K1M` (`__umodti3`)".
PS5CEMU-HAR (PPSA99360) imports it too: tools/import_audit.py lists
ELSr5qm4K1M __umodti3 as absent from libSceLibcInternal.prx (registry
index 359), so the predicted stop is
"FAIL: unresolved ELF import ELSr5qm4K1M from libSceLibcInternal.prx".
libSceLibcInternal imports fall back to libc.prx, so the export lives in
libc beside __udivti3. compute_nid and aerolib (zecoxao/sce_symbols
2883963a) both give ELSr5qm4K1M = __umodti3.

__umodti3 is compiler-rt's lib/builtins/umodti3.c (llvm-project
0ee439b705e82a4fe20e266bc8fea96d0e60e1ec): the remainder that
__udivmodti4 leaves. The restoring division of SonyUdivti3 already
computes it, so the loop moves to SonyUdivmod128 and both helpers use
it. A zero divisor reaches a 64-bit divide in __udivmodti4 and traps on
x86-64; this throws instead, as __udivti3 does, and names the dividend.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-10-12 12:26:15 +00:00
Dean 029c1bc17d Merge pull request #3926 from MarcZX100/codex/carry-fclose-error
fix(libc): return fclose failures to callers
2026-10-13 01:24:43 +13:00
Dean baee04e810 Merge pull request #4037 from Nixitov/feat/libc-divti3
feat(libc): implement __divti3
2026-10-13 01:20:26 +13:00