Skip to content

Let a thread resume at the instruction after a syscall - #210

Merged
ran-j merged 1 commit into
ran-j:mainfrom
Sinan-Karakaya:fix/syscall-resume-entry
Aug 18, 2026
Merged

Let a thread resume at the instruction after a syscall#210
ran-j merged 1 commit into
ran-j:mainfrom
Sinan-Karakaya:fix/syscall-resume-entry

Conversation

@Sinan-Karakaya

@Sinan-Karakaya Sinan-Karakaya commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

A syscall can hand control back to the scheduler before the instruction after it runs. SetSyscall lets the guest install its own handler for a syscall number; dispatchSyscallOverride then suspends the calling thread and queues that handler as a GuestInvocation. When the invocation finishes, EeScheduler resumes the parent thread at the address the generated code stored just before calling handleSyscall — the instruction right after the syscall.

The analyzer never marked that address as an entry point. queueResumeEntryTarget() is reached for OPCODE_JAL and SPECIAL_JALR only, so no generated function could be re-entered there, EeSchedulers hasFunction() check failed, and the thread was made dormant instead of resumed.

The failure is quiet, which is the annoying part: the thread just stops, the scheduler drains normally, and run() returns 0. It looks like a clean shutdown rather than a fault.

This is reachable during early boot on a real title. Dragon Quest VIII (SLUS-21207) hits it in crt0 — the Metrowerks startup code installs a handler for syscall 0x83 and immediately issues it, so execution ends about ten functions into the binary:

[guest-branch:missing-target] kind=DirectJump op=EE scheduler
  source=0x120a48 target=0x120a48 pc=0x120a48 ra=0x120ae4 ...
  trace=0x10008c -> 0x120b98 -> 0x120948 -> 0x116820 -> 0x116820
        -> 0x120a88 -> 0x120b88 -> 0x120b88 -> 0x120a40 -> 0x120a00

0x120a40 is the four-instruction syscall wrapper (addiu $v1, $zero, 0x83 / syscall / jr $ra / delay slot) and 0x120a48 is its jr $ra. That binary has 175 syscall sites, none of which had a resume entry.

Note the offset is +4, not +8: syscall has no delay slot.

Adds a test covering both the resume entry and the offset.

@Sinan-Karakaya
Sinan-Karakaya force-pushed the fix/syscall-resume-entry branch from 3a4a82d to 2b20434 Compare August 18, 2026 09:25
Comment thread ps2xRecomp/src/lib/control_flow_analyzer.cpp Outdated
A syscall can hand control back to the scheduler before the instruction
after it runs. SetSyscall lets the guest install its own handler for a
syscall number; dispatchSyscallOverride then suspends the calling
thread and queues that handler as a GuestInvocation. When the
invocation finishes, EeScheduler resumes the parent thread at the
address the generated code stored just before calling handleSyscall --
the instruction right after the syscall.

The analyzer never marked that address as an entry point. It queues
resume entries for JAL and JALR only, so no generated function could be
re-entered there, EeScheduler's hasFunction() check failed, and the
thread was made dormant instead of resumed. The thread simply stops;
because the scheduler then drains normally and run() returns, it looks
like a clean shutdown rather than a fault, which makes it awkward to
recognise.

This is reachable during early boot on a real title. Dragon Quest VIII
hits it in crt0: the Metrowerks startup code installs a handler for
syscall 0x83 and immediately issues it, and execution ends there,
roughly ten functions into the binary.

Note the offset is +4, not the +8 used for JAL and JALR -- syscall has
no delay slot.

(cherry picked from commit becb2be5bd0dc3deebeec8454df21ea7d7062b45)
@Sinan-Karakaya
Sinan-Karakaya force-pushed the fix/syscall-resume-entry branch from 2b20434 to 851d9ac Compare August 18, 2026 20:26
@ran-j
ran-j merged commit d9ea4fb into ran-j:main Aug 18, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants