Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

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

Abierto
#258 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 5 días

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
4/5
Tiempo estimado
3-5 días
Aptitud para principiantes
32/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
cpp

Línea de trabajo

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.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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.

Lenguaje dominante
C++
Estrellas
3.3k
Forks
149
Merge medio
5 d 10 h
PR fusionados (30 d)
4

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de ran-j/PS2Recomp

Todos los issues de ran-j/PS2Recomp

Issues similares

Más issues de C++

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.