Metal Gear Solid 4 (PPSA18194) aborts on main when its launcher starts
the game: sceSystemServiceLoadExec runs the guest shutdown, a render
thread that then calls into the stopped AGC driver gets ProcessShutdown
back from LibcAwaitExit, and the exception unwinds into title code,
which terminates the process before the new executable starts.
LibcAwaitExit parks a thread only once an exit has been requested, and
LoadExec ran the shutdown without requesting one. It now starts the
shutdown the way exit does, so such threads wait until the process is
replaced.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
fopen left the host's errno in place when the host open failed, so on Linux a symlink loop reached the title as 40 and an over-long name as 36, which are EMSGSIZE and EINPROGRESS on FreeBSD. GuestErrnoFromHost_nid_no_patch maps the host value to FreeBSD 11.4's: 1 to 34 are shared except EAGAIN (35), and ELOOP, ENAMETOOLONG, ENOTEMPTY, EDQUOT, EOVERFLOW and EDEADLK get their FreeBSD numbers; anything else is EIO.
Titles that bundle OpenSSL load the console's CA file from the sandbox
directory and fail every HTTPS request without it. ProsperoRadio
(PPSA99001, libcurl 8.18.0 + OpenSSL 3.5.2) on main + #4796 + #4525:
[ProsperoRadio][http] https://all.api.radio-browser.info/json/servers: Problem with the SSL CA cert (path? access rights?) (error adding trust anchors from file: /gkbHtXc3Wq/common/cert/CA_LIST.cer)
SwanStation (PPSA98510) stops on the same file (#5001).
The guest path resolver now accepts exact-path providers, and libkernel
registers one for /<sceKernelGetFsSandboxRandomWord()>/common/cert/CA_LIST.cer.
On first use it writes a PEM bundle once per process to
cert_cache/CA_LIST.cer in the run directory, read-only: on Windows the
current user's ROOT and CA system stores, elsewhere SSL_CERT_FILE or the
first distro bundle that exists. Without a certificate the path keeps
resolving under the guest root and does not exist, as before. Every other
path under the sandbox word is unchanged. The console file's format and
the rest of its common/cert directory are unverified and recorded in
docs/dev/debt/libkernel.md.
With this change, #4796, #4525 and #5025 on Windows 11, 60 s each:
[ProsperoRadio][http] 11846160 bytes in 1458 ms: 1161 passes, 803 waits (679 woken) for 1428 ms
[ProsperoRadio][catalog] page offset=0 bytes=11846160 objects=10000 accepted=9549 total=9549
...
[ProsperoRadio][catalog] page offset=60000 bytes=1712035 objects=1494 accepted=1436 total=59371
[SwanStationPS5] update: latest 1.2.5, this 1.1.3, available
[SwanStationPS5] gamedb: index written
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Unreal titles end in their own libc fatal path with the UTF-16
LowLevelFatalError message in a register, and the crash report shows
only the first 64 bytes of it as hex words, so the message is lost
(#4238: Demon Slayer PPSA03490, Stray PPSA02100).
For every general register that points at readable memory starting
with at least four printable ASCII characters, as UTF-16LE or as single
bytes, the report now prints the text up to a NUL, the first other
character, 512 characters or the end of readable memory (each page is
checked with VirtualQuery before it is read).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A ZArchive named app0.zar beside app0/ is a read-only layer under it. libc opens it at the first path resolution, looks paths missing from app0/ up case-insensitively, reads files across compressed blocks and keeps the archived descriptor table with shared positions.
Resolving an archive-only path through a call that does not read the archive throws instead of failing with ENOENT, as do opendir of a directory present in both places, fdopen of an archived descriptor and chdir into the archive. A path through .. in an archive-only directory resolves.
A GPU hang or a crash in a title leaves no record of what the queues
were last given: APS5_TRACE_GPU prints every submission (too much to
leave on), and the WAIT_REG_MEM timeout message shows only the packet
before the wait on the waiting queue.
Each queue now keeps a ring of its last 16 submissions
(APS5_PM4_RING=<n>, 0 to 4096, 0 disables): the TraceMs time in tenths
of a millisecond, serial, size, guest address and a copy of the first
512 dwords after indirect buffers are inlined, recorded under the
driver lock next to the serial, REWIND tails included. The ring of a
queue and the failure report registration are set up at the top of
Submit, before any output is reserved, so recording under the lock
does not allocate. Disabled, Submit only tests a static flag; enabled,
it costs one bounded copy per submission.
DumpSubmissionRings decodes the kept dwords with Pm4::PacketWords and
the new Pm4::KnownName, which returns the decoder's names as views of
static strings (empty for an unknown opcode, printed as UNKNOWN_0x%02x);
Pm4::Name now builds its std::string from it. Every line is formatted
with integer and string conversions only into a 512-byte stack buffer
and written with one WriteFile / write(2) on the stderr handle, so the
dump does not allocate from the C runtime heap, lines from other
threads do not interleave and the CRT stream lock is not taken (the
rule of #3871). The header gives the number of queues and the ring
size, and each queue line the submissions recorded and kept.
libc gets a small registry of failure reports
(LibcRegisterFailureReport_nid_no_patch, Unregister, Run); Run runs
the registered reports the first time it is called and returns at once
after that. The Windows crash report runs it after the thread dump of
an unhandled exception, after SIGABRT (which also covers the
APS5_HANG_DUMP_SECS timed dump, since it aborts) and after an invalid
CRT parameter, so an abort after a reported exception does not print
the rings twice. The driver registers its dump at the first submission
and unregisters at static destruction; the dump there waits a bounded
time for each ring lock. What the crash-path dump cannot do (a ring
held by the crashed thread, rings corrupted by the crash, Linux) is
recorded in technical debt.
APS5_STALL_WATCHDOG_SECS=<n> (0 to 86400, 0 disables) makes a waiter
that has not progressed for n seconds dump the rings once per wait: a
WAIT_REG_MEM (progress as for APS5_GPU_WAIT_TIMEOUT_MS: another packet
runs or completes), a REWIND waiting for the title, and a compute
submission held behind queue 0. The last one prints under the driver
lock, so submissions and the other queues wait for it (USAGE.md says
so). Both variables are read at the top of Submit, so an invalid value
of either throws at the first submission and never later inside a GPU
worker. The order wait behind queue 0 releases its order hold on scope
exit, so a throwing dump cannot leave it counted.
No packet semantics change; packet sizes and names are those of the
existing decoder (PM4 type-3 header layout as in Mesa
src/amd/common/sid.h). The queue bound of Submit and of the rings is
one constant, SubmissionQueueEnd.
Tests: agc_driver_pm4_tests gets ten modes, each its own ctest:
agc_driver_pm4_ring (APS5_PM4_RING=4, six submissions on queue 0 and
two on 0x20: the dump holds #3..#6 and #1..#2 in order, nothing
older), agc_driver_pm4_ring_empty (one line), _ring_disabled (one
line), _ring_invalid and agc_driver_pm4_stall_watchdog_invalid
(APS5_STALL_WATCHDOG_SECS=1x: the submission throws and is not
executed), _ring_report (LibcRunFailureReports_nid_no_patch dumps the
rings), _ring_report_heap (Windows: with ucrtbase.dll's HeapAlloc and
HeapReAlloc imports counting calls from the reporting thread, the
failure report of a submission holding INDEX_BUFFER_SIZE names it and
allocates nothing, while Pm4::Name of the same header is seen
allocating), and with APS5_STALL_WATCHDOG_SECS=1 a WAIT_REG_MEM
(agc_driver_pm4_stall_watchdog), a REWIND (_stall_watchdog_rewind) and
a compute submission held behind a queue 0 rendering wait
(_stall_watchdog_order), each held for about 2.5 s, dump once and
complete when released. The PM4 catalog test checks KnownName against
every opcode and custom name. windows_backtrace_failure_report checks
that abort() runs the registered reports once and not an unregistered
one; windows_backtrace_failure_report_once that abort() does not run
them again after an earlier run. With the dump decoding through
Pm4::Name, agc_driver_pm4_ring_report_heap fails with "the failure
report allocated from the C runtime heap"; with Run resetting its
flag, windows_backtrace_failure_report_once prints the report twice.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Lines queued behind the logger were lost when the process ended through
abort(), std::terminate, the Windows crash report or a guest _Exit,
because only driver failure, Driver::stop and a normal exit flushed the
queue.
libc keeps one end flush (LibcSetEndFlush_nid_no_patch) and runs it from
those paths. Its first registration chains a SIGABRT handler in front of
the previous one, so every abort() and std::terminate in the process is
covered, on Linux as well; with the default disposition the handler
restores it and raises again, so raise(SIGABRT) still ends the process.
Guest abort() and _Exit, and ReportCrash for the first fatal exception,
run the flush directly. AsyncLog registers a bounded flush (FlushFor, 2 s)
that polls two counters and takes no lock, so a dying process cannot hang
on a blocked stream or on the queue lock; after its first timeout it
returns at once. Flush counts queued and finished entries instead of
watching the queue and the writer flag, so it waits for the lines queued
before the call and not for a moment in which no thread is logging.
agc_async_log_fatal runs the test executable as a child that queues lines
behind a held stream and ends in abort(), std::terminate(), raise(SIGABRT)
(POSIX) or an access violation (Windows), and once more with the stream
never released. The lines of the first four must arrive in order, the last
child must end after the bound, and every child has a watchdog.
agc_async_log gains the bounded flush case.
Texture creation logged one stdout line per texture and per mip through
WriteLogLine, which formatted with the mingw printf and flushed every
line on the queue worker (about 786k lines in a 25 minute cave run).
AsyncLog keeps a bounded queue and a logger thread. AGC_LOG_DEFERRED
captures numeric arguments on the caller and formats them on the logger
thread; WriteLogLine hands finished lines to a per-module sink in
libc/LogMacros.hpp that AgcDriver installs, so all AgcDriver stdout
lines keep their order and are written one flush per batch. A full queue
blocks the caller, so no line is dropped. Flush runs on driver failure,
shutdown and at exit. APS5_LOG_SYNC=1 restores the synchronous path.
Identical "deferred write-back failed" notes are printed three times per
10 s and then counted into one "repeated N more times" line. A
"[log] 10 s" line reports lines, bytes, caller time and logger time
(APS5_LOG_STATS=0 hides it).
GuestWriteWatchCovers says whether a range is watched in full. The
Linux and macOS watches unregister page ranges, so a range can hold
watched pages between unwatched ones, and code that must act on every
watched byte of a store has had no way to find them short of asking
page by page. GuestWriteWatchOverlaps answers with one lookup in the
registered ranges, like Covers.
The macOS write-watch test and agc_write_tracking on Linux check it
around watched pages between unwatched ones, at both edges and for an
empty range.
Retain Pipodi safety guard from closed PR5851 without its resident alias expansion or automatic AGC alias routing. Main still refuses aliases over partial resident runs. Five unmodified-main regression runs fail before this guard; positive validation pending. Fork proposal only; no duplicate upstream PR intended.
(cherry picked from commit 078a5740b40c3b5b6384eec8f92363360c75b758)
A read-only open, stat or reachability check of a missing path listed its
directory twice: once for a case-insensitive match of the name and again for
a case-insensitive match of the relinked <name>.guest.prx (#5434). Both
names are now matched in the same listing. Precedence and the ambiguous-name
errors are unchanged.
Implement __modti3, the signed 128-bit integer modulo runtime helper complementing __divti3, __udivti3 and __umodti3. Add unit test coverage in guest_crt_tests and document zero-divisor exception behavior in technical debt.
- Windows (`_WIN32`): replace full-duration waitable-timer sleep in `PreciseSleepHundredNanos` and the repeated `PreciseSleepUs` call loop in `PreciseSleepUntil` with a hybrid sleep-then-spin helper (`PreciseSleepFromCounterWithTimer`) that sleeps on the thread-local high-resolution waitable timer up to a 250 us spin tail, falls back to `std::this_thread::sleep_for` if timer setup/wait fails, and finishes the remaining window using `QueryPerformanceCounter` + `YieldProcessor()` (thresholded to 200 ns and ceiling-rounded via `PreciseHundredNanosToQpcTicks` for non-10MHz QPC frequencies).
- Linux & POSIX (`!_WIN32 && !__EMSCRIPTEN__`): replace repeated `nanosleep` loop in `PreciseSleepUs` and `PreciseSleepUntil` with `PreciseSleepFromNanos` using `clock_gettime(CLOCK_MONOTONIC)`. On Linux (`__linux__`), cache `PR_SET_TIMERSLACK` (`1 ns`) once per thread (`thread_local`) and sleep via `clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME)` up to a 15 us spin tail (falling back to a conservative 100 us tail if `prctl` fails; 100 us `nanosleep` tail on other POSIX platforms) before finishing with `_mm_pause()` on x86 or `std::atomic_signal_fence` on non-x86.
- Tests: expand `core/libs/tests/PreciseWait.cpp` to assert that the 31-sample median of `PreciseSleepUs(50)` stays below 90 us (which fails on `main` at ~118 us on Linux and ~518 us on Windows) and verify ceiling QPC tick conversion and timer fallbacks.
A mapping without MAP_FIXED treated its address as the lowest place it
could go, and a hint at or above the end of the guest arena threw on the
host. FreeBSD 11.4's vm_mmap_object places such mappings with
vm_map_find_min, which takes the address as a hint only: it tries at or
above it, then again from its minimum address.
GuestArenaAllocateFromHint does the same in the arena: at or above the
hint when it lies below the arena's end and a range fits there, otherwise
from the arena's start. MapPlaced and the Windows mmap_aligned use it for
hinted mappings; GuestArenaAllocateAtOrAbove keeps its meaning for the
callers that need a range above an address.
PS5CEMU-HAR (PPSA99360) reserves its JIT range with
sceKernelReserveVirtualRange and a hint near its own code, a host address
above the arena here, and aborted with "mapping address hint is above the
guest address space arena" when launching a 3DS title.
The guest formatter passed the flags of %o, %u, %x and %X to the host, and
MinGW's printf prefixes a space to %u when the space flag is set: "% u" of 5
gave " 5". C17 7.21.6.1 applies both flags to signed conversions only, and
FreeBSD 11.4 vfprintf clears the sign for every unsigned conversion (nosign).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
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.
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
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