Skip to content

AGC: a guest unmap of direct memory that an already submitted DCB still reads stops Doom 3 BFG (PPSA99313) at "guest memory is not readable at 0x4009b00000" #8648

Open

Description

@dantdmdarealguy

Summary

Doom 3 BFG (catalog homebrew PPSA99313, griches/PS5_Doom3 8a4afc76, RADV from mihawk-99/PS5_Mesa 7b59ef27) stops about 6 s after start on main c70524a (Windows):

[unreadable] guest memory is not readable at 0x4009b00000 (range 0x4009b00000+0xc) from +0x185a31
[gpu] driver failure: AGC driver: guest memory is not readable at 0x4009b00000 (range 0x4009b00000+0xc)
terminate called after throwing an instance of 'std::runtime_error'
  what():  AGC driver: guest memory is not readable at 0x4009b00000 (range 0x4009b00000+0xc)

This is not an SRT or shader read. +0x185a31 in libSceAgcDriver.prx is the return from GuestMemory::CheckRange in prepareDrawInputs (Graphics/src/Draw.cpp:1089, the draw.indexed index-buffer check). The draw is DRAW_INDEX_2 with index base 0x4009b00000, 6 uint16 indices (VulkanDevice::Draw indices=6 instances=1 indexSize=2 address=0x4009b00000).

The title unmaps and releases the 128 MiB direct-memory block holding that index buffer after it has submitted the DCB with the draw, but before the queue worker reaches the draw. The DCB's end-of-submission fence was not written yet. AnyPS5's munmap pinning (GuestAllocationsRequireUnpinned / WaitForLeasesAndImports) only covers work that has already been recorded. Nothing holds memory that a queued DCB will read later.

