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.
FreeBSD sys_flock takes LOCK_UN over LOCK_EX over LOCK_SH, ignores other
bits and fails with EBADF when none of the three is set. The operation
reached the host unchanged, so malformed operations differed between
hosts: Linux rejects them with EINVAL and the Windows lock tracking threw.
Decode the operation before either host sees it.
flock mapped each call straight onto LockFileEx/UnlockFileEx, whose
byte-range locks stack per handle. LOCK_UN without a lock failed with
ERROR_NOT_LOCKED and aborted the process, a repeated LOCK_EX blocked
forever, converting between shared and exclusive failed, and a repeated
LOCK_SH needed as many unlocks.
Track the one lock each descriptor holds: a repeated mode and LOCK_UN
without a lock succeed, a conversion releases the old lock before taking
the new one, and close forgets the descriptor's lock.
On Windows the existence check accepts "file/", then _wstat64 rejects
it and FillFileStat throws, aborting the title. On Linux the host
reports ENOTDIR, which became ENOENT. A trailing slash on something
that is not a directory now returns ENOTDIR on both hosts, as on
FreeBSD.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AJM's instance and batch tables and APR's file, directory and AMM
state were namespace-scope statics, destroyed by std::exit while the
title's threads keep calling the libraries. A decoder thread then read
a freed instance (an unknown codec), sceAjmBatchWait no longer found
its batch, and APR resolved file ids through a freed vector. The state
is now allocated once and never destroyed.
Every direct memory allocation had a memfd of its own, so each one the
GPU used became its own udmabuf and its own imported buffer object. An
imported buffer object cannot be always valid in the GPU VM: amdgpu
validates every one of them at every command submission. PPSA14632
(LEGO Horizon Adventures) imports about 7000 allocations, 5800 of them
64 KiB, and each vkQueueSubmit took about 2.3 ms (5 s per 10 s, held
under the GPU mutex). A standalone test shows the cost follows the
count of imported dma-buf buffer objects, not their size or the count
of ordinary allocations.
- libkernel: on Linux all direct memory lives in one sparse memfd of
DIRECT_MEMORY_SIZE, at file offset = physical address
(PhysicalBacking keeps the file and its offset; SharedBacking returns
that offset). A range allocated again is zeroed through a temporary
mapping, as the old per-allocation memfd was.
- Driver: an import of shared direct memory that lies inside one
64 MiB chunk of the file binds its own VkBuffer into that chunk's
memory, one udmabuf import per chunk (HostImport::chunk holds it, so
it lives until the last buffer bound to it retires). Ranges that
cross a chunk or do not meet the buffer alignment are imported alone
as before.
On PPSA14632 vkQueueSubmit drops from about 2400 us to 46 us on
average, the GPU idles 36 ms instead of 172 ms per present, and
presents go from about 40 to 80-97 per 10 s.
Released direct memory stays committed in the file (see
TechnicalDebt.md): a live chunk import pins its pages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As requested in review: a failed sceKernelGetModuleInfoFromAddr no longer
reports an empty answer. Adds a failure test (symbol interposition) and
extends the metadata test (EH record walk, terminator, no-header DSO).
Co-authored-by: jweidenh <38249793+jweidenh@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>
A title that ships its own libc.prx unwinds C++ exceptions with that
libc's libunwind, which asks sceKernelGetModuleInfoForUnwind where each
frame's eh_frame is. On Linux the call always reported no tables, so
no C++ exception thrown in guest code could be caught. For the main
executable and .guest.prx images, report the PT_GNU_EH_FRAME tables
that sceKernelGetModuleInfoFromAddr finds, as Windows does from
.ehmeta. Other host images still report none.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeC9bA3n74TzJRYv9AgBE8
Winsock fails send, sendto and sendmsg on a socket shut down for writing
with WSAESHUTDOWN, which neither Windows error map handled, so libkernel
and libSceNet reported EIO. FreeBSD reports EPIPE there, and the Linux
maps already translate the host's EPIPE to 32. Map WSAESHUTDOWN to 32
in both.
Fill takes either info struct, so sceKernelGetModuleInfo reports the
name and segments sceKernelGetModuleInfoFromAddr reports for the same
image, on Linux and Windows.
sigprocmask keeps the first word of the title's set as it is, where
FreeBSD keeps signal n at bit n - 1 (_SIG_BIT), but Dispatch tested bit
n. A title that blocked SIGTERM with a FreeBSD-built set (bit 14) was
not protected, and a set with bit 15 blocked SIGTERM instead of SIGURG.
Dispatch now tests bit n - 1; the stored mask and the set returned by
sigprocmask are unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
sceKernelLoadStartModule discarded its arguments, and the relinker's Linux guest writer
synthesized an initialisation trampoline that also cleared the argument registers before
jumping to the module entry point, so a converted module always reached its own start-up
code with args = 0 and argp = NULL.
On PPSA28997 two of the title's own modules validate that parameter block as their first
act and dereference it unconditionally: SaveData faults on `mov (%rsi),%eax` with rsi = 0
beside its `cmp $0x10` size checks, and PSN faults the same way at PSN+0x3ee7 with rbx = 0.
The Windows branch delivers the block, so identical code reports its parameter-version
error and continues instead of faulting.
sceKernelLoadStartModule now records the pair before dlopen and clears it afterwards, and
the emitted DT_INIT stub reads it through two accessors exported by libkernel. The
initialiser is called rather than tail-jumped so its return value can be reported through
*res as the PS5 API specifies. The state is thread_local, which makes the handoff per-call:
ld.so runs a module's initialiser on the thread that called dlopen. DT_FINI keeps a plain
call-through, so unloading neither reads stale arguments nor reports a result.
The accessor names use the _nid_no_patch suffix so the NID resolver leaves them alone and a
guest import can name them exactly.