Commit Graph

114 Commits

Author SHA1 Message Date
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
Yunus Shaikh 56d35172f5 fix(libkernel): accept flag 0x8000 in sceKernelMapFlexibleMemory
The libc.prx heap that Animal Well (PPSA22520 01.000.011, SDK 9.04)
bundles maps each arena with sceKernelMapFlexibleMemory(&addr, len, 3,
0x8000). It threw "Unsupported memory mapping flags 0x8000" before the
first frame. The flag is ignored as sceKernelMapNamedFlexibleMemoryInternal
already does; other unknown flags still throw.
2026-10-12 15:38:48 -07:00
fdfawef324 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 <noreply@anthropic.com>
2026-10-12 18:03:53 -04:00
babyhuey ff56dadc0a feat(libkernel): export sceKernelInternalMemoryGetAvailableSize 2026-10-12 17:18:53 -04:00
Adrià b5b33511f7
Merge pull request #4710 from babyhuey/feat/libkernel-scekernelmmap
feat(libkernel): export sceKernelMmap
2026-10-12 23:15:49 +02:00
xXPBSXx d283d484a2 fix(libkernel): answer sceKernelGetPageTableStats
PPSA30601 (Resonance) stops at the throwing stub. The four int* arguments follow
OpenOrbis' libkernel.h, the Orbital type database and SharpProspero; the values
follow KytyPS5 (2048 entries of 2 MiB per side) without its accounting of used
entries, listed in TechnicalDebt.md. A null pointer returns EFAULT.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 22:54:27 +02:00
babyhuey db3726a868 feat(libkernel): export sceKernelMmap 2026-10-12 15:34:17 -04:00
Dean 5a031d49e8
Merge pull request #4324 from abhinav29102005/feat/libkernel-memory-pool-commit-type
fix(libkernel): propagate memory type in sceKernelMemoryPoolCommit and batch commit
2026-10-12 23:53:02 +13:00
Dean c390d81552
Merge pull request #4326 from winstrex/feat/libkernel-sceKernelEnableDmemAliasing
feat(libkernel): implement sceKernelEnableDmemAliasing
2026-10-12 23:50:39 +13:00
Dean 7cb5238d53
Merge pull request #4281 from eljackjoestar-cmd/codex/reserve-direct-memory
perf(libkernel): commit Windows direct backing when mapped
2026-10-12 23:49:42 +13:00
abhinav29102005 99a39859db test(libkernel): assert memory type propagation from batch commit entry 2026-10-12 11:34:03 +05:30
abhinav29102005 4f947dab16 test(libkernel): dispatch memory pool batch op 1 commit entry in test 2026-10-12 11:33:47 +05:30
abhinav29102005 ed3ca36b54 test(libkernel): construct batch commit entry for memory pool test 2026-10-12 11:33:19 +05:30
abhinav29102005 e106b3ba9b test(libkernel): assert neighbor page isolation after pool commit 2026-10-12 11:32:55 +05:30
abhinav29102005 f608965b47 test(libkernel): assert direct memory type update after pool commit 2026-10-12 11:32:37 +05:30
abhinav29102005 e04347ca60 test(libkernel): call sceKernelMemoryPoolCommit in direct memory test 2026-10-12 11:32:24 +05:30
abhinav29102005 899e7ddd0a test(libkernel): declare sceKernelMemoryPoolCommit in guest memory test harness 2026-10-12 11:32:07 +05:30
winstrex 749541f653 feat(libkernel): implement sceKernelEnableDmemAliasing
Direct memory can already be mapped more than once, so there is no
aliasing to enable and the call returns 0. ProsperoEden (PPSA99008)
imports it and calls it before mapping the same direct memory again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 02:40:10 -03:00
Dean Galvin 5b7cb8d126 perf(libkernel): find direct mappings that share physical memory through an index 2026-10-12 18:29:46 +13:00
Dean 5d7be6bdc8
Merge pull request #4003 from zmeizy/claude/memory-pool-batch-type-protect
fix(libkernel): apply the memory type in sceKernelMemoryPoolBatch typ…
2026-10-12 18:20:05 +13:00
Dean 35ef0b7377
Merge pull request #3937 from hansjm10/fix/libkernel-munmap-unmapped
fix(libkernel): let munmap succeed over a range with nothing mapped
2026-10-12 17:28:36 +13:00
eljackjoestar-cmd 9873fae1ef perf(libkernel): commit Windows direct backing when mapped 2026-10-12 07:59:34 +04:00
Dean Galvin 5b1868d23d fix(libkernel): reject mprotect of host memory that is not a guest thread stack
The Windows path from #3788 changed the protection of any committed private host region outside the guest registry, including a range crossing a guest stack's lower bound. Ranges inside a live guest thread stack keep the path from #2982.
2026-10-12 12:50:24 +13:00
Adrià dc70a66cbd
Merge pull request #3788 from xXPBSXx/fix/libkernel-mprotect-host-memory-v3
fix(libkernel): allow sceKernelMprotect on committed host memory outside the guest registry
2026-10-12 00:01:51 +02:00
Adrià 32f6414314
Merge pull request #3713 from SleglJan/fix/libkernel-flexible-module-charge
fix(libkernel): count guest module segments against the flexible memory budget
2026-10-11 23:54:59 +02:00
Claude 29af23e718
fix(libkernel): apply the memory type in sceKernelMemoryPoolBatch type-protect entries
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ha6Tsy62AFUyp9a5Mf7vjA
2026-10-11 20:58:50 +00:00
xXPBSXx a68bc9dfdc fix(libkernel): allow sceKernelMprotect on committed host memory outside the guest registry
Thread stacks are host-private memory; the call threw instead of setting the native protection.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 18:35:35 +02:00
fdfawef324 9d96752fcd fix(libkernel): let munmap succeed over a range with nothing mapped
munmap and sceKernelMunmap over a range in which no page is mapped
failed: munmap returned EINVAL and sceKernelMunmap let the registry's
exception escape. Both now return 0. The console returns 0 for
sceKernelMunmap of a range it just unmapped and of a never-mapped
address, and EINVAL only for a zero length, as measured by prosper's
console oracle (kernelx.golden.tsv, kx_fmem_unmap_again,
kx_fmem_munmap_unmapped).

