The Value of the Stack in Debugging
When a Z80 program crashes, the CPU registers often hold garbage data. The Stack, however, holds the historical sequence of return addresses created by every CALL
instruction. Examining the stack is the primary method to determine how the program arrived at the crash point—the call chain.
The Principle: You read the values on the stack, and these values are the addresses of the instructions immediately following the last few CALL
commands.
Reading the Stack After a Crash
Assuming a crash occurs and you enter a debugger or emulator environment:
- Check SP: Find the current value of the Stack Pointer (SP) register. This is the memory address where the top of the stack currently resides.
- Examine Memory: Look at the memory starting at the address stored in
SP
.
Stack Contents (LIFO):
Memory Address | Content | Meaning |
---|---|---|
SP |
Low Byte of Return Address 1 | Last subroutine called |
SP + 1 |
High Byte of Return Address 1 | |
SP + 2 |
Low Byte of Return Address 2 | Second-to-last subroutine |
SP + 3 |
High Byte of Return Address 2 | |
… | … | … |
Locating the Bug: By reading the return addresses and cross-referencing them with your source code’s listing file, you can trace the entire path the CPU took before it failed.
The Register Snapshot
When writing complex programs, it is common to create a Register Snapshot routine that you can call before a potentially buggy section of code.
Routine Goal: Save the state of all CPU registers to a fixed, safe memory location.
Snapshot Routine:
SAVE_STATE:
LD HL, SNAPSHOT_RAM_ADDR ; Address where register state will be stored
PUSH AF ; Save AF to stack
POP (HL) ; Move AF from stack to RAM (HL++)
PUSH BC ; Save BC
POP (HL) ; Move BC from stack to RAM (HL++)
; ... (Repeat for DE and HL)
RET
Usage: If the program crashes later, you can examine the memory starting at SNAPSHOT_RAM_ADDR
to see the last known good state of all the registers, which is invaluable for identifying subtle errors.
Common Stack Errors
- Stack Overflow: When the stack pointer runs into (and overwrites) valuable program data or code (usually caused by too many nested `CALL′s or not enough reserved stack space).
- Stack Underflow: When a routine performs more
POP′s than
PUSH′s, corrupting the critical return addresses and causing a crash when `RET′ is executed.