Reproduction

  1. relinker --windows --registry eboot.elf app.exe (eboot unwrapped from the catalog release). Then copy libs/*.prx and the three MinGW DLLs, and copy app0 (no game data: the title expects the user's Doom 3 BFG base/).
  2. Run APS5_TRACE_UNREADABLE=1 APS5_TRACE_MEMORY=1 APS5_TRACE_GPU=1 APS5_TRACE_LABEL=200300000 app.exe.

It stops in 9 of 9 runs: cold and warm shader cache, and with #8295 merged.

Evidence

The block was mapped once at start-up and unmapped once:

[memory] map direct 0000004007c00000+0x8000000 phys=0x18600000 prot=0x33 flags=0x0 align=0x200000
...
[memory] unmap 0000004007c00000+0x8000000
[memory] release physical 0x18600000+0x8000000

The queue is a single queue 0x0. Every DCB ends with RELEASE_MEM ... 0x200300000 <seq>, the RADV PS5 winsys queue marker. The DCB with the draw (serial 17, 1701 dwords) has seq 9. Warm cache, APS5_TRACE_GPU plus APS5_TRACE_LABEL=200300000:

[gpu] 2468.3 execute serial=15 queue=0x0 dwords=190
[label] ... RELEASE_MEM 0x200300000: 0030c514 20000000 00300000 00000002 00000008 ...
[gpu] 2468.5 done serial=15 queue=0x0
[gpu] 2504.0 submit queue=0x0 dwords=1701 at 0000000200413840
[gpu] 2504.1 execute serial=17 queue=0x0 dwords=1701
[gpu] 2505.1 submit queue=0x0 dwords=1570 at 0000000200415300
[gpu] 2544.0 queue 0x0 COND_EXEC at DWORD 868 reads 0x0 at 0x40ab0f0008: skips 3 dwords
[gpu] 2548.2 queue 0x0 COND_EXEC at DWORD 1166 reads 0x0 at 0x40ab0f0008: skips 3 dwords
[memory] unmap 0000004007c00000+0x8000000
[gpu] driver failure: AGC driver: guest memory is not readable at 0x4009b00000 (range 0x4009b00000+0xc)

The marker still holds 8 when the title unmaps, about 45 ms after it submitted seq 9. Excerpt of serial 17 (APS5_DUMP_QUEUE=0, register writes omitted):

[queue]   DMA_DATA 60200000 00325800 00000002 00325800 00000002 800006c0
[queue]   DMA_DATA 60200000 003a0000 00000002 003a0000 00000002 80000020
[queue]   DRAW_INDEX_2 00f80000 09b00000 00000040 00000006 00000000
...
[queue]   ACQUIRE_MEM 00000000 ffffffff 000000ff 00000000 00000000 0000000a 00010080
[queue]   RELEASE_MEM 0030c514 20000000 00300000 00000002 00000009 00000000 00000000

In the title's port at 7b59ef27 (mihawk-99/PS5_Mesa src/amd/vulkan/winsys/ps5), read from the source:

  • radv_ps5_buffer_destroy unmaps and frees a BO's direct memory at once. Nothing defers it until the GPU has finished.
  • Fences and sync objects complete only when radv_ps5_queue_poll reads the marker at or past the submission's seq. That read has no timeout and no assume-complete path. radv_ps5_queue_wait_seq only gives up after a 10 s stall. An empty submit (vkQueueWaitIdle/vkDeviceWaitIdle) signals at the last submitted seq.

So nothing in the title waited for seq 9 before it freed the block. On the console the GPU has long since run a 1701-dword DCB by the time the CPU frees memory tens of milliseconds later. On AnyPS5 the queue worker is still recording serial 17 and preparing its shaders.

What the title does next: with a local diagnostic that skips draws with an unreadable index buffer (not proposed), the run continues. It then stops at the known VideoOut: display no longer supports 119.88 Hz output (the TV on this machine is 60 Hz). So the unmap burst (many 16 KiB and 4 MiB blocks as well) is the renderer reconfiguring around its 120 Hz output switch, not the title exiting.

Ruled out

Question

Console behaviour is racy here: if the GPU had not run the draw, it would fault. Which policy do you want AnyPS5 to implement?

  1. Treat it as a title bug (report to griches/PS5_Doom3 / PS5_Mesa: a BO is freed while a submitted DCB still references it), or
  2. Make a guest unmap/release of GPU-visible direct memory wait until each queue has processed the DCBs submitted before the unmap. This needs care for DCBs that wait on CPU-written labels (they must not deadlock the unmapping thread) and for the cost described in AGC: whole-registry leases make every guest munmap/mprotect wait for GPU work; Onimusha (PPSA27836) drops below 1 FPS for 15-20 s at a time #6453.

Setup: Windows 11 Pro, Intel Core i9-14900K, 128 GB RAM, NVIDIA GeForce RTX 5080 (driver 32.0.16.1742), WinLibs GCC 15.2.0, Release.

Activity

  1. MarcZX10 commented on Oct 20, 2026

    @MarcZX10
    Contributor

    I would avoid an unconditional queue drain on every guest unmap. The measurements in #6453 show that broad waits in this path can stall titles badly, and a DCB may still be waiting for a CPU-written label, so blocking the unmapping thread until all earlier submissions complete can deadlock. The report also has not yet established console behavior for this exact lifetime pattern. I suggest first confirming whether the title-side PS5 driver defers the BO release or treats this as invalid use. If AnyPS5 needs compatibility behavior, it should track the submissions that actually reference the affected direct-memory range and defer host release until those uses complete, without waiting on CPU work from the same guest thread.

  2. dantdmdarealguy commented on Oct 20, 2026

    @dantdmdarealguy
    ContributorAuthor

    Agreed on not draining queues on every unmap. On the title side, the report already covers the driver: in the port's winsys (mihawk-99/PS5_Mesa 7b59ef27, src/amd/vulkan/winsys/ps5/radv_ps5_bo.c), radv_ps5_buffer_destroy unmaps and frees the BO's direct memory immediately, with no deferral to GPU completion. Its fences complete only when the queue marker reaches the submission's seq, and the marker was still at 8 when seq 9's index buffer was freed. So by the port's own semantics this is use-after-free in the title, and it works on the console only because the GPU has already run the DCB.

    If compatibility is wanted, your targeted version fits the evidence: record which direct-memory ranges each submitted DCB reads (here the DRAW_INDEX_2 index range, recorded at submit time), and defer only the host release of an unmapped range until those DCBs have been processed, without blocking the unmapping thread. Otherwise this belongs upstream in the port, and I can report it there.

  3. MarcZX10 commented on Oct 20, 2026

    @MarcZX10
    Contributor

    I checked the nearby lifetime work before considering a patch: #8622 changes when repeated ranges are imported (map time versus first GPU use), and #7849 retires host imports when a mapped range is cut. Neither records a submitted DCB’s reads or retains a direct-memory allocation until that queued submission reaches them, so this report remains a separate lifetime gap. The existing evidence supports a nonblocking, per-range deferred host release if compatibility is chosen; an unconditional queue drain would also inherit the CPU-label deadlock and broad-wait costs already called out here.

  4. MarcZX10 commented on Oct 20, 2026

    @MarcZX10
    Contributor

    I traced the submission and unmap paths further. Driver::Submit copies and queues the DCB before the worker has discovered all draw/resource addresses; direct-memory unmap then removes the registered view and backing from the guest address space. Retaining only the reported index-buffer range would miss other queued GPU reads, while leaving the whole guest mapping visible after logical unmap could let CPU accesses or a later VA reuse observe the old backing.

    Before implementing compatibility behavior, I think we need a maintainer decision on whether AnyPS5 should compensate for this title-side use-after-free, plus a contract for queued GPU reads versus CPU access and VA reuse after unmap. The existing path does not expose a narrow per-DCB range-retention hook yet, so I don't see a safe focused patch from the current evidence alone.

  5. MarcZX10 commented on Oct 20, 2026

    @MarcZX10
    Contributor

    Thanks, that pins down the title-side behavior: the port releases the BO immediately, so the report is a real use-after-free that only avoids visible corruption when GPU execution happens to finish before the free. That strengthens the case for documenting/reporting the lifetime bug upstream. I still do not see a safe emulator-side patch from the current hooks: Submit queues the DCB before the worker discovers all resource reads, so retaining only the reported index range would be incomplete. I will keep the AnyPS5 compatibility question separate until there is a contract for queued reads, CPU access, and VA reuse after unmap.

  6. dantdmdarealguy commented on Oct 21, 2026

    @dantdmdarealguy
    ContributorAuthor

    Reported upstream to the port: griches/PS5_Doom3#2 (the BO is freed while the submitted command buffer that reads it is still pending; suggested deferred destruction keyed to submission completion). Leaving the AnyPS5 compatibility question here for a maintainer decision, as discussed above.

  7. MarcZX10 commented on Oct 21, 2026

    @MarcZX10
    Contributor

    Thanks for reporting the lifetime bug upstream and linking the proposed deferred-destruction direction. That keeps the title-side defect documented while the AnyPS5 question stays focused on whether to emulate the console timing and, if so, what memory/unmap contract to preserve. I still would not add an emulator-side release policy until maintainers settle queued GPU reads versus post-unmap CPU access and VA reuse.

  8. babyhuey commented on Oct 22, 2026

    @babyhuey
    Contributor

    One detail from a Windows trace of the same stop (10/19, main 7602dde, APS5_TRACE_MEMORY=1) that may help weigh this: the unmap happens while the title shuts itself down. With no game data installed, the title logs Could not load default font "Arial_Narrow" and starts its fatal-error exit. The unmap 0000004007c00000+0x8000000 and the physical release come during that teardown, and the unreadable index buffer at 0x4009b00000 is a few lines later.

    So in this setup the race is between the title's error exit and a frame it had already submitted. How often it hits varied by build in my runs: none of the runs on 4ffb0cd, every run on 7602dde, and 2 of 3 on 784f82b. That fits timing rather than a change in the draw itself. I couldn't check whether it also happens with the game's base/ data present, where the title wouldn't take that exit.

    AI-assisted: yes

  9. Amoenus commented on Oct 28, 2026

    @Amoenus
    Contributor

    Sonic follow-up,28October2026: two current mainb99 composition82498aa17c57 attempts pass tutorial loading into rendered3D cinematics. The cold run reaches its planned600-second cap; the retained-cache run its900-second cap,continuing Werehog/Chip dialogue. No guest watchdog,fence-4,query-not-ready,fatal or presentation refusal in either interval. Controlled stops,not crashes; interactive gameplay unverified. This does not isolate a fix or establish a shared cause with Doom3/#8648.

    Earlier16abe loading aborts and their diagnostic order remain preserved. The pinned public Sonic/Mesa watchdog can itself return guest VK_ERROR_DEVICE_LOST without observing physical host-device loss; exact source-to-dump identity and root cause remain unproven.

    RTX5080/NVIDIA621.01,inserted Khronos1.4.363,both current runs0errors/VUIDs and8known-class layerCount warnings; all368native/retained/host/settings hashes match after each exit. Retained-cache last counters158shader/plan hits,6misses,without cache I/O failures. The long pause/slow cinematic remains an observation; a standard timings-only performance trace is next,not a guessed driver/cleanup diagnosis. Full receipts/curated excerpts linked above. AI-assisted:yes(Codex);author responsible.

  10. Amoenus commented on Oct 30, 2026

    @Amoenus
    Contributor

    Following @babyhuey's teardown observation,another title now gives a mapped-range/unmap/read sequence worth distinguishing from an initially invalid pointer. Sonic PPSA99902,composition0ac7/main6b48,stops during tutorial loading on both Windows GPUs after a fresh standard conversion;the retained-conversion control also stops.

    Run Exit after Unreadable DWORD Earlier covering direct map,then unmap request Guest watchdog/fence/query failures
    retained-nvidia 190.140s 0x2017cf2b0 0x2017c0000+0x10000 5/3/2
    fresh-nvidia 131.226s 0x20177cf80 0x201770000+0x20000 3/2/1
    fresh-amd 354.223s 0x2017cefb0 0x2017c0000+0x10000 11/5/2

    All three later fail at a four-byte Mesh-stage resource capture,originating at user-data word8. The failed address was inside the earlier direct mapping;its covering unmap request follows the guest watchdog/fence messages and precedes the unreadable read. DoMunmap logs on entry,so the line is not a completion timestamp. No queued-DCB/fence serial correlation has been established here,and this is not your proven index-buffer case or proof of the same cause. It supports investigating late work during teardown;it does not settle the compatibility contract or justify a broad drain.

    Curated line-numbered evidence,exact source/runtime/conversion pins and limits. Actual RTX5080/621.01 and Radeon Graphics/26.10.1,inserted Khronos1.4.363;zero host-validation diagnostics,but guest API failures remain above.389native libraries and immutable runtime/settings hashes verify after exit. No raw capture/replay,private failed-request inspection or game payload attached. AI-assisted:yes(Codex).

    Pinned public PS5_Mesa watchdog/sync reference and source refresh explain how no marker progress can become guestVK_ERROR_DEVICE_LOST without a host Vulkan device-loss report. Exact source-to-dump build identity remains unverified;no timeout/unmap policy change proposed.

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