Skip to content

fix(libSceFiber): pin fiber contexts writable under write tracking on Windows - #283

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
SP4C3B4R-8:fix/fiber-windows-stack
Oct 7, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
SP4C3B4R-8:fix/fiber-windows-stack

Conversation

@SP4C3B4R-8

@SP4C3B4R-8 SP4C3B4R-8 commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

What

Fiber contexts live in guest memory. When write tracking had armed such a page read-only, a push on the fiber stack faulted and Windows could not dispatch the exception, because the exception frame is written to the same protected stack, so the process was terminated without any report. Fiber contexts are now pinned writable (GuestArenaPinWritable_nid_postfix) for their lifetime; pinned pages are never armed and are always collected as written. Initialising a fiber object again without finalising it unpins the previous context first.

Relation to #637

#637 keeps direct memory mapped at a single address in MEM_WRITE_WATCH memory, which is never write-protected. After #637 this PR only matters for fiber contexts in pages mapped at two or more addresses. In the title I tested, all 13 pins (9 distinct contexts, 1 MiB each) are in pages with a single mapping, so with #637 that title would not need the pinning. Whichever PR lands second gets rebased (both touch WindowsMappings.hpp). If #637 lands first, I'm fine with closing this one, or keeping it for aliased contexts.

Tested

Windows 11, MinGW-w64 GCC 15.2.0 (winlibs, ucrt posix seh). Full build; ctest passes. New case CheckPinnedSharedPages in core/libs/tests/GuestMemory.cpp (Windows only). It maps direct memory and pins its middle page. The pinned page must stay PAGE_READWRITE and be reported on every collect. Its unpinned neighbour must be armed and reported once per write. After Unpin and a collect, the page must be armed again. The case fails if pinning is removed, or if collects arm pinned pages. Checked with a retail title: without this change it terminates silently (0xC0000005, no report) 39–57 s after start in 4 of 4 runs, with it it runs on (2 of 2, 120 s). Details in the comments.

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
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes (Claude). I reviewed and take responsibility for every line.

🤖 Generated with Claude Code

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks. Which retail title did you check this with, and does it still terminate on current main without this PR? The branch is from 10-01, and CI hasn't run on it. A rebase onto main, plus the title ID and the point where it terminates, would show that both fixes (the TIB bounds order and pinning the fiber contexts) are still needed.

@SP4C3B4R-8

Copy link
Copy Markdown
Contributor Author

@oneandonlydean PPSA21564 v01.007.000, on Windows 11, Ryzen 7 7800X3D, RTX 5070 Ti. What I have so far is from your astrobot at 9121abeb (2026-10-03), before you pulled #282–#284 in: without these commits the process ended silently (no fault report, no log line) about 17 s after start, and with them it ran to the title. I didn't isolate the two fixes of this PR from each other or from #282 and #284 in that run, and I haven't tried current main yet.

I'll rebase onto current main so CI runs, then check on current main (with #285 so it gets past the GDS stop) without this PR, and each fix alone, and post the title ID, the point where it terminates and the result of each. Tomorrow at the latest.

Written with AI assistance (Claude Code).

… Windows

Write tracking protects shared guest pages read-only. A push on a fiber
stack in such a page faults, and Windows cannot dispatch it because the
exception frame lands on the same protected stack, so the process was
terminated without a report. Pin fiber contexts writable for their
lifetime; pinned pages are always collected as written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@SP4C3B4R-8
SP4C3B4R-8 force-pushed the fix/fiber-windows-stack branch from 2443a93 to 57588cf Compare October 7, 2026 09:30
@SP4C3B4R-8 SP4C3B4R-8 changed the title fix(libSceFiber): fix fiber stack switching under write tracking on Windows fix(libSceFiber): pin fiber contexts writable under write tracking on Windows Oct 7, 2026
@SP4C3B4R-8

Copy link
Copy Markdown
Contributor Author

@oneandonlydean results. PPSA21564 v01.007.000, Windows 11, Ryzen 7 7800X3D, RTX 5070 Ti, MinGW-w64 GCC 15.2.0, APS5_HOST_IMPORT_MIB=20480, write watching on (default), boot without -lvl, 120 s per run, APS5_TRACE_FIBER=1.

