This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Debug: Detailed Instrumentation of render_scanline
Summary
Added detailed debugging instrumentation to the methodrender_scanline()of the PPU in C++ to identify the exact origin of theSegmentation FaultWhat happens when you run the emulator with the Tetris ROM. Even though the unit test for signed addressing mode passes correctly, the actual execution still crashes, indicating that there is another use case not covered by the test that causes an invalid memory access.
Instrumentation adds logs usingprintfto capture critical values (scan line, scroll, tilemap addresses, tile IDs, calculated addresses) just before trying to read tile memory. These logs will allow us to identify exactly which values are causing the crash.
Hardware Concept
On the Game Boy, the PPU renders each scanline by accessing two main areas of memory: theTilemap(at 0x9800-0x9BFF or 0x9C00-0x9FFF) and theTile Data(at 0x8000-0x97FF). The rendering process for each pixel on the line involves:
- Calculate position on tilemap using scroll (SCX, SCY)
- Read the Tile ID from the tilemap
- Calculate the address of the tile in VRAM according to the addressing mode (signed/unsigned)
- Read tile data (2 bytes per line)
- Decode the pixel and write to the framebuffer
If any of these calculated addresses are outside the valid VRAM range (0x8000-0x9FFF), the memory access can cause aSegmentation Fault. Although we added validations in Step 0138, the crash suggests that there is some specific case that is not being captured by these validations or that occurs before reaching them.
Fountain:Pan Docs - Background, Tile Data, Tile Maps
Implementation
Added debugging instrumentation usingprintf(ratherstd::cout) because it is safer for debugging crashes, since it does not depend on buffers that may not be emptied before a crash.
Components created/modified
src/core/cpp/PPU.cpp: Added detailed logs inrender_scanline()
Design decisions
- Static variable for printing control:A global static variable is used
debug_printedto print logs only once (during the first scan line) and avoid flooding the console. - Selective logs:The first 20 pixels of the first line are printed to capture relevant information without cluttering the output.
- Warning logs:Special logs were added when addresses out of range are detected, since these are suspicious cases that could cause the crash.
- printf instead of std::cout:
printfIt is more direct and safer for crash debugging, since it writes directly to the stream without intermediate buffers.
Code added
// At the beginning of the file
#include <cstdio>
// Static variable to control printing
static bool debug_printed = false;
// In render_scanline(), at the beginning
if (!debug_printed) {
printf("[PPU DEBUG] render_scanline(ly=%d) | scx=%d, scy=%d | tile_map_base=0x%04X | signed_addressing=%d\n",
ly_, scx, scy, tile_map_base, signed_addressing ? 1 : 0);
}
// Inside the render loop, for the first 20 pixels
if (!debug_printed && x < 20) {
printf(" [PIXEL x=%d] map_x=%d, map_y=%d | tile_map_addr=0x%04X, tile_id=%d, tile_addr=0x%04X\n",
x, map_x, map_y, tile_map_addr, tile_id, tile_addr);
}
// At the end of the method
debug_printed = true;
Affected Files
src/core/cpp/PPU.cpp- Added debug logs inrender_scanline()to capture critical values before accessing memory
Tests and Verification
Current status:This is a debug entry. The code has been modified to add instrumentation, but has not yet been recompiled and run.
Verification plan:
- Recompilation:Execute
.\rebuild_cpp.ps1to recompile the C++ module with the new logs - Running with ROM:Execute
python main.py roms/tetris.gband capture the log output before the crash - Analysis:Parse the last line printed before the
Segmentation Faultto identify the exact values causing the problem
Expected result:The logs should show the values ofly, scx, scy, tile_map_addr, tile_id, andtile_addrfor the first pixels. The last line printed before the crash will indicate which values are causing the invalid memory access.
Sources consulted
- Bread Docs:Background and Window
- Bread Docs:Tile Data
- Bread Docs:Tile Maps
Educational Integrity
What I Understand Now
- Crash debugging:When a program crashes with a Segmentation Fault, instrumentation with logs is an essential tool to identify the exact point where the problem occurs. The logs must capture the values of the critical variables just before the operation that causes the crash.
- printf vs std::cout:For crash debugging,
printfIt is preferable because it writes directly to the stream without intermediate buffers that may not be emptied before the crash. - Address validation:Although we added validations in Step 0138, the crash persists, suggesting that there is a specific case not covered or that the crash occurs in a different location than expected.
What remains to be confirmed
- Exact crash values:We need to run the emulator with the instrumentation to see the exact values causing the Segmentation Fault.
- Crash location:Although we suspect that it occurs in
render_scanline(), the logs will confirm if this is the case or if it occurs elsewhere (for example, in the MMU when reading). - Cases not covered:Once we have the crash values, we can identify which specific cases are not being handled correctly by our validations.
Hypotheses and Assumptions
Main hypothesis:The crash occurs during rendering of a specific line when attempting to access a VRAM address outside the valid limits (0x8000-0x9FFF). The logs will allow us to confirm this hypothesis and see exactly which values are causing the problem.
Assumption:The unit test passes because it creates an ideal and predictable situation, while the real ROM uses combinations of values (tile IDs, scroll, etc.) that expose bugs in edge cases that are not covered by the test.
Next Steps
- [ ] Recompile the C++ module with the new logs:
.\rebuild_cpp.ps1 - [ ] Run the emulator with Tetris:
python main.py roms/tetris.gb - [ ] Capture and analyze log output before the crash
- [ ] Identify the exact values causing the Segmentation Fault
- [ ] Correct the bug identified in the next step (0140)
- [ ] Delete debug logs once the issue is resolved (to improve performance)