The other half of #3196, a range with an unmapped page in the middle, is
already fixed on main by 04ed61957; the test covers both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:59:32 +05:30
Jan Šlégl e753080391 fix(libkernel): count guest module segments against the flexible memory budget
sceKernelAvailableFlexibleMemorySize and the flexible maps counted only
explicit flexible mappings, so the full 448 MiB was reported while the
title's executable and modules were loaded. A title that derives its code
and data size from the budget minus the available size got an unsigned
underflow.

The segments of the main executable and of every .guest.prx module that
the title's ELF gives permissions now count, rounded to 16 KiB pages, as
shadPS4 maps every non-system module segment as flexible memory. The
relinker's own blocks and the host system libraries do not count. The
charge ends when a module is unloaded.

Refs #2883

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fnrk5Dazjb2tLooVSpdRQq
2026-10-11 17:48:05 +02:00
MarcZX100 e56a727375 feat(build): support Linux AddressSanitizer builds 2026-10-11 15:40:36 +00:00
alfalfa-star f3a2dcff7d test(libkernel): map fixed over a hole inside a direct allocation
One 4-page direct mapping loses its second page to munmap, then pages
1-2 are mapped fixed. The range starts in the hole; before the fix the
unmap of the overlap threw "guest protection or unmap range has a
hole". The pages around it keep their data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:34:12 +02:00
xXPBSXx aace6a3f38 fix(libkernel): report the largest free direct memory block from sceKernelAvailableDirectMemorySize
The call returned the first free gap in the range instead of the largest free block.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 00:58:28 +02:00
raghav314 be1f501525 fix(agc): import GPU-mapped guest memory when it is mapped
The registry calls an observer on the mapping thread, after the registry
lock is released, with the ranges a kernel mapping gave GPU access:
direct and flexible maps with it, and fixed remaps with it over a
registered range (a reservation). The mutation looks them up while it
still holds the lock and hands them over weakly, with the registry
generation it ended at, so the observer does not keep a range alive.
The driver imports those ranges on its current device, so an
address-based build finds the import instead of pinning the memory on
the queue that first uses it. A range that gains GPU access through
sceKernelMprotect, and AnyPS5's own libc heap, are still imported on
first use.