Current main (219a1cf8) + #285, without this PR: it stops before the point where this matters. 2 of 2 runs end at 37–46 s with compute shader 0x5005f6100: AGC driver: guest memory is not readable at 0x30 (range 0x30+0x4) (the #479 / #759 class; #759 doesn't merge cleanly onto main now, so I didn't stack it). By then the title has only initialised 6 fibers and returned from them 10 times.

Your astrobot 39b59157, which gets past that stop, with APS5_NO_SNAPSHOT_CHECK=1 (see below):

runs result
as is (this PR included, 2aaedccb) 2 both run the full 120 s; 13k–16k fiber events
2aaedccb reverted 4 all end silently at 39–57 s, exit code 0xC0000005, no fault report, no log line; the last fiber event is always return Resident, right before the title initialises its next fibers (Armadillo, Transition)
TIB bounds order only (pinning removed) 2 same as reverted: silent 0xC0000005 at 40–41 s
pinning only (bounds set before the switch, as before) 2 both run the full 120 s

So only the pinning is needed. That makes sense: the bounds order only matters if a push on the fiber stack faults, and with the contexts pinned writable those pages are never armed, so the push doesn't fault. I've dropped the bounds change: the branch is now one commit on current main, pinning only (57588cf2, title updated). Full build passes; ctest 382 of 383, the one failure being agc_driver_scalar_sop1 (movrelsd_2_b32), a recompiler test this change doesn't touch, which @MichonGoddijn231849 also reports failing on main. CI should run on the push.

Separately: without APS5_NO_SNAPSHOT_CHECK, 39b59157 as is stops at boot in 2 of 2 runs, about 40 s in, with compute shader 0x5005f6100: AGC graphics: guest snapshot differs from registered memory. Same shader as the main stop above. I'll put that in #357.

Written with AI assistance (Claude Code).

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for isolating the pinning from the bounds change, the 4-way table makes the cause clear. Code reading checks out (the only arm is behind clear && !pinned, and Pin/Unpin invalidate the clean ranges). Two things before merge:

  • No test. The CI jobs that are green don't exercise any of this, and the evidence is a retail title run we (and Linux CI) can't reproduce. core/libs/tests/GuestMemory.cpp already has _WIN32 cases using GuestArena, so a case there would run on the Windows job: map a direct memory range, GuestArenaPinWritable_nid_postfix part of it, GuestArenaCollectWrites_nid_postfix(..., clear=true) twice, then check that the pinned page is still PAGE_READWRITE (VirtualQuery) and is reported on both collects, while an unpinned page in the same range is armed and only reported once; after Unpin + collect it gets armed. It should fail without the change.
  • Relation to #637 (DotDebian, same header, same root cause as #361). With #637, direct memory mapped at a single address lives in MEM_WRITE_WATCH memory and is never write-protected, so the fiber-stack fault only remains for pages mapped at two or more addresses. The two PRs conflict textually in WindowsMappings.hpp; whichever lands second needs a rebase. Please say in the description whether pinning is still needed on top of #637 (i.e. whether the title's fiber contexts are in aliased pages), so it's clear what this PR covers after both.

Minor: sceFiberInitialize on an object that is already initialised pins again without unpinning, so those pages stay pinned (reported as written on every collect) for good. Unpinning the previous context when magic == FIBER_MAGIC would avoid it, if a title re-initialises without finalising.

…itten

Also unpin the previous context when a fiber object is initialised again
without being finalised.

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

SP4C3B4R-8 commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor Author

@adriadam10 thanks, all three are in 78cb1598:

Written with AI assistance (Claude Code).

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, all three points are addressed. Checked: the PR merged onto current main builds and passes ctest on Linux (400/400). In code review, a pinned page is never armed (clear && !pinned), Pin/Unpin invalidate the clean ranges, and the expected counts in CheckPinnedSharedPages match the 16 KiB view / 4 KiB report granularity. The Windows CI job runs the new case. Merging once Windows and Linux CI are green. #637 will need a rebase on WindowsMappings.hpp after this.

@adriadam10
adriadam10 merged commit 832247b into boykopovar:main Oct 7, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants