Repository navigation
Description
On Windows, a guest thread that takes a write fault on a write-protected shared view resumes with the bytes directly below rsp overwritten when its AVX state is live. System V leaf functions keep locals in that red zone, so the guest then reads garbage. The code is on main (313de1c5) and is not specific to one title.
Mechanism
- Direct memory is mapped as section views.
WindowsMappings::Collectwrite-protects the views it collects (core/libs/prx/libc/include/WindowsMappings.hpp, "arm shared memory write tracking"). - The vectored handler in
core/libs/prx/libc/src/specifics/windows/CrashReport.cppcallsGuestArenaHandleWrite, which restores the protection, and returnsEXCEPTION_CONTINUE_EXECUTION. - Windows delivers the exception on the faulting thread's own stack, directly below
rsp; the Windows ABI has no red zone. When the thread's AVX state is live, the kernel writes the upper halves ofymm0-ymm15(0x100 bytes) at the top of that frame. Measured, they startrsp mod 64bytes belowrsp. - With clean AVX state the first 0x120 to 0x140 bytes below
rspare left alone, which is why most code survives. - The bytes are gone before any handler runs, so the handler cannot restore them.
HandleSse4ain the same handler resumes an exception on the guest stack in the same way.
Evidence
Astro Bot (PPSA21564), Windows 11 Pro 10.0.26200, Core Ultra 9 285K, MinGW-w64 GCC 15.2.0, astrobot branch eeda258c plus #296, #349, #355, #286 and #289. Full notes in discussion #357.
- Access violation on
SceSndzAudioOutMainabout 10 s after start, in 2 of 4 launches. The faulting function (eboot+0xec6090) is a leaf that keeps locals at[rsp-0x08]..[rsp-0x31]without movingrsp, and stores into a ring buffer in direct memory. - With every resumed exception recorded per thread: in 3 of 3 captured crashes the last resumed write fault on the crashing thread was at the same
rspas the crash, a few instructions earlier (+0xec61e1then+0xec64abor+0xec64b5;+0xe46400then+0xe4636d). - In the
+0xec64abcasersp mod 64is 8 and[rsp-0x10], the savedrbx, reads back as 0.
Standalone reproduction
No AnyPS5 code. g++ -O1 redzone.cpp with MinGW-w64 GCC 15.2.0.
redzone.cpp
#include <windows.h>
#include <cstdint>
#include <cstdio>
static void* g_page;
static LONG WINAPI Resume(EXCEPTION_POINTERS* info) {
if (info->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION) return EXCEPTION_CONTINUE_SEARCH;
DWORD previous;
VirtualProtect(g_page, 4096, PAGE_READWRITE, &previous);
return EXCEPTION_CONTINUE_EXECUTION;
}
extern "C" void Probe(void* target, std::uint64_t* out, std::uint64_t wide);
__asm__(
".globl Probe\n"
"Probe:\n"
" movabs $0x1122334455667788, %rax\n"
" mov $512, %r9\n"
" lea -8(%rsp), %r10\n"
"1: mov %rax, (%r10)\n"
" sub $8, %r10\n"
" dec %r9\n"
" jnz 1b\n"
" test %r8, %r8\n"
" jz 2f\n"
" vpcmpeqd %ymm0, %ymm0, %ymm0\n"
" vpcmpeqd %ymm1, %ymm1, %ymm1\n"
" vpcmpeqd %ymm2, %ymm2, %ymm2\n"
" vpcmpeqd %ymm3, %ymm3, %ymm3\n"
" vpcmpeqd %ymm4, %ymm4, %ymm4\n"
" vpcmpeqd %ymm5, %ymm5, %ymm5\n"
" vmovups %ymm0, (%rcx)\n"
" jmp 3f\n"
"2: movb $1, (%rcx)\n"
"3: mov $512, %r9\n"
" lea -8(%rsp), %r10\n"
"4: mov (%r10), %rax\n"
" mov %rax, (%rdx)\n"
" add $8, %rdx\n"
" sub $8, %r10\n"
" dec %r9\n"
" jnz 4b\n"
" vzeroupper\n"
" ret\n");
int main() {
AddVectoredExceptionHandler(1, Resume);
volatile char touch[0x2000];
touch[0] = 0;
for (int mode = 0; mode < 3; ++mode) {
g_page = VirtualAlloc(nullptr, 4096, MEM_RESERVE | MEM_COMMIT, mode ? PAGE_READONLY : PAGE_READWRITE);
static std::uint64_t slots[512];
Probe(g_page, slots, mode == 2);
int nearest = -1;
int changed = 0;
for (int i = 0; i < 512; ++i) {
if (slots[i] == 0x1122334455667788ull) continue;
if (nearest < 0) nearest = i;
++changed;
}
const char* name = mode == 0 ? "no fault" : mode == 1 ? "byte write faulted and resumed" : "ymm write faulted and resumed";
if (nearest < 0) std::printf("%s: nothing below rsp changed\n", name);
else std::printf("%s: %d of 512 qwords changed, the nearest at rsp-0x%x\n", name, changed, (nearest + 1) * 8);
}
}no fault: nothing below rsp changed
byte write faulted and resumed: 197 of 512 qwords changed, the nearest at rsp-0x140
ymm write faulted and resumed: 229 of 512 qwords changed, the nearest at rsp-0x40
Workaround and its cost
APS5_NO_WRITE_WATCH=1. Without write watching the driver never reaches WindowsMappings::Collect, so no view is write-protected and no write fault is resumed on a guest stack. Astro Bot: two launches of 240 s and 498 s, no crash on SceSndzAudioOutMain.
- Dreaming Sarah (PPSA02929) on
main(e1654a97plus feat(libkernel): implement sceKernelMlock #359): 60 FPS by default, 38 FPS with the switch. - Astro Bot with the switch: 1.6 to 1.9 FPS, then
vkAllocateMemory device buffer: Vulkan result -2(VK_ERROR_OUT_OF_DEVICE_MEMORY) after 239 to 498 s on a 16 GB RTX 5080. A local counter saw 175 760 device buffer allocations in 239 s, with buffers of 289 MB to 1.99 GB allocated over and over; GPU use swings between 7 and 14 GB. - Hypothesis, not established: those allocations are the whole-range copies made when nothing is write-watched. The frame rate with tracking on was not measured for this title.
Possible directions
None chosen here; this looks like a design decision.
- Stop write-protecting shared views. Reporting only the shared views as untracked while private memory stays watched is not enough: tried as a local diagnostic, it renders black frames, in Dreaming Sarah on
maintoo. - Back direct memory with private
MEM_WRITE_WATCHmemory instead of section views. - Remove red-zone use at relink time.
The first two leave the HandleSse4a path as it is.
Related
- Discussion Astro Bot (PPSA21564) #357 (Astro Bot bring-up), where this was first reported.
- fix(libSceFiber): pin fiber contexts writable under write tracking on Windows #283 handles a neighbouring case: the guest stack itself is the write-protected page, so the exception cannot be dispatched at all.
Investigated with AI assistance (Claude Code).
Activity
boykopovar commentedon Oct 3, 2026
Guys, I see everything and I'll get on it. And thank you all so much for helping. But now I still need to review more than a hundred pull requests and at the same time complete the optimization, which will remove the last fundamental extra work compared to the PS5 - I want to transfer the recompilation of the shader from the moment of display to the moment of registration.
adriadam10 commentedon Oct 3, 2026
@boykopovar about moving shader recompilation from display to registration: in case it saves you time, I split it into small pull requests. Each one can be reviewed and merged on its own, and none changes the generated SPIR-V (checked byte for byte on the 88 shaders the test suite compiles).
getSourcefor the plan,findOrCompileVariantagain for the first variant). The plan now owns the values it walks, and the first variant takes the program the entry was built with. First use: 126.6M → 70.4M instructions (callgrind, 1754-word compute shader).RegisterShaderhands compute binaries to a worker that builds the cache entry from the header: SH registers, user SGPR count, wave size fromspecials->dispatch_modifierbit 15. The first dispatch then only specializes and emits. If a dispatch doesn't match the prediction, it builds its own entry as today. One open question: I couldn't confirm that titles passdispatch_modifiertosceAgcCbDispatch. If you know, that settles it. Pixel and vertex depend on draw state and are not included.ValidateProgramby value/block id instead of pointer-keyed hash maps. It was about 20 % of a recompile; now -32 % there, -7…13 % cycles overall.agc_shader_memoryfailed under AddressSanitizer because the test used stack arrays as guest pages.#470–#472 are green on CI; #473 is still running. I measured on synthetic shaders, not on a title. If you can share a few
APS5_DUMP_SHADERS=1dumps from Dreaming Sarah, I can rerun the numbers on real shaders.QDarkRex commentedon Oct 4, 2026
Same standalone result on an AMD Zen 3 without AVX-512 (Ryzen 7 5700X, Windows 11, MinGW-w64 GCC 15.2.0,
g++ -O1 redzone.cpp), 3 runs:On this host Astro Bot hit B in 2 of 2 write-watched launches (
rip eboot+0xec64ab, read of0x4000, and+0xec64b5), details in #357. The enabled XSAVE area here is 832 bytes (XCR0 = 0x7, CPUID 0xD), so the AVX upper halves are its last 256 bytes, the part next torsp.A guess about why B depends on the host, not checked: with AVX-512 enabled the area grows to about 2.7 KB, and the components after AVX (opmask and ZMM) would put the AVX halves about 2 KB further below
rsp, out of reach of a leaf function's locals. The Core Ultra 9 285K above has no AVX-512 and hits B; SP4C3B4R-8 did not report it in #357. @SP4C3B4R-8, if you have a minute: which CPU is that, and what doesredzone.cppprint there? If the guess holds, a fix verified only on an AVX-512 host could look fine while B still happens on others.Tested with AI assistance (Claude Code).
SP4C3B4R-8 commentedon Oct 4, 2026
@QDarkRex your guess holds here. My host is an AMD Ryzen 7 7800X3D (Zen 4, AVX-512 enabled), Windows 11 10.0.26300, MinGW-w64 GCC 15.2.0,
g++ -O1 redzone.cpp, 3 runs:XCR0 = 0xe7and CPUID 0xD reports an enabled XSAVE area of 2432 bytes, so the nearest overwritten qword is about 0x650 belowrspinstead of 0x20-0x140 on your 5700X and the 285K. That is far below the[rsp-0x08]..[rsp-0x31]locals of the leaf at+0xec6090, which fits B never showing up in my write-watched runs.Since the System V red zone is only 128 bytes, an AVX-512 host like mine never shows the bug, so a fix for #361 checked only on such a host proves nothing, and I can't be the one to verify it.
Written with AI assistance (Claude Code).
frankea commentedon Oct 10, 2026
Third host without AVX-512, same result. AMD Ryzen 7 5800X (Zen 3), Windows 11 10.0.26200, MinGW-w64 GCC 15.2.0 r7,
g++ -O1 redzone.cpp, 3 runs:So this machine shows the clobber and can verify a fix; the AVX-512 hosts above cannot.
Where the two platforms differ, from
main(c72d5ce1):PhysicalBacking: amemfdon Linux, a pagefile section on Windows (DirectMemory.cpp), mapped shared at every guest address throughGuestArenaMap.rsp.WindowsMappings::CollectarmingPAGE_READONLYon the views and the vectored handler resuming (HandleWrite). That is the only path where a tracked write is delivered as a user-mode exception on the guest thread, and the kernel frame belowrspcomes with it. Nothing in the handler can prevent it.MEM_WRITE_WATCHmemory (WindowsMappings::Commit), which reports writes without any exception. It is the only Windows primitive that does.So of the three directions in the issue, the second is the one that removes the exception rather than moving it. A sketch that keeps the section only where it is needed:
PhysicalBackingon Windows allocates nothing up front. The firstMapof a physical range replaces the placeholder with privateMEM_WRITE_WATCHmemory (the existingCommitpath), soCollectreachesGetWriteWatchfor direct memory too and never arms a view.Mapof a physical range that is already mapped is the only case that needs a section. Convert then: create the section,PAGE_NOACCESSthe first mapping, copy its pages into the section, replace both ranges with views. A guest write during the copy faults and resumes as today, so the window is the conversion itself, not every tracked write.GuestBufferMemorycreateHostPointerImport) can import the guest address of private direct memory directly, since no page of it is ever write-protected.MapAliasstays for converted ranges, as now.What I could not measure here, having no title: how often a title maps the same physical range twice.
APS5_TRACE_MEMORY=1on Dreaming Sarah or Astro Bot, countingsceKernelMapDirectMemorycalls whose physical range overlaps an earlier live mapping, would say whether step 2 is a corner case or the common case. If someone with a title can post that count, it settles the design.I can prototype 1 to 3 and verify on this host. If a different direction is preferred, say so and I'll follow it.
Investigated with AI assistance (Claude Code).
TheAggie commentedon Oct 10, 2026
Two data points for this issue, from a Ryzen 7 7800X3D (Zen 4, AVX-512, the same CPU @SP4C3B4R-8 reported). Windows 11 Pro 26200, MinGW-w64 GCC 15.2.0, NVIDIA GeForce RTX 5070 Ti.
1. AVX-512 hosts are affected too, once zmm16-31 are live
The earlier runs on AVX-512 hosts measured the clobber about 0x650 below
rsp, but only with zmm16-31 clean. I filled 8 KiB belowrspwith a pattern, dirtied one register group, took a write fault, resumed, and found the nearest overwritten qword:rspxmmonlyymm0-15upper halvesk1zmm0-15upper halveszmm16-31(Hi16_ZMM)zmm16-31, thenvzeroupperWith Hi16_ZMM live, the kernel writes it at the top of the frame, inside the 128-byte red zone, and
vzeroupperdoesn't clear zmm16-31. Zeroing them (orXRSTORto init) puts the group back in its init state, and the clobber moves back to about 0x760.They do become live on guest threads in practice:
cpuid. RetroArch's eboot (PPSA99169) has 740 instructions using zmm16-31, all in one block; on a PS5 (Zen 2) they never run, here they do.amdvlk64.dll(loaded for this machine's integrated GPU) has 361 instructions using zmm16-31,nvoglv64.dll2. No Windows system DLL, CRT, GCC runtime or AnyPS5 library uses them. The audio/video libraries that link FFmpeg are the exception (141 each).The resulting crash, RetroArch running the Dolphin core with write watch on: zlib's
inflate_fastkeeps locals at-0x48/-0x60/-0x78(%rsp). A write fault in its copy loop, on the inflate output buffer in direct memory, overwrites them, and inflate later passes a ~190 MB length toadler32. It is deterministic: the same crash site in every run (inadler32, usually at the same fault address), also with the process pinned to one CPU. It goes away when the heap's pages are never write-protected, and when zmm16-31 are reset in the vectored handler before resuming.Neither mitigation closes the window. The handler reset only protects a thread's later faults; the first fault after the registers are dirtied again still clobbers. Clearing zmm16-31 after every Vulkan call (a local experiment) was not enough either, because the guest's own AVX-512 code dirties them again. So, as on the non-AVX-512 hosts, the exception has to go. That supports @frankea's direction.
2. How often a title maps the same physical range twice
@frankea asked for this. Measured with
APS5_TRACE_MEMORY=1, counting maps (map directandremap fixed) whose physical range overlaps a mapping still live at another address:In RetroArch the aliasing is almost all one 32 MiB block, mapped once at 0x404fb0000 and then remapped at a dozen CPU addresses (81 extra mappings over the run). That looks like Dolphin's emulated-RAM arena and its mirrors. Of the rest, one 32 MiB block has 4 extra mappings and one 256 KiB block has 1. That's 64.25 MiB aliased out of 4.1 GiB mapped. So step 2 would be rare for a plain RADV title, but would repeat for an emulator core that mirrors its memory.
A side note for a separate issue if wanted: the guest sees the host's
cpuidandxgetbv, so it can choose code paths the PS5 doesn't have (AVX-512 here).Investigated with AI assistance (Claude Code).
oneandonlydean commentedon Oct 10, 2026
#2054 does step 1 of this: direct memory stays write-watched private pages until it is aliased.
rm-NoobInCoding commentedon Oct 10, 2026
Another title and host that hit this. Unity (PPSA07117), Windows 11 10.0.26300, Intel Core Ultra 7 265K (no AVX-512), RTX 4070, WinLibs GCC 15.2.0 r7.
eboot+0xdad17a, a null read in a leaf function (Unity input-axis update) that keeps a pointer at[rsp-0x8]. Under x64dbg, the last first-chance exception before it is a resumed write fault in the same loop at+0xdad00d(mov dword [rcx+0x9c], 0).redzone-style test on this host: withymm1andymm9dirtied before the fault, 30 of 32 qwords in[rsp-0x100, rsp)were overwritten. The nearest was atrsp-0x18.APS5_NO_WRITE_WATCH=1the title runs, but at 24 fps instead of 60.APS5_PROFILE_DRAWshows the GPU busy 4.7 ms per frame. The[guestmem]compare path took 38.9 s of CPU per 10 s (about 355 GB compared), and draws averaged 1 ms each, mostlyreadTargetandlookup.This host can verify #2054 once it is rebased. I can run
guest_memoryand this title on it.Investigated with AI assistance (Claude Code).
johnmaia commentedon Oct 10, 2026
Another title and host that hit this: Disney Epic Mickey: Rebrushed (PPSA15657, UE4.27). Windows 11 10.0.26300, Intel Core i7-13700K (no AVX-512), RTX 4080, WinLibs GCC 15.2.0 r7, Release.
FATAL 0xc0000005atapp.exe+0x55fac49(eboot0x55eac49), a null write after Continue.0x55eaad0is a leaf (a bounding-box tree refit) that keeps a pointer in the red zone at[rsp-0x18]and then stores through it. The pointer reads back as 0, which matches a fault resumed on that thread overwriting the red zone.630b18f0cherry-picked onto my branch), the game ran more than 14 minutes past that point without this crash.xXPBSXx commentedon Oct 11, 2026
Another title and host: DREDGE (PPSA09076, Unity), Windows 10, Ryzen 7 5800X3D (Zen 3, no AVX-512),
maina11c8f7. Intermittent0xc0000005inJob.Worker 4, 2 of 8 runs of 60 to 180 s, atapp.exe+0x17f0796(mov rax,[r8+0x30]withr8 = 0x600000000) and+0x17efe13(a read of0x600000004). The first routine loadsr8from[rsp-0x30]and usesvmovups ymm. WithAPS5_NO_WRITE_WATCH=1: 8 runs of 60 s, no fault. Details and the other checks (no mapping near0x600000000) are in #3915. I did not test #2054.AI-assisted (Claude Code).
queeeef commentedon Oct 12, 2026
A way to resume these faults without touching the guest stack: let a debugger process continue them.
With a debugger attached, Windows reports an exception to the debugger before the user-mode dispatch. If the debugger continues it with
DBG_CONTINUE, the thread resumes from the kernel's trap frame, and nothing is written belowrsp. Measured on an i5-13600K (Windows 11 26300): a leaf function fills 64 qwords belowrsp, then writes to a read-only page.rsp(of 64)rsp-0x108ymm1/ymm9dirtyrsp-0x8Local prototype
aps5-faultd.exe, attaches withDebugActiveProcess.WindowsMappingsmarks the pages it arms in a shared table, one byte per 16 KiB page, set before the page is protected.WindowsMappingsdrains the ring under its mutex, asHandleWriterecords a fault today.HandleSse4a.Result: Palworld (PPSA23126) used to crash at character creation within 30–60 s, at
app.exe+0x575bdc0or+0x4009f3, each a red-zone local read back as 0. With the helper it ran for over 2 minutes there without a crash. It handled 1,600–12,600 faults per 10 s, at the same frame rate as without it.Cost: each handled fault stops the whole process for one debug event round trip.
Unlike #2054, it keeps the shared views and their tracking unchanged. Would you take this as a PR, opt-in at first?
AI-assisted (Claude Code)
AlxcBTW commentedon Oct 12, 2026
Data point on the red-zone mechanism (thanks @parl0v for #44). It looks host-CPU dependent.
Windows builds the user-mode exception frame (CONTEXT + XSAVE area) right below the faulting
rsp, but it writes the XSAVE area with init optimization: a state component that is still in its initial state is skipped. So whether the 128-byte SysV red zone gets overwritten depends on the CPU's XSAVE layout (which component ends up directly underrsp) and on which vector registers are live when the fault happens.The repro below fills 4 KiB below
rspwith sentinels, takes one write fault on a read-only page, and lets a VEH fix the page and resume. It then reports the highest qword that changed. Ryzen AI 9 365 (Zen 5, AVX-512), Windows 11 26200:(The range covers the eight
rspalignments the repro tries.) On this machine Hi16_ZMM is the last component and sits directly underrsp, so the red zone is only hit while zmm16-31 hold data. None of the code our guest threads run through uses zmm16-31: ntdll, KernelBase, ucrtbase and vcruntime140 don't (checked by disassembly), our libs don't either, and the PS5's Zen 2 guest code has no AVX-512. So here the resumed shared-view faults never reach a guest red zone. That matches Sword of the Sea: with no view arming at all (0 resumed faults) our in-game Wwise heap crash still happens, so that crash is a separate bug.A CPU without AVX-512 (e.g. an i5-14400F) has no opmask/ZMM components above AVX. So the AVX upper halves (or whatever component comes last there) probably sit right under
rsp, and any live ymm register would land in the red zone. That would explain why the boot race reproduces on such machines and not on ours. I haven't been able to test a non-AVX-512 CPU. @parl0v, could you run it? If theymm0/ymm5 liveline says "inside the red zone", that confirms it.Either way, a clean run on one CPU says nothing about another, so leaf framing (
--frame-red-zone) or not resuming faults on guest threads are the host-independent fixes.rzxsave.c (gcc/clang, x64:
gcc -O1 rzxsave.c -o rzxsave.exe)parl0v commentedon Oct 12, 2026
Another title and host: Sword of the Sea (PPSA25344, UE5). Windows 10 19045, Intel i5-14400F (Raptor Lake, no AVX-512), RTX 4060, MinGW-w64 GCC. Base is the #44 working-group snapshot
sots/next(6b3c17ca4), which maps direct memory as views (#2054's private pages switched off there to avoid a red menu).Crash and proof. The boot crashes in 6 of 23 starts at
app.exe+0x37f77b3, a read of0x740in0x1437f74c0. That's a leaf called from a UEParallelForlambda that keeps an object pointer at-0x38(%rbp)withrsp = rbp-0x28, i.e. atrsp-0x10. I recorded every exception the vectored handler resumes (thread, rip, rsp, kind, fault address) and printed the crashing thread's newest on FATAL. 3 of 3 crashes look the same:+0x37f7706is a store inside the same leaf, between its good read of the slot and the bad one.Volume. About 41,000 resumed write faults in the first 40 s of boot, on about 41,000 distinct pages (only 18 pages faulted twice). So it's first-touch writes into ranges
Collectarmed, mostly texture-sized ranges written once by the streamer, not re-arm churn.#2054 on this title. With #2054's private pages switched back on (and
APS5_WRITE_WATCH_IMPORTS=watch, which the menu needs on that base), it's 0 crashes in 21 boots, against 6 in 23. View faults per boot fall under 8,192 (the counter's first step). The cost on this host is a 2.6× slower menu (3.0 vs 7.9 FPS, 2 runs each). The GPU driver reads about 96 GB of guest memory per 130 s instead of 9 GB, and copies about 9.7 GiB in and out of imports. So on this title #2054 trades the crash for speed until the import path is cheaper.How many functions are exposed. In SotS's relinked
app.exe, 604 of about 237,000 functions are leaves that keep values belowrsp. That's small enough to treat statically. I have a relinker prototype,--frame-red-zone(branchsots/red-zone-framingin my fork): for a leaf with apush rbp; mov rbp, rsp; push …prologue, a trampoline stub lowersrspby 128 after the pushes and raises it before eachpop …; pop rbp; retexit, so a fault's frame lands below the locals. It frames 80 of the 604 here, including0x1437f74c0. Frameless leaves addressing-N(%rsp)directly are the larger part and not done.Result with the prototype, shared views kept (no #2054): 25 of 25 boots clean, plus 2 smoke boots, against 6 of 23 without it. The fault volume is unchanged at 40,960 per boot; only the clobber is gone. Menu speed is unchanged too: 8.09 FPS vs 7.91/7.97. So for this crash, framing the exposed leaves fixes it without giving up the shared-view path, and it should be host-independent (no XSAVE layout dependence). It doesn't cover the frameless leaves, so other titles' crash functions (DREDGE's
[rsp-0x30]routine, Astro Bot's+0xec6090) need that part first.fdfawef324 commentedon Oct 13, 2026
Another host and title variant: ASTRO BOT (PPSA21567). Host is Windows Server 2025 build 26100, AMD Ryzen 9 5900X (Zen 3) and RTX 3060, building
mainplus open PRs.The crash. The audio thread
SceSndzAudioOutMaincrashes in the title's look-ahead limiter about 70-80 s after boot, in 5 of 5 runs. That leaf keeps its loop state in the red zone, fromrsp-0x8down torsp-0x30. Just before the crash, the thread takes write faults on tracked shared views at the limiter'svmovups ymmstore. Which slot is lost depends onrspalignment, and it matches the layout measured above:rsp % 64rsp-0x30rsp-0x10A standalone probe on the same host agrees. With live YMM upper halves, a vector-handler-resumed fault writes the area starting
rsp % 64bytes belowrsp. With clean YMM state, nothing belowrspchanges.The trigger. The title's direct-memory pool import is refused. The host import budget was 5973 of 6144 MiB live, so buffers fall back to the heap mirror. The faults come from
WindowsMappings::Collectarming those views. RaisingAPS5_HOST_IMPORT_MIBto 16384 removes the refusal, but the crash stays at the same site, still followingrspalignment.Fault-free tracking such as #637 or #2054, the debugger-continue approach, or relinker-side red-zone protection should all fix this title.
EagleBearerYS commentedon Oct 13, 2026
Another title and host: Resident Evil Village (PPSA01556, 01.210.000, RE Engine), on Windows 11 Home 26300 with an AMD Ryzen 9 5900HS (Zen 3 laptop, no AVX-512) and an RTX 3070 Laptop GPU. The base is
main18c438b with the open pull requests the title needs to get this far (listed in #5169).The crash. In 2 of 5 boots, GameThread reads
0x18atapp.exe+0x69cb959(mov 0x18(%rax),%rax).raxis read as 0 from an array of 0x30-byte entries that is all zeros.app.exe+0x1469cbb..). It keeps its loop state belowrsp, at-0x8to-0x28(%rsp)(a source pointer, counts, bounds), and copies the entries withvmovups ymm0/ymm1.app.exe+0x69cbc6bfaulting on a write-tracked view, 56 bytes below the caller'srsp.rsphold zeros and3f800000pairs, like YMM upper halves. The loop reloaded its state from them, stopped short and left zeros, which the caller dereferenced.#5052 doesn't cover this title. A relink with
--frame-red-zonereports297248 functions, 3036 leaf functions keep values below RSP, 0 framed; skipped 3036: no push rbp; mov rbp, rsp prologue. All of RE Engine's red-zone leaves are frameless.#2054 removes the faults, at a cost on Windows. It conflicts with
main, and on the same base it needed one more change. Since 9873fae the direct memory section isSEC_RESERVE, and #2054's copy window wrote into uncommitted section pages. That crashed 25 s into boot insceKernelMunmap→EraseMappings→SavePrivate, and committing the window's pages fixed it. With #2054, boot had 0 resumed write faults, but the title screen dropped from about 0.5 to 0.17 frames per second. Imports of the private direct memory are compared on every use: 121,462 compares, 1.6 TB and 170 s of CPU by the title screen, against 252 compares without #2054. That's the cost Sword of the Sea reported. I can post the conflict resolutions if they help.The title's audio thread (
AK::EventManager, Wwise) also crashes at a later scene change, on a list record that reads as if shifted by 4 bytes. Its nodes lie in a write-tracked range, so it may be the same mechanism, but I haven't shown that yet.johnmaia commentedon Oct 14, 2026
Still on current
mainwith Disney Epic Mickey: Rebrushed (PPSA15657, UE4). Windows 11, RTX 4080,maince5c839 unmodified, relinked with its relinker. About 3 minutes into the first level:This is the same leaf as before: eboot 0x55eaad0, a bounding-box tree refit that keeps a pointer below
rsp. Its null base comes from a red-zone slot that was overwritten. I haven't tried #637, #5596 or #5052 with this title yet.AI-assisted: yes (Claude Code)
tibzejoker commentedon Oct 14, 2026
#5596 (the port of #637 onto current
main, DotDebian's design) is merged. On Windows, direct memory mapped at a single address is now private write-watched memory, so CPU writes there no longer take the faults that overwrote the red zone. GTA V, which crashed this way with write watch on, now loads Story onmainwith it.AI-assisted (Claude Code).
oneandonlydean commentedon Oct 14, 2026
#5596 closed this on merge: singly mapped direct memory is now tracked without write faults on Windows, which removes the red-zone overwrite for those writes (GTA V, PPSA04264, no longer crashes loading Story). Faults on other paths are what #5052 covers.
@johnmaia your Epic Mickey run was on
ce5c83953, before #5596. Could you retry PPSA15657 on currentmain? If the leaf at eboot 0x55eaad0 still faults, post it here and this gets reopened.johnmaia commentedon Oct 14, 2026
@oneandonlydean retried PPSA15657 (Epic Mickey: Rebrushed) on Windows 11, RTX 4080, with
maina310431 unmodified and the game relinked with its relinker. Since #5596 the title no longer reaches the point where the red-zone fault happened. It stops earlier, during the level load (one run) or before the main menu's Continue (the other), on the debt throw from #5024:The same build with only #5596 reverted (
git revert -m 1 3e4fe4ce3), with the same input sequence, loads the level and stays in it for 10 minutes without that throw or any fault (8.5 FPS).maina310431maina310431 with #5596 revertedSo on this title #5596 moves storage images that have results pending onto memory that leaves the write watch without saved guest bytes, the case #5024 left as a throw. Whether the 0x55eaad0 red-zone fault is gone can't be checked until this is resolved. In the reverted run Mickey stood still in the level, while the earlier fault came after about 3 minutes of play. Logs are available if useful.
AI-assisted: yes (Claude Code)
AlxcBTW commentedon Oct 14, 2026
One more data point for the throw @johnmaia hit. Sword of the Sea (PPSA25344) on Windows 11, RTX 5070 Laptop, NVIDIA 617.42, on
main52d134c with #5596 plus our lab perf commits; none of those touchTexture.cppor the write watch. One fresh-dir run got through the post-New-Game window, where this title used to fault onFAsyncLoadingThread(#361: wild pointers, a 12 GB garbage allocation). 281 s later, still on the loading screen, it stopped on the same debt throw from #5024:So it isn't specific to Epic Mickey. With #5596, storage images with pending results land on memory that has left the write watch without saved guest bytes. On our current-14-based runs without #5596, the #361 faults hit about 30 % of New Game starts, so this is the one case that blocks both titles now.
oneandonlydean commentedon Oct 14, 2026
@johnmaia thanks for the bisect. On Windows, #5596 makes singly mapped direct memory private, so its host import is now a direct import that leaves the write watch (before, it went through a shared alias that stayed watched). A storage image whose results were still pending when its memory was first imported then had no saved guest bytes, and its next store threw.
#5657 saves those bytes, and the blocks the CPU wrote while the memory was watched, at the moment the range leaves the watch. Could you run PPSA15657 on it with the same steps?
fix(agc): keep an import's resident memory compared once it is shared
fix(agc): keep an import's resident memory compared once it is shared
fix(libc): refuse to collect a shared page marked to be compared
fix(libc): stop write-protecting resident runs on Windows
local(agc): owned proofs by write watch, no guest-thread write faults
fix(libc): stop write-protecting resident runs on Windows