Breakpoints and Conditions
A breakpoint stops the machine at an address. A condition decides whether it stops this time.
bp add pc 8000
bp add pc 8000 if a == 0x3b
bp add pc main_loop if mem[hl] > $40 && b != 0The condition is evaluated only after the address has matched.
Breakpoint addresses
A plain address, as in bp add pc 8000, stops whenever the Z80 reaches $8000, whatever is paged in there. An address in a command is hex: 8000, $8000 and 0x8000 are the same address, and #32768 is decimal.
A page and an offset, as in bp add pc 05:1118, stops only when the CPU executes offset $1118 of 8K page $05, in whichever slot that page is mapped. Both numbers are hex, and the offset is taken modulo $2000. On the Next the same $C000 is different code in each of hundreds of pages, so an address alone can stop in a routine you were not looking for. A page-qualified breakpoint marks no line in the disassembly, having no single Z80 address.
A name, once a symbol file is loaded: bp add pc main_loop. A number wins over a name, so a label spelled like valid hex, such as beef or add, is still read as the number. sym NAME says what a name resolves to.
bp list shows every breakpoint with its id, bp del ID removes one and bp clear removes all of them.
Watchpoints
A watchpoint stops on something other than reaching an address. Watchpoints and breakpoints share one set of ids.
Memory: wp add mem 5800 w stops on a write to $5800, r on a read, and rw or nothing on either. rw adds two watchpoints, one per direction, and reports both ids:
> wp add mem 5800
wp #4 #5 addedA register: wp add reg sp < 7F00 stops on the instruction that makes the comparison true, wherever execution is, and again each time it becomes true after having been false. The comparison is one of ==, != (or <>), <, <=, > and >=, and the value is hex. It catches a stack that runs away, which no address or memory watch can: LD SP,HL and ADD SP,n move the stack without writing memory.
wp list, wp del ID and wp clear list and remove watchpoints only; wp del refuses a breakpoint’s id. bp add mem is the old spelling of wp add mem, and answers with the new one.
Page windows
map add 6000-9FFF 19 declares that page $19 backs $6000–$9FFF in the program being debugged. The window and the page are hex, and the page must exist in the machine’s RAM.
An SLD annotation written in a declared window is armed against the declared page, in place of the page the assembler recorded or could not record. Declaring, withdrawing or clearing a window re-arms the loaded SLD’s annotations:
> map add 6000-9FFF 19
6000-9FFF is page 19 (14 annotation(s) re-armed)map list shows each window, noting where a different page is mapped there now. map del 6000 withdraws the window starting at $6000, and map clear withdraws every window. A script declares a window with map_page(0x6000, 0x9FFF, 0x19) and withdraws it with unmap_page(0x6000).
A later declaration wins for the addresses it shares with an earlier one.
Condition vocabulary
Registers a f b c d e h l af bc de hl ix iy sp pc i r af' bc' de' hl' (or af2 bc2 de2 hl2), the flags fs fz fh fpv fn fc (fp is another name for fpv), and the CPU state with no register to read it from: im, iff1, iff2 and halt.
Numbers in a condition are decimal unless prefixed: 42, 0x2a, $2a and 0b101010 are the same number. A command’s address is hex without a prefix.
Memory is mem[…]. mem[hl] is the byte the CPU would read at HL, through whatever is paged in now. A width after a colon reads more than one byte, little-endian as the machine stores them:
mem[hl] the byte at HL
mem[hl:2] the 16-bit word at HL
mem[$5C78:4] four bytes, as one numberWidths are 1 to 4.
phys[…] ignores paging and reads where the bytes are, so a condition can watch a page that is not mapped in. It takes a width too, and is refused past the end of the machine’s memory.
nr[…] and nrraw[…] read a Next register. The first is what software reads back; the second is the byte last written. They differ wherever the hardware decodes or gates what it stores, so nr[$56] != nrraw[$56] catches a paging register that is not doing what was asked of it.
port[…] is what a read of an I/O port would return, worked out without performing the read. A port Bizmuth does not expose is an error, not a value.
Any other name is a symbol from the loaded file. A symbol is its address as the CPU sees it, except inside phys[…], which takes it to where the bytes live:
pc == main_loop the address the CPU would be at
mem[player_x] read through the current paging
phys[player_x] read wherever its page isA constant defined with EQU is a number, and asking for its physical address is an error.
A name may contain dots, as in mem[header.ran], spelled as the SLD spells it. A condition naming a symbol is accepted before any SLD is loaded, and fails to evaluate until one is.
bp add pc a100 if !mem[hl] the byte at HL is zero; !hl would test the register
bp add pc 6276 if fz && sp < 0xff00
bp add pc 7752 if nr[0x56] != nrraw[0x56] a paging register not doing what was asked
bp add pc 0000 if i == 0xbd one crash at $0000, not every pass through itOperators and precedence
Arithmetic, shift and bitwise operators, loosest first:
||
&&
== != < <= > >=
& | ^
<< >>
+ -
* / %
! - ~Parentheses group, and nothing else: (hl) is the HL register in brackets, not the byte at HL.
& binds tighter than a comparison. nr[7] & 3 == 2 means (nr[7] & 3) == 2. In C the same line means something else.
Comparisons do not chain. a == b == c is refused; write a == b && b == c.
Unsatisfiable conditions
Adding a breakpoint checks whether its condition could ever be true, and says so:
> bp add pc 8000 if a & 0x0F == 0x20
bp #3 added
warning: this comparison can never be true: the left side is 0 to 15, the right side is always 32Every value has a range fixed by its kind: a byte is 0 to 255, im is 0 to 2, and a mask caps whatever it is applied to. mem[hl] == 0x1234, im == 5 and a != 0x100 are all reported.
The breakpoint is armed anyway, since the condition may be part-way through being edited.
A range is widened wherever it cannot be worked out exactly, so a warning means the condition cannot hold, not that it was not understood.
Where a symbol’s bank is known, the warning offers the condition that pins it:
warning: 'main_loop' is in bank 20; add '&& nr[$56] == 20' to mean that oneBreakpoint cost
Before each instruction Bizmuth tests one bit for the address, whatever the number of breakpoints armed, and walks the breakpoint list only when the bit is set. With nothing armed the test is skipped.
See also
- Assertions: conditions written in your source, not typed at the debugger.