This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Forensic Analysis of Execution Trace
Summary
Created a forensic tracing tool (tools/debug_trace.py) to analyze
running the emulator without a graphical interface. Analysis of the first instructions
revealed that the game runsDI (0xF3)at address 0x0150, disabling
the IME (Interrupt Master Enable) at startup. The game never runsEI (0xFB)to re-enable interruptions, which explains why the game stays in a
infinite loop waiting for V-Blank.
A delay loop was detected between addresses 0x1383-0x1389 that decrements the DE register from 0x06D6 (1750) until it is zero. The game is waiting for an event (probably V-Blank) which never happens because IME is disabled, even if IE was configured correctly.
Hardware Concept
On the Game Boy, interrupts require two levels of enablement:
- IME (Interrupt Master Enable): Global interrupt control, controlled by
the instructions
DI (0xF3)andEI (0xFB). If IME = False, none interrupt will be processed, regardless of IE. - IE (Interrupt Enable, 0xFFFF): Memory register that controls what types of interrupts are enabled (V-Blank, LCD STAT, Timer, Serial, Joypad).
For an interrupt to be processed,bothmust be enabled: IME = TrueANDthe corresponding bit in IE = 1. If IME is disabled, even if IE is configured correctly, interrupts will not be processed.
Games typically runD.I.at startup to disable interruptions during
initialization, and then executeEIto re-enable them. If the game never
executeEI, interruptions remain disabled and the game may be left
stuck in waiting loops.
Fountain:Pan Docs - Interrupts, Interrupt Master Enable (IME), Interrupt Enable Register (IE)
Implementation
The tool was createdtools/debug_trace.pythat:
- Load a ROM using the class
Viboy - Intercept all writes to memory using a wrapper
TraceMMU - Executes a configurable number of instructions (default 50,000)
- Register each instruction with: PC, opcode, registers, cycles and I/O writes
- Detect infinite loops by analyzing repeated patterns in PC history
- Generates a report with critical writes (IE, LCDC, IF, STAT) and the first/last 50 instructions
- Shows the first instructions to analyze the game initialization sequence
Components created/modified
tools/debug_trace.py: Main forensic tracing script (updated to show first instructions)TraceMMU: Wrapper class that intercepts writes to memory
Design decisions
It was decided to create an MMU wrapper instead of modifying the emulator code to keep the plotting tool separate from production code. This allows enable/disable tracing without affecting the main emulator.
Loop detection is implemented by analyzing repeated patterns in PC history. Both exact loops (same repeated sequence) and short loops are detected with oscillation between few directions (typical of conditional jumps).
Affected Files
tools/debug_trace.py- Forensic tracing tool (new, updated)
Tests and Verification
The plotting script was run withtetris_dx.gbc(user-contributed ROM, not distributed):
- Command executed (initial analysis):
python tools/debug_trace.py tetris_dx.gbc --max-instructions 5000 - Command executed (extended analysis):
python tools/debug_trace.py tetris_dx.gbc --max-instructions 100000 - Around:Windows 10, Python 3.13.5
- Result:Successful execution, 100,000 instructions traced
- What is valid:
- That the game executes DI (0xF3) in instruction #3 (PC: 0x0150)
- that the gameNEVERexecute EI (0xFB) at 100,000 instructions
- That the game never writes to IE (0xFFFF) during the 100,000 instructions
- That there is a wait loop that persists after 100,000 instructions
- That the game writes to I/O (370 writes detected, mainly at 0xFF00 - P1)
Analysis Findings - Starting Sequence
Analysis of thefirst instructionsrevealed the initialization sequence:
PC: 0x0100 | NOP (0x00) - Cartridge code start
PC: 0x0101 | JP nn (0xC3) - Jump to 0x0150
PC: 0x0150 | DI (0xF3) - ⚠️ DISABLE INTERRUPTIONS (IME = False)
PC: 0x0151 | LD (0xFF00+C), A (0xE0) - Write to 0xFF04 (DIV - Timer Divider)
PC: 0x0153 | LD (0xFF00+C), A (0xE0) - Other write to I/O
PC: 0x0155 | LD BC, d16 (0x01) - Load BC with 0x0004
PC: 0x0158 | CALL nn (0xCD) - Calls function at 0x1380
PC: 0x1380 | LD DE, d16 (0x11) - Load DE with 0x06D6 (1750)
PC: 0x1383-0x1389 | Wait Loop - Decrements DE to 0
Analysis Findings - Extended Verification (100,000 instructions)
Extended analysis with100,000 instructionsconfirmed the initial findings:
DI/EI (Interrupt Master Enable) EXECUTIONS:
Instruction #3 | PC: 0x0150 | DI (0xF3) | IME before: Disabled
SUMMARY:
- DI executed: 1 time (instruction #3, PC: 0x0150)
- EI executed: 0 times (NEVER in 100,000 instructions)
- Writes to IE (0xFFFF): 0
- I/O writes: 370 (mainly 0xFF00 - P1 Joypad)
- Final state: Persistent loop between 0x12DD-0x12EC
Confirmed critical finding:The game runsDI (0xF3)one time
in instruction #3 (PC: 0x0150), disabling IME at startup. The gameNEVER execute
EI (0xFB)in 100,000 instructions to re-enable interrupts. This
means that even if IE was configured correctly, interrupts would not be
would process because IME is permanently set to False.
Additional observation:After 100,000 instructions, the game is still in a loop, but now between addresses 0x12DD-0x12EC (different from the initial loop 0x1383-0x1389). The game writes to 0xFF00 (P1 - Joypad) during the loop, suggesting that it is waiting user input or some event that never occurs.
Analysis Findings - Waiting Loop
LOOP DETECTED:
PC ranges between: 0x1383-0x1389
Pattern:
0x1383: NOP (0x00)
0x1384: NOP (0x00)
0x1385: NOP (0x00)
0x1386: DEC DE (0x1B) - Decrement DE
0x1387: LD A, D (0x7A) - Load D into A
0x1388: OR E (0xB3) - OR between A and E
0x1389: JR NZ, -5 (0x20) - Relative jump if non-zero
Loop analysis:The game is in a delay loop that decrements DE from 0x06D6 (1750) until it is 0. When DE reaches 0, the conditional jump (JR NZ) is not executed and the loop should end. However, the game probably waits for something else to happen (like a V-Blank interrupt) before continuing, but since IME is disabled, the interrupts They are never processed.
Sources consulted
- Bread Docs:Interrupts, Interrupt Enable Register (IE)
- Bread Docs:CPU Instruction Set - JR (Jump Relative)
Educational Integrity
What I Understand Now
- IME vs IE:Interrupts require two levels of enablement: IME (global control, DI/EI) and IE (memory register). If IME is disabled, no interrupts will be processed, even if IE is configured correctly.
- DI/EI:Games typically run DI at startup to disable interrupts during initialization, and then run EI to re-enable them. If the game never runs EI, interrupts remain disabled.
- Delay Loops:Games use delay loops to pause execution for a given time. These loops typically decrement a record until it is 0.
- Infinite loop:If a game waits for an interruption that never happens (because IME is disabled), you may get stuck in an infinite loop waiting an event that will never come.
What remains to be confirmed
- Why the game doesn't run EI:Confirmed that the game never runs EI on
100,000 instructions. It is not clear whether:
- The game never gets to the part of the code that executes EI (gets stuck before)
- There is a bug in the DI/EI implementation that prevents IME from being enabled correctly
- The game expects IME to be enabled by default (initialization bug)
- What happens after the delay loop:Analysis of 100,000 instructions shows that the game goes into different loops (0x1383-0x1389 initially, then 0x12DD-0x12EC). The game never completely breaks out of the waiting loops.
- IME initial status:Analysis shows that IME is "Disabled" before of executing DI in statement #3, suggesting that IME is initialized to False (correct). However, I need to check the implementation to confirm.
- DI/EI Implementation:I need to verify that the implementation of DI/EI on the CPU works correctly and the IME updates as expected when these instructions are executed.
Hypotheses and Assumptions
Main hypothesis (CONFIRMED):The game runs DI on startup to disable interruptions during initialization, butnever runs EIto re-enable them (verified on 100,000 instructions). This causes the game to get stuck in a waiting loop waiting for V-Blank, which is never processed because IME is permanently disabled.
Secondary hypothesis:It is possible that:
- The game has a bug and never reaches the code that executes EI
- There is a bug in the DI/EI implementation that prevents IME from being enabled correctly
- The game expects IME to be enabled by default (emulator initialization bug)
Unverified assumption:I can't check if the game should run EI without analyze the ROM code (which is not possible without disassembly tools). However, the fact that the game never runs EI at 100,000 instructions strongly suggests there is a problem in the initialization logic or in the emulator implementation.
Next Steps
- [ ] Check the DI/EI implementation on the CPU to ensure that IME is updated correctly
- [ ] Trace more instructions (100,000+) to see if the game eventually executes EI
- [ ] Analyze the initial state of IME and other critical registers at the start of execution
- [ ] Check if there are any issues with interrupt handling that are preventing the game from enabling them
- [ ] Consider forcing IME = True temporarily to see if the game continues (if IE is set)
- [ ] Check if the game expects IME to be enabled by default at startup (initialization bug)