The import at map does not reconcile the other imports with the
registry, so its cost does not grow with the number of imports. It skips
a range that is gone, and any range when the imports were reconciled
with a newer registry since; the next lookup imports those as before.
It installs the pin waiter as the other import paths do, so unmapping a
range imported at map retires its import. The live import bytes are kept as a
running total instead of being summed over every import. Once the import
table belongs to the current device the import takes only the table's
lock; the first import on a device (which runs the write-watch probe)
and a device that was just replaced go through the GPU lock. Clearing
the observer waits for calls already running.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:23:23 +02:00
Dean 262c031692
Merge pull request #2907 from oneandonlydean/carry/libkernel-reserved-range-hint
fix(libkernel): map non-fixed hints inside a reserved range in place
2026-10-11 09:26:41 +13:00
ps54all 3444ebf5cd fix(libkernel): map non-fixed hints inside a reserved range in place
A map without MAP_FIXED whose hint lies wholly inside a range still
reserved by sceKernelReserveVirtualRange is placed at the hint, as a fixed
map into the reservation would be. Hints outside reservations and ranges
that are partly mapped keep their hint behaviour. The reservation is
checked under the registry mutation, so it cannot be released and its
address reused between the check and the map (MarcZX100, #2687).

Fixes #2687

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2cY3SsTYnh4u4ZtEr2Adx
2026-10-11 07:10:29 +13:00
Scott Schuster 58e871cf2a fix(libkernel): place sceKernelReserveVirtualRange at its address hint
Without MAP_FIXED the address is a hint, as for FreeBSD's mmap: the reservation goes at the hint when that range is free, else at the first free range above it. The hint was dropped, so every hinted reservation landed at the lowest free address.

RetroArch (PPSA99169) reserves its cores' direct-memory heap near 0x2000000000 outside the GPU window (0x200000000-0x300000000) and refuses a range inside it; with every reservation at 0x200400000 the heap was never made, and Dolphin's first aligned allocation returned null.

AI-assisted (Claude Code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 07:59:00 -05:00
Dean b0cccf20a0
Merge pull request #861 from jeffrey12cali/fix/linux-guest-arena
fix(guest-arena): place guest address space in the PS5 window on Linux
2026-10-10 23:55:28 +13:00
Dean Galvin 766370108d fix(libkernel): read random devices in the Windows sceKernelPread
The Windows sceKernelPread stopped going through pread when it learned to
return errors, and with it lost the random device read, so a pread on
/dev/urandom reached ReadFile on a handle that is not a disk and failed
with ESPIPE. Read random devices first, as the Linux path does.

A pread into a read-only guest mapping now returns EFAULT instead of
throwing, matching Linux and the AIO read beside it, so the Windows
guest memory test checks for that return.
2026-10-10 23:26:37 +13:00
jeffrey12cali c4062623bd test(guest-arena): keep the fixed-mapping probe on Linux only
CheckUnmappedArenaRangeStaysReserved uses mmap, PROT_READ, PROT_WRITE,
MAP_FIXED_NOREPLACE and EEXIST directly, so the Windows build fails to compile it.
The probe exists to show that an unmapped arena range still rejects a hostile
MAP_FIXED_NOREPLACE mapping, which is Linux-specific behaviour.

Guard the function and its call site with __linux__. The other tests in the file
compile on both platforms.
2026-10-10 22:56:07 +13:00
jeffrey12cali e4e0702946 fix(guest-arena): place guest address space in the PS5 window on Linux
The arena constructor was Windows-only, so on Linux Available() was false: the guest heap fell back to the host allocator and direct memory landed wherever the kernel pleased. Titles that require a low virtual address rejected the mapping and re-issued the identical request, which surfaced as a start-up assert inside their own allocator.

Seed the window from the mappings that already exist instead of reserving it up front, because a PROT_NONE reservation of the whole range starves the kernel's own top-down mmap, which picks addresses just below the stack inside that range. Commit, reset and set-protection become portable so the arena owns its used ranges on both platforms, and fixed mappings are checked against the arena's used ranges rather than against EEXIST from a reservation.

The window-placement and no-overwrite checks in guest_memory were compiled only for Windows; they now run on Linux too, and the window check fails without this change.

Fixes #692
2026-10-10 22:56:07 +13:00
raghav314 c11a9cb59f fix(agc): address-based builds map only memory mapped for the GPU
The registry records whether a range was mapped or protected with GPU
access (0x10/0x20), and an address-based build leaves out ranges without
it, as the console's GPU page tables do. The main image is registered
without GPU access; the title's own mprotect with GPU bits adds it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:34:34 +05:30
Adrià 8fded89e6b
Merge pull request #1803 from tv7/fix/mprotect-guest-module-images
fix(libkernel): reprotect bundled guest module images on Windows
2026-10-10 10:45:37 +02:00
Adrià 4fbf6ca5a3
Merge pull request #1960 from Raghav314/fix/agc-import-leaves-pages-unarmed
fix(agc): host imports write-protect only pages a collect already protects
2026-10-10 10:45:04 +02:00
Adrià 874c144712
Merge pull request #1909 from TheAggie/feat/libkernel-mmap-aligned-super
feat(libkernel): accept MAP_ALIGNED_SUPER in mmap
2026-10-10 10:44:57 +02:00
Lexroid b1a14c6f36 fix(libkernel): unregister guest module images in dlclose and propagate failures
Move the image unregistration out of the Module destructor into
dlclose_nid_postfix. It runs while the handle is still registered, so a
failure (the image stays leased by GPU work) propagates to the caller and
leaves the module loaded and usable. Images shared by another open
handle stay registered until the last handle closes.

guest_memory now checks that dlclose under a held lease throws and keeps
the module, its symbols and its protection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 10:23:50 +02:00
Lexroid 57c179c74d fix(libkernel): unregister guest module images when they are unloaded
A .guest.prx image registered by sceKernelMprotect stayed in the guest
allocation registry after dlclose freed it, so its dead ranges blocked
later mappings at that address and could reach GPU builds.

The Windows module destructor now unregisters the image before
FreeLibrary. Unregistering waits for outstanding leases on the image like
unmap and protect do; if the wait fails the module stays loaded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 10:23:42 +02:00
Lexroid 9f1d7b0516 fix(libkernel): reprotect bundled guest module images on Windows
On Windows, sceKernelMprotect accepted only the executable's image and threw
"Memory protection of a foreign image is not supported" for every other
image. Bundled title modules are relinked into DLLs named <module>.guest.prx,
so a title that reprotects its own module pages failed. Dying Light 2's
bundled libNRD does this at start-up (#1794).

DoMprotect now also accepts images whose file name ends in .guest.prx, and
registers their committed pages like the executable's. Other images are
still refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 10:23:42 +02:00
Pouare514 03d975af3c fix(libkernel): reject malformed ranges in sceKernelReleaseDirectMemory
A zero, unaligned, overflowing or out-of-range length reached the physical pool, where a sub-page length silently freed a whole page and fragmented the block map, and an out-of-range length walked the page table past the backing store. Validate alignment and bounds up front and return EINVAL instead.
2026-10-10 08:22:59 +02:00
Adrià 40aeb97d57
Merge pull request #1152 from alfalfa-star/perf/linux-direct-memory-import-chunks
perf(agc): share one dma-buf import per 64 MiB of Linux direct memory
2026-10-10 08:14:40 +02:00
raghav314 c7f4690cd3 fix(agc): host imports write-protect only pages a collect already protects
Importing guest memory collected the write watch over the whole import,
which write-protected every page of it, so pages nothing had collected yet
took a write fault on their first CPU write. The import now collects only
the parts a collect already protects (GuestWriteWatchCollectArmed). The
rest stays unprotected until something collects it, which reports it as
written; a GPU write recorded into such memory (MarkWritten) first collects
the never-collected parts of its 64 KiB blocks
(GuestWriteWatchCollectFresh), so that report is older than the GPU's
results and does not read as a CPU write over them.

Those pages are stamped as written and their CPU pages are noted, but the
block's CPU stamp is left alone, so a GPU label record or a pending storage
image elsewhere in the block does not see a CPU write. StoredOver and
UnchangedSinceCollected read the noted pages whenever a block has them, so
a store to such a page is still seen by a check over that page. The extra
generation step happens only when something was collected; a block whose
write protection fails is unwatched and the write returns no generation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 11:38:28 +05:30