Repository navigation
fix(runtime): anchor SetupThread's automatic main stack at the top of RDRAM and record kernel-managed stack metadata - #398
Open
LeandroSSB wants to merge 2 commits into
Open
LeandroSSB wants to merge 2 commits into
LeandroSSB wants to merge 2 commits into
Conversation
… RDRAM SetupThread (syscall 0x3C) with stack=-1 (the automatic-stack case, the default crt0 path) used to return PS2_RAM_SIZE - requestedSize as the initial $sp. Games link static arenas right under the region they expect the kernel to pick: on MOH:Vanguard (SLUS_215.97) the arena ends at 0x1FD81C0 and the honored placement (0x1FD8000) landed the live init frames *inside* it. The game's first static malloc returned a block adjacent to the stack base and its zero-fill wrote over the saved $ra of the whole init call chain; the epilogue then jumped to pc=0, dispatchGuestBranch saw pc==0, the scheduler made the main thread dormant, and boot stalled with only interrupts running. Anchor the automatic stack at the top of RDRAM (PS2_RAM_SIZE - 0x10, the same value run() seeds) so it stays clear of any arena a game links near the top of RAM. Also record the kernel-managed region correctly in the thread metadata: SetupThread anchors $sp at the region's TOP, but `stack` is a base everywhere else in the ABI (CreateThread passes the low address, ReferThreadStatus reports the allocation's lower bound), so ThreadInfo now records initialStack = anchoredSp - stackSize (base + size reconstructs the anchor; stackSize==0 keeps the anchor). This is the metadata defect documented in issue ran-j#259 ("base after CreateThread but a top after SetupThread"). Tests: "SetupThread anchors the automatic main stack at the top of RDRAM" (the anchor must leave the requested size clear of the arena), "SetupThread exposes stable main stack metadata through ReferThreadStatus" (corrected base, stable at two live $sp values, base+size == anchor), and the GS sync-V callback test now performs the game's own SetupThread shape before registering the callback. Evidence on MOH:Vanguard: distinct guest functions per 60s run went from 54 to 1041, SIF RPC init and IOP module loads (SIO2MAN/MTAPMAN/PADMAN) now execute. Co-Authored-By: Claude Code <noreply@anthropic.com>
Three failure paths in the guest dispatch loop used to be invisible: - EeScheduler::run(): the !hasFunction(context.pc) branch poisons context.pc to 0 when invocations are pending, with no report; the thread then goes dormant at the next pc==0 check. Log the pc before the poisoning. - PS2Runtime::dispatchGuestBranch(): a checkpoint yield unwinds the whole guest C++ stack leaving pc at the pending call target; log the source/target so the resume point is visible. - PS2Runtime::dispatchGuestBranch(): a callee returning with pc==0 (an epilogue jumping through a smashed $ra) silently unwinds the caller chain; log the registers at that point. All three are RUNTIME_LOG-gated and fire at most once per failure, not per dispatch. Found while booting MOH:Vanguard, where the only symptom of the stack collision was a silent dormancy. Co-Authored-By: Claude Code <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause
SetupThread(syscall 0x3C) withstack = -1— the automatic-stack case, the default crt0 path — computed the initial$spasPS2_RAM_SIZE - requestedSize. Games link static arenas right under the region they expect the kernel to pick, so the honored placement can land the live init frames inside the game's own arena.On MOH:Vanguard (SLUS_215.97) the static arena ends at
0x1FD81C0and the game asks for a0x28000stack: the delivered sp was0x1FD8000— 0x1C0 below the arena end. The game's first staticmallocreturned a block adjacent to the stack base and its init zero-fill wiped the live frames of the whole init call chain: the epilogue didjr $rawith$raread from zeroed stack → pc=0 → silentmakeDormantof the main thread; only interrupts ran from then on.This is also the metadata half of the shape documented in #259 ("the stack value is the base after CreateThread but a top after SetupThread"):
SetupThreadanchors$spat the region's TOP, butstackis a base everywhere else in the ABI (CreateThreadpasses the low address,ReferThreadStatusreports the allocation's lower bound).Fix
stack = -1, anchor the initial sp at the top of RDRAM (PS2_RAM_SIZE - 0x10, the same valuerun()seeds); the requested size stays recorded as the thread'sstack_sizemetadata.ThreadInfo.stackrecords the kernel-managed BASE (anchor − stackSize), not the anchor top, so consumers reading the metadata see the real extent (base + size reconstructs the anchor exactly;stackSize == 0keeps the anchor).dispatchGuestBranchfailure paths ([dispatch-pc0],[dispatch-yield], no-function branch) now log behindRUNTIME_LOG— the[dispatch-pc0]line is what pinpointed the death above.Test
ps2xTest(ps2_runtime_kernel_tests.cpp,ps2_gs_tests.cpp):$spvalues, and base + size == anchor.SetupThread(stack=-1, 0x28000)shape before registering the callback and asserts the anchor (PS2_RAM_SIZE - 0x10) with the metadata present.Measured (MOH:Vanguard boot)
Distinct guest functions per 60 s run: 54 → 1041 (~19x);
SifInitRpccompletes with IOP modules loading; the[dispatch-pc0]silent-dormancy signature: present in every run before → 0 after.🤖 Generated with Claude Code
Suite evidence
Fresh configure+build of
ps2x_testson this branch (VS 2022, Release): 447/447 passed, 0 failed.