Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Runtime: async invocation stacks alias thread stacks, so every interrupt handler corrupts the interrupted frame's return address

Aperta
#258 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 5 giorni

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
32/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
cpp

Direzione di ricerca

Start in ps2xRuntime/include/ps2_runtime.h and ps2xRuntime/src/lib/ps2_runtime.cpp at reserveAsyncCallbackStack() and the two resets of m_asyncCallbackStackTop to PS2_RAM_SIZE. Trace how SetupThread/CreateThread pass stack address and size into EeScheduler and how GuestThread::stack is stored (top vs base). Done when async stacks are derived from declared thread extents, overlap is refused, and stacks entirely below the async floor are ignored so titles can take interrupts without clobbering $ra.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

What happens

PS2Runtime::reserveAsyncCallbackStack() carves stacks for asynchronous invocations (INTC/DMAC handlers, GS callbacks, alarms) downwards from m_asyncCallbackStackTop, initialised to PS2_RAM_SIZE (0x02000000):

  • ps2xRuntime/include/ps2_runtime.h -- uint32_t m_asyncCallbackStackTop = PS2_RAM_SIZE;
  • ps2xRuntime/src/lib/ps2_runtime.cpp:507 and :976 -- both reset it to PS2_RAM_SIZE

So the first reservation returns 0x01FFFFF0.

That is exactly where the guest's main thread stack lives. Measured at the first schedule in Ridge Racer V: sp=0x1fffff0. The "dedicated" invocation stack therefore aliases the stack of the very thread it interrupts, and each asynchronous handler overwrites the frames of the function it interrupted.

What it costs

Captured with a hardware watchpoint on 0x01FFFFA0:

--- write 1: 0x01FFFFA0 <- 0x0021DF40 ---
#0  rrv_00220470            <- its prologue saves its return address
#1  PS2Runtime::dispatchGuestBranch
#2  rrv_0021DE90            <- the game loop
#3  EeScheduler::run

--- write 2: 0x01FFFFA0 <- 0x00000000 ---
#0  rrv_0021FE88            <- interrupt handler, running as an invocation
#1  EeScheduler::run

FUN_00220470 keeps its return address at 16($sp) with sp = 0x01FFFF90, i.e. 0x01FFFFA0. The handler, started on an invocation stack beginning at 0x01FFFFF0, lays its own frame there and writes its $ra -- zero, as expected for an invocation -- right on top of it.

FUN_00220470 is then resumed at its epilogue, reloads $ra, and finds zero. jr $ra jumps to address 0. The scheduler sees pc == 0 with an empty invocation list and puts the thread to sleep; _start reaches its Exit. The game loop did not complete a single iteration.

Suggested fix

A constant reserve below the top of RAM proves the cause but is not a solution: nothing tells a constant how far down a thread stack reaches, and a game with many threads would exhaust the region.

The kernel already has the information. SetupThread (syscall 0x3C, InitMainThread) and CreateThread both receive a stack address and a stack size and pass them to EeScheduler. Derive the asynchronous region from the actual declared thread stack extents, and refuse the allocation when they would overlap.

Two traps worth knowing if you implement it:

  1. The meaning of GuestThread::stack is not uniform -- see the companion issue about SetupThread storing a top where CreateThread stores a base.
  2. A stack sitting entirely below the asynchronous floor constrains nothing. Ridge Racer V's _spuCBThread has its stack at 0x01DAD500, inside the game's .bss and below the 0x01F00000 floor; taking it into account pushed the ceiling under the floor, the allocation failed, and the runtime raised "EE invocation stack space exhausted". Only keep stacks whose top is above the floor.

Result after fixing it locally

before after
game loop body completions 0 2552
main thread state Dormant Running
GS kicks / primitives 4 / 2 24 / 12

Any title that takes an interrupt during a nested call loses the interrupted frame's return address the same way.


Found while statically recompiling Ridge Racer V (NTSC-U, SLUS_200.02) with PS2Recomp.
Tested on commit 75d729ce; main was c5a9d02 at the time of writing, one README-only commit ahead.
Host: Windows 11 x64, clang-cl 22.1.3 + Ninja, MSVC toolset 14.51.36231, Windows SDK 10.0.26100.0, CMake 4.2.3.
None of these are specific to this game.

Lingua principale
C++
Stelle
3.3k
Fork
149
Merge medio
5g 10h
PR unite (30g)
4

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di ran-j/PS2Recomp

Tutte le issue di ran-j/PS2Recomp

Issue simili

Altre issue su C++

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.