Skip to content

Sifu (PPSA03001): RenderThread faults popping a free list whose head reads 0xFFFFFFFFFFFFFFFF #3209

Closed

Description

@xXPBSXx

AI-assisted (Claude Code), reviewed by the contributor.

Title: Sifu (PPSA03001, from the owner's own dump), Windows. Relinked from the reconstructed ELFs (eboot plus 31 prx, 37 modules); the import audit reports 0 absent imports.

Observed: the title runs for a while and then RenderThread 1 faults with an access violation, reproducibly (two runs, same pool):

FATAL: unhandled exception 0xc0000005 on thread 'RenderThread 1'
  rip app.exe+0x1bc2586   read of 0xffffffffffffffff
  rax 00000020193b0000 rsi 00000020193b0220 rcx ffffffffffffffff
  [rax] ffffffffffffffff ffffffffffffffff ...   (all 0xff)
  [rsi] ffffffffffffffff fffffffffffffffe ...

The second run faulted a few instructions away (app.exe+0x1bc2700) with rcx 00000020193b0060, so it is the same pool at 0x20193b0000.

What the code does (disassembly of the relinked app.exe around 0x141bc2586): it pops an entry from a free list. It loads the head pointer from [rsi], tests it for zero (empty list), decrements a counter and then reads [head]. The head here is 0xffffffffffffffff and the memory around it is all 0xff, so the zero test does not catch it and the next read faults.

Not established: who writes 0xFF into that region. It is either the title (a poison or "empty" fill that a later step is supposed to replace) or memory that our direct/flexible mapping hands out without the content the title expects. I did not find a 0xFF fill in libkernel/DirectMemory. I also did not identify which mapping call created 0x20193b0000: a gdb trace of the kernel memory calls stopped early under the debugger and did not reach that region.

If someone knows how this title's allocator initialises these pages, or has a quick way to log writes into a guest range, that would settle it. Other output of the run: many unsupported registered shader type 8 lines (shaders not prepared at registration), no other error.

Activity

  1. ArjenS-LA commented on Oct 11, 2026

    @ArjenS-LA
    Contributor

    There are two existing Windows diagnostics that may help trace this without the gdb session:

    $env:APS5_TRACE_MEMORY = "1"
    $env:APS5_WATCH_ADDR = "0x20193b0220"
    .\app.exe 2> sifu-memory-watch.log

    APS5_TRACE_MEMORY logs mapping, protection and release operations. APS5_WATCH_ADDR watches eight bytes at an absolute address and reports trapped writes with the writer?s location. The address above targets [rsi], the free-list head in your first capture; adjust it if the allocation moves between runs.

    Sources at main 961b25fe: memory tracing, write watcher.

    The watcher polls until the page is committed, so initialization can happen before it arms. It also temporarily makes the page writable while single-stepping. Consequently, no reported write would not rule out an earlier or concurrent write.

    Could you include the exact AnyPS5 commit/extra patches, the mapping entry covering this address, and any [watch] output before the crash? That would help distinguish incorrect initial contents from a later overwrite. I checked these diagnostic paths in source; I haven?t reproduced Sifu locally.

  2. xXPBSXx commented on Oct 11, 2026

    @xXPBSXx
    ContributorAuthor

    Update: this is fixed by the sceKernelAvailableDirectMemorySize change from #3143 (branch fix/libkernel-available-direct-memory-largest-block), no other change involved.

    Check: same relinked Sifu, same machine, same libraries, only that one file differs.

    • With the change: a 15 s run and a 70 s run (APS5_TRACE_MEMORY=1) with no fault; the 70 s run was still going when I stopped it, compiling shaders and creating textures, no error line.
    • With that file's change reverted (all else equal): RenderThread 1 faults again at the same place, app.exe+0x1bc2586, read of 0xffffffffffffffff.

    My reading, not verified: the title sizes or places its pool from what sceKernelAvailableDirectMemorySize reports, and the first-gap answer led to the pool being used before it was filled. I have not looked at why the memory reads as 0xFF in the failing case. The issue can be closed together with #3143 once that change is merged.

    AI-assisted (Claude Code).

  3. xXPBSXx commented on Oct 11, 2026

    @xXPBSXx
    ContributorAuthor

    Thanks to @AchrafReyani for carrying the sceKernelAvailableDirectMemorySize change (#3292, merged); that is the change that fixed this Sifu fault in my bisect (see above), so this issue should be fixed on main. I have not rerun Sifu on a pure main build since the merge, only on my test branch.

    AI-assisted (Claude Code).

  4. oneandonlydean commented on Oct 11, 2026

    @oneandonlydean
    Collaborator

    Closing: fixed by #3292, per the bisect above. Reopen if PPSA03001 still faults there on plain main.

  5. xXPBSXx commented on Oct 11, 2026

    @xXPBSXx
    ContributorAuthor

    This issue is closed, but I could not confirm the fix on current main; reopen if you agree. Sifu (PPSA03001), relinked from the PPSA03001_elf_v2 reconstruction with the relinker of main f990506 (37 modules; the import audit shows 1 absent import, sceSaveDataDialogUpdateStatus), Windows 10, 90 s runs with the libraries of main f990506, ca4ab0d and a11c8f7 (the change from #3292 is in all of them): the run does not reach the render thread, it stops in Unreal's start-up with PS5 Out of Memory ... UsedPhysical 9278849024, AvailablePhysical 4902027264 and Ran out of memory allocating 5565054976 bytes with alignment 65536, followed by a fault in the title's own libc.prx. Demon Slayer (PPSA03490) stops on the same allocation (see #3073). I do not know whether this differs from the setup in which the RenderThread 1 fault was fixed (a different reconstruction or data set would explain it); the free-list fault could not be re-tested because the title stops earlier.

    AI-assisted (Claude Code).

  6. xXPBSXx commented on Oct 12, 2026

    @xXPBSXx
    ContributorAuthor

    Bisect result for the Sifu change since the earlier fix, with evidence. Setup: Sifu (PPSA03001) relinked once (the same app.exe for every step, so only the libraries differ), Windows 10, main libraries built per commit, git bisect over first-parent merges, each step a 60 to 75 s run under the game lock.

    This is a candidate cause with a reversible test, not a proposed fix: reverting it would affect the title #3238 was written for.

    AI-assisted (Claude Code).

  7. xXPBSXx commented on Oct 12, 2026

    @xXPBSXx
    ContributorAuthor

    Mechanism for the PlayGo cause of this stop, with the trace data and a narrower condition proposed to the author of #3238: #3238 (comment) . In short: Sifu asks about chunk ids one at a time in ascending order until a call fails; the old check fails at chunk 1 (two calls in all), the new one accepts every id (4,023 consecutive ids in a 60 s traced run) and the title's later Ran out of memory allocating 5565054976 bytes is exactly 65536 * 84916 (an inference, not traced).

    AI-assisted (Claude Code).

  8. xXPBSXx commented on Oct 12, 2026

    @xXPBSXx
    ContributorAuthor

    Sifu (PPSA03001) on current main d371ed3 with the old PlayGo chunk check restored locally (one line in IsKnownChunk, not committed; see the mechanism comment above), Windows 10, one 150 s run (the same app.exe as in the bisect): no abort, no fatal error, no Ran out of memory. The log shows the title mounting its save data (SifuProfile, SifuProfile-1), shader preparation (918 shaders written in one 10 s window, 3,734 misses in all), and only these warnings: unsupported registered shader type 8; it is not prepared at registration (6 lines), its program address is not in the shader registers; preparing it when it is used (3), [gpu] storage image ...: memory no longer registered; N pending ranges dropped (4). I did not see the earlier RenderThread free-list fault or the hole abort. What I did not check: the picture, the frame rate, anything after 150 s.

    So on current main the only start-up stop I can attribute is the PlayGo chunk acceptance from #3238; without it Sifu gets through its start-up and into shader and texture work.

    AI-assisted (Claude Code).

  9. xXPBSXx commented on Oct 12, 2026

    @xXPBSXx
    ContributorAuthor

    Regression sweep on current main (ceb342b, plain, no local change; Windows 10, libraries built from that commit, one 60 s run per title under the game lock). The app.exe of each title is the one relinked earlier (the relink dates differ; libraries only gain exports, so older relinks load with newer libraries). A single 60 s run cannot judge intermittent faults.

    Title Stop now (60 s) Before Regression?
    Sifu (PPSA03001) silent after /app0/.. open, no fatal (the chunk enumeration of #3238, see #3209; later hole abort / allocation error) ran into shader and texture work (main before #3238) yes, known: #3238 (see #3209)
    Hades (PPSA03355, --module-dir . layout) no fatal start fixed by --module-dir . (#3850); intermittent RenderThread null memcpy (#2724) no (intermittent)
    DREDGE (PPSA09076) no fatal intermittent Job.Worker fault, 25 to 30 % of runs (#3915) no (intermittent)
    Cult of the Lamb (PPSA06465) no fatal ran 180 s clean no
    Harmony's Odyssey (PPSA16827) no fatal ran 180 s clean no
    Lies of P (PPSA10261) no fatal ran 150 s clean no
    Stray (PPSA02100) no fatal; unsupported registered shader type 8 warnings same warnings (#2749) no
    RE4 (PPSA07411) FAIL: unresolved ELF import WVZBP4IyM+E (setSpecialFloatFormatType) same, 8 absent imports (#2776) no
    Tomba! 2 (PPSA29866) FAIL: unresolved ELF import WkYtyOg30do (libSceVideoOut) exports in open #3819 (#3646) no
    Teardown (PPSA15246) sceNpAuthGetIdTokenV3 not implemented #3701 listed the libSceCesCs imports, which load now no (later stop)
    PPSA25936 storage image access to depth/stencil surface ... is not implemented same (#1409) no
    Hotline Miami (PPSA09682) fault in Loading.PreloadManager (app.exe+0x7a6730) same, deterministic (#3805) no
    Dead Cells (PPSA15552) NGS2: rack id 0x4001 is not implemented same (#3553) no
    Cloudberry Kingdom (PPSA23827) kqueue_nid_postfix not implemented same; #3764 open no

    One real regression, already reported (Sifu, #3238). Not covered: pictures and frame rates (log only), runs longer than 60 s, Linux.

    AI-assisted (Claude Code).

  10. xXPBSXx commented on Oct 12, 2026

    @xXPBSXx
    ContributorAuthor

    Sifu (PPSA03001) follow-up to the regression sweep: the fault at libc.prx.guest.prx+0x14270 is not new on current main, it depends on run length.

    Same relinked directory, 3 runs each at 60 s and 2 runs each at 90 s, libs from main ceb342b94 vs. current upstream/main (52 commits later):

    Run length main ceb342b94 current main
    60 s 3/3 silent (err=1), no fatal 3/3 silent (err=1), no fatal
    90 s 2/2 FATAL 0xc0000005, libc.prx.guest.prx+0x14270 2/2 same fault, same offset

    The fatal appears between 60 s and 90 s on both. My earlier "60 s silent" result was therefore too short to see it; it does not show a regression between the two commits. What the register dump shows: a read of 0xffffffffffffffff (an invalid pointer passed into a libc routine, rbx points at a deadbeefcafebabe block), not a write or memcpy into a null destination, so it does not look like the Hades memcpy signature at +0x141b0. I have not identified the calling guest function, and I have not run the PlayGo-revert variant at 90 s, so I cannot say whether #3238 plays a part.

    AI-assisted (Claude Code).

  11. xXPBSXx commented on Oct 12, 2026

    @xXPBSXx
    ContributorAuthor

    Sifu (PPSA03001) crash at libc.prx.guest.prx+0x14270, narrowed down with gdb (-batch, one run under the game lock, current main libs):

    • The instruction at the fault address is int $0x45 followed by nop; ud2 inside the guest libc function l3fh3QW0Tss (+3360). That is a deliberate trap sequence, not a bad load or store; the "read of 0xffffffffffffffff" in our report is how the exception is reported for this trap. I could not map the NID l3fh3QW0Tss to a name (it is not in core, and abort, _Exit, exit, __stack_chk_fail, __cxa_pure_virtual, _exit, longjmp, raise have other NIDs), so I do not claim which libc function it is.
    • Immediately before the trap the title prints (in the gdb run and in the three latest plain runs I checked): PS5 Out of Memory: AvailablePhysical: 4902486016 UsedPhysical: 9278390272 and Ran out of memory allocating 5565054976 bytes with alignment 65536 (twice, LowLevelFatalError). 5565054976 = 65536 x 84916, the same request as in the fix(libScePlayGo): accept chunk ids when the dump has no chunk definitions #3238 chain (PlayGo chunk enumeration, see my earlier comments). So the fatal exit is the title's own fatal-error path after that failed allocation, not memory corruption.
    • 0xdeadbeefcafebabe is our value: __stack_chk_guard in core/libs/prx/libc/src/specifics/gcc/StackGuard.cpp:4. In the dump rbx is the address of that variable (gdb symbol <__stack_chk_guard>) and the stack words are saved canaries. It is not an engine or allocator poison pattern, and it is harmless here. A search of the Sifu eboot.bin for the byte pattern finds nothing.

    Not established: why the OOM fatal only shows up after more than 60 s in my runs, and whether it also appears on a main without #3238 for 90 s (I have only the earlier 60 s revert result). Single title, one gdb run plus four plain 90 s runs.

    AI-assisted (Claude Code).

  12. xXPBSXx commented on Oct 12, 2026

    @xXPBSXx
    ContributorAuthor

    Sifu (PPSA03001), where the 5565054976-byte failure comes from. One 100 s run with APS5_TRACE_MEMORY=1 on current main libs (the run ended with the same libc.prx+0x14270 trap), plus host numbers.

    • No kernel call fails. The whole memory trace (594 lines before the fatal) contains no failure or error line; the last entries are ordinary unmap / map direct / remap fixed calls. The request of 5565054976 bytes (65536 x 84916) never reaches our kernel as an allocation: the title refuses it in its own allocator and prints PS5 Out of Memory: AvailablePhysical: 4902486016, UsedPhysical: 9278390272 first. So there is no error code from us to report for it.
    • The title takes all direct memory at start-up. The first allocate physical calls are halving chunks (0x12c00000, 0x1b0000000, 0xd8000000, 0x6c000000, 0x36000000, 0x1b000000, ...) that end exactly at physical offset 0x360000000, i.e. our sceKernelGetDirectMemorySize of 0x360000000 (13.5 GiB), and then it sub-allocates itself. Its Available + Used = 14180876288, about 300 MB below 0x360000000 (14495514624). I did not find where those two numbers come from (no call to sceKernelAvailableDirectMemorySize is visible in this trace; I only looked at the [memory] lines).
    • Host is not the limit: during the queue before the run Windows reported 64 GB RAM with 39 GB free and a 74.7 GB commit limit with 28 GB free (Win32_OperatingSystem), and the failing request is never issued to Windows. I did not sample the peak commit of app.exe during the run.
    • Timing: runs of 60 s end quietly (no OOM line), runs of 90 s and 100 s end with it, so the request comes later than 60 s; I have no timestamps in the logs to say when exactly.

    What this does not show: why the engine computes 5.5 GB. The earlier bisect ties it to the PlayGo chunk enumeration after #3238 (reverting that one line avoided it at 60 s), but I did not re-run the revert at 100 s with this trace. So: not a limit of ours in the kernel path and not host commit; the number is the title's own, derived from state we provide (PlayGo chunk data, direct-memory size).

    Correction (added later): I wrote that no kernel call fails in this trace. That is not supported: APS5_TRACE_MEMORY prints the arguments of the calls that succeed, not their return values, and sceKernelAllocateDirectMemory returns EAGAIN without any trace line. So the trace shows which calls were made, not that they all succeeded. I am re-running with return codes logged and will post the result.

    AI-assisted (Claude Code).

  13. xXPBSXx commented on Oct 12, 2026

    @xXPBSXx
    ContributorAuthor

    Sifu (PPSA03001) PlayGo revert test at 100 s on current main (d146ec1b6), local one-line change only (IsKnownChunk returns chunks.ids.contains(id) as before #3238), not committed; same relinked directory, alternating order:

    Libs Runs Ran out of memory allocating 5565054976 + libc.prx+0x14270 fatal
    main, 100 s 2 2 of 2
    main, 90 s (earlier runs, same relink) 4 4 of 4 (2 of them on ceb342b94)
    main + PlayGo revert, 100 s 4 1 of 4

    With the revert, 3 of 4 runs stay alive for the full 100 s (no fatal, no OOM line; the title reaches shader registration and logs Shader 0x2008e60000 has unsupported registered shader type 8), one run hits the same OOM and the same trap as main. Without the revert, 6 of 6 runs fail. Fisher exact (6/6 vs 1/4): p = 0.033, two-sided. So #3238 is a major factor for the 5565054976-byte request, but not the only one: it still appeared once with the old check.

    Reading the code for the numbers in the title's message: sceKernelGetDirectMemorySize returns the fixed DIRECT_MEMORY_SIZE (0x360000000), sceKernelConfiguredFlexibleMemorySize and the flexible total are the fixed 448 MiB (FLEXIBLE_MEMORY_SIZE, core/libs/prx/libkernel/DirectMemory/Export.cpp:37), and sceKernelAvailableFlexibleMemorySize is that minus image segments and flexible usage. The title's AvailablePhysical + UsedPhysical = 14180876288 is not the direct size (14495514624) nor direct + flexible (14965276672), so I could not reproduce its sum from our values and do not know which calls it uses. UsedPhysical of 9.28 GB cannot come from our flexible number (448 MiB total).

    Limits: one title, 4 + 6 runs, change was a local revert of the check (a test, not a proposed fix, since it undoes the behaviour change of #3238); I did not trace where the 5565054976 is computed.

    AI-assisted (Claude Code).

  14. oneandonlydean commented on Oct 12, 2026

    @oneandonlydean
    Collaborator

    The free-list fault this issue is about is fixed: your runs just before and after #3292 are clean. The out-of-memory stop at start-up is a separate bug, most of it #3238 by your numbers, and PPSA03490 hits it too. Please open an issue for it with the bisect, the chunk trace and the 100 s table, or a PR with the file test you proposed on #3238.

  15. xXPBSXx commented on Oct 12, 2026

    @xXPBSXx
    ContributorAuthor

    Sifu (PPSA03001) trace of the PlayGo and direct-memory calls on current main (d146ec1b6), 4 runs of 100 s with temporary stderr logging in scePlayGo validation (ValidateChunks, local only, not committed) and in sceKernelGetDirectMemorySize / sceKernelAvailableDirectMemorySize:

    • The title queries PlayGo one id at a time (n=1, first=0,1,2,...) in a loop that never ends while every id is accepted: 7.3-7.8k logged lines per run with a log line every 5000 calls, i.e. about 36-39 million validation calls in 100 s (last counter values 36290000 to 38875000 in the runs where I read it). first wraps at 65536 (counter 70000 shows first=4463), because chunk ids are uint16_t. In those runs the chunk table is undefined, so IsKnownChunk returns true for every id (core/libs/prx/libScePlayGo/Export.cpp:73).
    • 84916 is not a count of PlayGo calls or of distinct ids: the counter passes 84916 in the first second in every run, and chunk ids cannot exceed 65535, so 65536 x 84916 does not come from a number of distinct chunk ids.
    • sceKernelAvailableDirectMemorySize is called 0 times in the 4 runs; sceKernelGetDirectMemorySize 4 times at start-up. So the title's "AvailablePhysical" does not come from the largest-free-block call.
    • Important limit: with this logging build none of the 4 runs reached the Ran out of memory allocating 5565054976 message or the trap (OOM=0 in all 4), while the same main without logging fails in 6 of 6 runs of 90 to 100 s. The logging changes the timing enough that I could not observe the OOM with the counter, so I cannot say which call count (if any) precedes the failed allocation. I did not test a count limit.

    What it does show: with the chunk table undefined, the title's enumeration only ends on a failing call, and with the old check (chunks.ids.contains(id), empty table) the first call fails (3 of 4 revert runs survive 100 s). Whether a bound on accepted ids would fix Sifu is untested.

    AI-assisted (Claude Code).

  16. xXPBSXx commented on Oct 12, 2026

    @xXPBSXx
    ContributorAuthor

    Hades (PPSA03355, Windows) memory trace around the intermittent RenderThread fault (libc.prx.guest.prx+0x13a15, write of 0), current main d146ec1b6, APS5_TRACE_MEMORY=1, 150 s runs, same relinked directory:

    • The trace has no failing kernel call and no Failed to allocate line in any run. The title takes 4 GiB, then 256 MiB, 3 GiB and 64 MiB of direct memory (about 7.3 of our 13.5 GiB) and then repeatedly does allocate physical 256 MiB -> map direct -> unmap <same address>+0x0 (the length printed is 0). No release physical follows, so each round keeps its 256 MiB (5 to 8 rounds in 150 s).
    • Our sceKernelMunmap returns EINVAL for a length of 0 (DirectMemory.cpp, DoMunmap). shadPS4 (src/core/libraries/kernel/memory.cpp, sceKernelMunmap) and KytyPS5 (source/emulator/src/Kernel/Memory.cpp, KernelMunmap) do the same. These are reimplementations, not console measurements; I found no source for what the console returns.
    • The map/unmap(0) pattern is not what separates failing from passing runs: it appears in the clean traced runs too (2 of the 3 clean traced runs show it; the first clean run, with 10 memory lines, had none). The one failing traced run (run 1) crashed right after its third map; its last memory lines are the same as in the clean runs up to that point.
    • Local test, not committed: sceKernelMunmap(addr, 0) returning 0 instead of EINVAL. 150 s runs: with the change 0 of 2 fatal (7 and 8 allocation rounds), plain main 1 of 2 fatal (plus 1 of 3 in the earlier traced runs, so 2 of 5 for main). 0/2 against 2/5 is nothing to conclude from (Fisher p = 0.47), and I am not proposing the change: it would deviate from three implementations without a console source.
    • Kernel side: the direct-memory pool (MemoryPool.cpp) takes one mutex in every method (lock order pool mutex, then g_directLock); I did not find a path where a thread sees "not enough memory" without a failing return. A stress test would not have a failing case to reproduce, so I did not write one.

    So the 'sizes too small' idea is not supported for Hades (no failing call, about 46 % of direct memory unused at that point), and the failing runs crashed early (after 3 to 5 allocation rounds). Not established: what the title does with the unmap result, and why only some runs end with the null write.

    Correction (added later): I wrote that no kernel call fails in this trace. That is not supported: APS5_TRACE_MEMORY prints the arguments of the calls that succeed, not their return values, and sceKernelAllocateDirectMemory returns EAGAIN without any trace line. So the trace shows which calls were made, not that they all succeeded. I am re-running with return codes logged and will post the result.

    AI-assisted (Claude Code).

  17. xXPBSXx commented on Oct 12, 2026

    @xXPBSXx
    ContributorAuthor

    Follow-up to my two memory-trace comments above (Sifu OOM, Hades memory trace), now with return codes. Temporary local build of current main (2ca6198e9) that prints the result of sceKernelAllocateDirectMemory, sceKernelMapDirectMemory / MapNamedDirectMemory and sceKernelMunmap (not committed). Not instrumented: sceKernelMapFlexibleMemory, ReserveVirtualRange, ReleaseDirectMemory, sceKernelMmap-family.

    Hades (PPSA03355), 3 runs of 150 s, none fatal: all AllocDM and MapDM calls return 0. The only non-zero results are sceKernelMunmap(addr, 0) -> 0x80020016 (EINVAL), 4 to 5 per run, one per 256 MiB round. That is the same call I described before; it occurs in these clean runs as well. The failing run I caught earlier (RenderThread fault) was not reproduced with this build, so I still do not know what the title does with that EINVAL.

    Sifu (PPSA03001), 2 runs of 100 s, both ending in the libc.prx+0x14270 trap: every MapDM returns 0; 18 AllocDM calls return 0x80020023 (EAGAIN). These are the title's halving probes at start-up: after a 0x12c00000 first block it asks for 0x360000000 (all 13.5 GiB, EAGAIN), then 0x1b0000000 (ok, then EAGAIN for the same size), 0xd8000000, 0x6c000000, ... down to 0x10000, i.e. it takes everything that is free in power-of-two steps, each size until the first refusal. So the refusals are the title finding the end of the pool, not a failure of a request it needs. No AllocDM call with the failing size 0x14bb40000 (5565054976) occurs, so that request is refused inside the title's own allocator before any of these kernel calls (the flexible/reserve paths are not covered by this log).

    What this changes: the earlier statement that no kernel call fails is now backed by return codes for the direct-memory calls (apart from the EAGAIN probes and the munmap(addr, 0) EINVAL). It does not explain why the title wants 5565054976 bytes (see the PlayGo comments).

    AI-assisted (Claude Code).

  18. xXPBSXx commented on Oct 12, 2026

    @xXPBSXx
    ContributorAuthor

    Sifu (PPSA03001) test of a bound on the accepted PlayGo ids when no chunk definition exists: local change only, not committed (IsKnownChunk returns id < 256 when the table is undefined, the defined case unchanged), compared with plain upstream/main 32e62f2f7, 4 alternating runs of 100 s each, same relinked directory:

    Libs fatal libc.prx+0x14270 trap with Ran out of memory allocating 5565054976 other outcome
    main 3 of 4 1 run without OOM, ended later in a shader-registration stop (unsupported registered shader type 8) plus an app.exe fault
    main + id bound 256 2 of 4 1 run without OOM and without fatal (stop line only), 1 like the main run above

    So a bound of 256 does not make Sifu stable: the OOM and the trap still occur in 2 of 4 runs, and plain main also varied (1 of 4 without OOM), which is the run-to-run variation I could not explain earlier. I am therefore not proposing this bound. Facts that matter for any proposal: Guardians of the Galaxy (#3238) needs ids 1 to 10 accepted without a chunk table; Sifu's dump has no sce_sys/playgo-chunk.dat (the file SleglJan describes in #3238 with 64 to 100 chunks in other titles), so reading the real count from that file, as SleglJan's fork branch does, would not help Sifu either. What would make the PlayGo loop end for Sifu without breaking Guardians is not established.

    AI-assisted (Claude Code).

  19. xXPBSXx commented on Oct 12, 2026

    @xXPBSXx
    ContributorAuthor

    Sifu (PPSA03001): where the 5565054976-byte request comes from, and a correction to my earlier id-bound test. All on 32e62f2f7, Windows, 100 s runs.

    The allocation. The title's fatal-error report (Ran out of memory allocating ... alignment 65536) is called from its allocator; the frame chain of the trap run leads to a container growth routine (app.exe+0x21de26 in my relink: movsxd rsi,ecx; shl rsi,4; call <allocator>), i.e. an array of 16-byte elements. 5565054976 B is 65536 x 84916 after rounding up to the 64 KiB allocation alignment, so the requested element count is about 347.8 million; with the title's growth step that means roughly 253 million elements before the growth. Reading this from disassembly only; I did not break in the routine.

    The cause, measured. With a temporary counter in ValidateChunks (the check behind scePlayGoGetLocus and the other chunk calls; printed every 10 million calls, not committed), 3 of 3 runs reached at least 250,000,000 calls (last printed value 250000000, the next print at 260000000 not reached) and then ended with the OOM message and the libc.prx+0x14270 trap. That matches the ~253 million elements above. The same title spins on scePlayGoGetLocus with n=1 and an ever increasing id (my earlier trace), because with no chunk definition every id is accepted, and each call seems to add one 16-byte entry on the title's side. That also explains why the earlier logging builds did not reach the OOM within 100 s: they were slower per call.

    Correction. I reported that a bound of id < 256 for the no-definition case left the OOM in 2 of 4 runs. I now think that comparison used a stale library in some runs (alternating copies into the same work directory; I did not hash them then). Re-run with the library hash checked against the work directory's copy: bound id < 256: 4 of 4 runs without OOM and without fatal for the full 100 s (the title reaches unsupported registered shader type 8 in the shader registration, and continues); the bound plus the counter build: another 4 of 4 without OOM, the counter never reached 10 million. Without the bound: 3 of 3 OOM (counter build), plus the earlier 6 of 6.

    What this supports. For this title the unbounded acceptance of chunk ids when there is no chunk table is the trigger. A bound of 256 stops it; it is an assumption, not a measured console limit (the packages SleglJan looked at in #3238 have 64 to 100 chunks, I found no documented maximum). Guardians of the Galaxy (#3238) needs ids 1 to 10 accepted without a table; 256 keeps that. Not tested: Guardians itself (not in my set), other titles, and the guest_playgo unit test, whose current case expects id 0xFFFF to be accepted and would need to change. This is a test result, not a PR.

    AI-assisted (Claude Code).

  20. xXPBSXx commented on Oct 12, 2026

    @xXPBSXx
    ContributorAuthor

    State of the PlayGo bound for Sifu (PPSA03001), after the pre-crash runs were repeated:

    • Branch with the change and the adapted guest_playgo case, not opened as a PR: https://github.com/xXPBSXx/AnyPS5/tree/fix/playgo-bound-without-chunk-table (e3c0ffc00, one line in Export.cpp, the test, one TechnicalDebt.md sentence). guest_playgo fails on main with the adapted case and passes with the change; conventions check passes.
    • Final build, 6 runs of 100 s on the title, library hash checked: no OOM message, no fatal in any run (the title reaches its shader registration); without the change 9 of 9 runs ended in the OOM and the libc.prx+0x14270 trap.
    • Why this case at all: main reads the chunk count from sce_sys/playgo-chunk.dat (fix(libScePlayGo): take the chunk set from sce_sys/playgo-chunk.dat #3435, Export.cpp:33-44), but Sifu's dump has no such file, so it falls into the remaining case "no table: accept every id" (IsKnownChunk, Export.cpp:71-74).
    • Options as I see them: (a) put the real playgo-chunk.dat into the dump (then fix(libScePlayGo): take the chunk set from sce_sys/playgo-chunk.dat #3435 applies and no bound is needed; I cannot do this, the file is not in my copy of the title); (b) the bound in this branch, with the risk that a title without a table that uses ids of 256 or more would fail; a "highest id requested so far" rule would be stateful and I do not recommend it.
    • Not measured: where the 16-byte entries are created on the title's side (scePlayGoGetLocus only writes out_loci), and what the console returns for an unknown id.

    AI-assisted (Claude Code).

  21. xXPBSXx commented on Oct 12, 2026

    @xXPBSXx
    ContributorAuthor

    The change is now open as #4491 (ids below 256 accepted without a chunk table; Sifu, Demon Slayer and Returnal numbers in its description). The chunk count read from playgo-chunk.dat (#3435) and the Guardians case of #3238 (ids 1 to 10) are unchanged.

    AI-assisted (Claude Code).

  22. xXPBSXx commented on Oct 12, 2026

    @xXPBSXx
    ContributorAuthor

    Demon Slayer (PPSA03490): the GameThread timed out waiting for RenderThread after 120.00 secs stop seen once with the PlayGo bound (1 of 3 runs, ended at about 144 s on main ffa198407 + bound). Not reproduced since, so this is what the one saved dump shows and what is not known.

    • The fatal handler's all-thread dump of that run lists 128 threads, 80 of them unnamed, and has no thread named RenderThread (named ones include RHIThread, TaskGraphThread*, PoolThread*, AgcSubmissionThread, AgcInterruptThread, AgcCleanupThread). In the Hades RenderThread fault of Sifu (PPSA03001): RenderThread faults popping a free list whose head reads 0xFFFFFFFFFFFFFFFF #3209 the same handler printed the name, so the name is set in at least that title. Possible readings: the render thread is not running, or it is among the unnamed ones. I cannot tell which from this dump: the unnamed threads show only rip and rsp (frame chain rbp is 0), no frames.
    • RHIThread waits in libkernel.prx+0x36475 (the same wait as the TaskGraphThread* workers), entered from app.exe+0x2db43b8.
    • Not shown: what the render thread waits for, or whether it exists. I ran 4 more runs of 240 s under gdb (batch, catching only the int 0x45 trap, library hash checked) with the PlayGo bound: none of them reached the OOM, the trap, or the 120 s timeout, i.e. the hang did not reproduce under gdb (timing differs); that is 0 of 4 here and 1 of 7 across the bound runs on this title overall (the 3 plain runs and these 4).
    • Next step I would take if wanted: log thread creation, naming and exit in libkernel (local build) and run until the hang shows up, to see whether the render thread exists in the hung run. Not done.

    AI-assisted (Claude Code).

  23. xXPBSXx commented on Oct 12, 2026

    @xXPBSXx
    ContributorAuthor

    Hades (PPSA03355), the intermittent RenderThread null write (libc.prx+0x13a15, about 3 of 8 runs): what the relinked image shows (static, objdump of app.exe from main 32e62f2f7, no dynamic proof), and what is still open.

    • Register state at the fault (from the fatal dump): memcpy(dst = 0, src = 0x2503b20e0, n = 0x1e10); src lies in the 4 GiB direct-memory window the title maps at 0x200000000. The caller is a row-copy loop (app.exe+0x318059, inside the function at app.exe+0x317bf0), called from app.exe+0x30a456.
    • That function takes a texture id (edi), a source buffer (rsi) and a size (edx). It returns early when rsi is null (test rsi,rsi; je), so the source is non-null here (it is the src above). It then looks the texture up by id ([obj+0xc] == id) in a global vector of 16-byte entries and copies rows into a destination derived from the found object. The destination base being 0 therefore comes from that lookup result, not from the source buffer.
    • The caller (0x30a437..0x30a456) checks a local pointer for null before the call and stores it into the node's texture object ([r15+0x18]+0x18), then calls the copy by id. If the copy then finds a different object for that id, or one whose mapped pointer is still 0, it writes through 0. That would fit an intermittent, timing-dependent fault between the thread that creates the texture and the render thread; I did not verify it.
    • What I could not establish: which thread fills that field and when, whether a failed allocation (sceKernelAllocateDirectMemory, MapDirectMemory, malloc) is involved, and whether it is a race in the title that the console's timing hides. Return codes for the direct-memory calls in 3 clean Hades runs and the Sifu runs are in an earlier comment; none failed except sceKernelMunmap(addr, 0).
    • Dynamic runs (gdb catching the fault) were started and stopped before one caught it (one early catch was a different, harmless fault in app.exe).

    AI-assisted (Claude Code).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions