Skip to content

fix(libc): alias non-contiguous shared guest views on Windows - #2254

Closed
Angel21PC wants to merge 1 commit into
boykopovar:mainfrom
Angel21PC:fix/libc-alias-noncontiguous-shared-views
Closed

Angel21PC wants to merge 1 commit into
boykopovar:mainfrom
Angel21PC:fix/libc-alias-noncontiguous-shared-views

Conversation

@Angel21PC

Copy link
Copy Markdown
Contributor

What

On Windows, a host import of shared guest memory goes through a read-write alias from WindowsMappings::MapAlias. The alias was a single MapViewOfFile3 of one section, so MapAlias threw when the views of the range were not one contiguous run of that section or spanned several sections:

read-write alias of shared guest memory 0x…+0x…: the range is not one contiguous run of views of a section

importAllocation does not catch it, so the title aborts. Two layouts give such ranges:

  • one sceKernelMapDirectMemory over two adjacent direct memory allocations: each allocation has its own section (CreateDirectMemoryBacking), AddMapping maps each one, and the mapping is one registered range;
  • pages remapped in place (MAP_FIXED) to physical pages in another order. Each remap leaves its own registered range, and importAllocation imports one registered range at a time, so no host import on main asks for this alias yet.

fpPS4's PS4 kernel reimplementation (branch kern) accepts a direct memory mapping whose physical range is allocated without gaps and with one m_acl (_dmem_map_test, L930-L984), and merges neighbouring allocations with equal attributes (dmem_map_simplify_entry, L1198-L1235). shadPS4's MapMemory also walks several direct memory areas in one call. Neither is a hardware measurement, and this is not verified on PS5.

Such ranges are now aliased run by run: a placeholder reservation is split at each run of consecutive pages of one section, and each run maps that section at its offset with MEM_REPLACE_PLACEHOLDER, the mechanism Map uses for guest pages. Two adjacent allocations give two views. UnmapAlias unmaps every run and forgets the alias even when one unmap fails. A contiguous range keeps the single view.

Left as before:

  • a range with a page that is not a shared view, such as flexible memory, is still refused;
  • importAllocation still imports one registered range at a time; imports of views spanning several registered ranges are not added here.

Tested

Windows 11 Pro, Intel Core i7-12700K, NVIDIA GeForce RTX 4070, 32 GB, MinGW-w64 GCC 15.2.0.

  • guest_memory (core/libs/tests/GuestMemory.cpp; the three new checks are Windows only):

    • CheckAliasAcrossDirectAllocations: allocations of 32 KiB and 16 KiB, adjacent and mapped with one call. The alias has one view per allocation, reads and writes all three pages, and unmapping it frees them;
    • CheckAliasOfReorderedSharedPages: two pages remapped in reverse physical order. The alias reads and writes both;
    • CheckAliasRefusesPagesThatAreNotSharedViews: a range whose second page is flexible memory is still refused.

    On main, each of the first two fails with the message above (address aside); the third passes on both.

  • agc_driver_dcc_keys_first_write (also maps an alias), agc_write_watch_coverage, agc_driver_typed_heaps, guest_heap_rounding_overflow and application_heap_* pass.

  • tools/check_conventions.py --base origin/main passes.

  • Poppy Playtime: Chapter 4 (PPSA25063) aborted once with this error in a presentation compute shader (0x3008540000+0x400000), in a local build that also imports buffer views spanning several registered ranges, which this PR does not add. The log does not show whether that range was one registered range or several. With an earlier revision of this change (one view per page) in that build, the title reaches gameplay and the error did not recur in 12 later runs. The error was rare, and the logs do not show whether an alias of several views was created, so this does not show that the change prevents it.

  • Not tested:

    • this revision (one view per run) in any title; the Poppy runs above used the one-view-per-page revision with other local changes;
    • a Vulkan host import of an alias made of several views (a failed import falls back to copies);
    • the rollback when mapping a run fails, and an unmap that fails (the alias is still forgotten);
    • Linux (the change is Windows only).

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes

On Windows, a host import of shared guest memory goes through a
read-write alias from WindowsMappings::MapAlias. The alias was a single
MapViewOfFile3 of one section, so MapAlias threw when the views of the
range were not one contiguous run of that section. One
sceKernelMapDirectMemory over two adjacent direct memory allocations
gives such a range, as each allocation has its own section, and so do
pages remapped in place to another physical order.

Such ranges are now aliased run by run: a placeholder reservation is
split at each run of consecutive pages of one section, and each run
maps that section at its offset. UnmapAlias unmaps every run and forgets
the alias even when one unmap fails. A contiguous range keeps the single
view, and a range with a page that is not a shared view is still
refused.

guest_memory maps two adjacent direct memory allocations of two pages
and one page with one call, and two pages in reverse physical order.
It reads and writes every page through the alias. The first test also
checks that the alias has one view per run and that unmapping it frees
its pages. A range whose second page is flexible memory is still
refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@oneandonlydean

Copy link
Copy Markdown
Collaborator

Can you run this revision on Poppy Playtime: Chapter 4 (PPSA25063) on main, without the local multi-range import change, and post the abort log from main and the result with this PR? The only title run so far used the one-view-per-page revision plus other local work, so it doesn't show that main reaches this path or that this fixes it.

@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
#861 core/libs/tests/GuestMemory.cpp L567-568, L1269-1270
#1803 core/libs/tests/GuestMemory.cpp L21, L1269

@Angel21PC

Copy link
Copy Markdown
Contributor Author

Ran it on main faf6a11 with nothing local: 4 runs of Poppy Playtime: Chapter 4 (PPSA25063), alternating main and main with this PR merged (same relinked executable; the PR only changes libc), with APS5_TRACE_MEMORY=1 and APS5_TRACE_HOST_IMPORT=1. Main does not reach this throw with this title, so there is no abort log from main to post:

  • The title allocates its direct memory at boot in 17 allocations and maps it with about 2230 MAP_FIXED remaps into reserved ranges. No mapping crosses from one allocation to the next, so the adjacent-allocations case does not occur.
  • Main imports one registered range at a time, and each remap is its own range. None of the 3132 imports covers more than one remapped piece, so every alias is a single contiguous view. The runs with this PR look the same: its new path is not used.
  • The 1024x1024 storage image from the abort in the description (0x3008540000+0x400000 there) straddles two remapped pieces at non-consecutive offsets of one allocation in 2 of the 4 runs, the reordered case. Main imports only the first piece and never asks for an alias of both. The abort in the description came from the local change that imported that span as one range.

All 4 runs stop 6-14 s in, after that image is created, on an unrelated shader: ShaderRecompiler::Recompile: shader info collection failed: packed pixel ancillary input has an unsupported live use.

So this only matters once a Windows import spans registered ranges, which main doesn't do. Closing it; it can come back with such a change, which needs it.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants