2024-01-20 Debugging ZIP
Charles Anthony
Posted on February 7, 2024
Fetch the latest code: "git pull".
This will fetch "zipd.ini" and "dp.txt"
"zipd.ini" Loads ZIP, configures instruction tracing, sets the file "dp.txt" as the source of keyboard input, runs ZIP for 8,192 instructions. It writes the instruction trace to the file “zip.debug” and quits.
zip.ini:
set debug -N zip.debug
set cpu z80
load zip.bin
dep pc 0
attach sio foo.txt
set cpu history=8192
s 8192
show cpu history=8192
q
"dp.txt" contains the text "DP\r"; when this is input to ZIP, ZIP should parse the "DP" token and search for it in the dictionary. Upon finding it, it should then execute the DP primitive which will push the current value of the dictionary pointer to the stack. It should continue parsing, find the end of the input buffer, print "OK" and wait for additional input.
altairz80 zipd.ini
A bunch of stuff will fly by; ignore it, everything is captured in the file “zip.debug”. Editing it, we see at the top:
zipd.ini-1> set debug -N zip.debug
%SIM-INFO: Debug output to "zip.debug"
Debug output to "zip.debug" at Sat Jan 20 10:17:25 2024
Altair 8800 (Z80) simulator Open SIMH V4.1-0 Current git commit id: 625b9e8d+uncommitted-changes
2921 bytes [12 pages] loaded at 0.
Step expired, PC: 09AA4 (NOP)
CPU: C0Z0S0V0H0N0 A =00 BC =0000 DE =0102 HL =0000 S =0000 P =0000 LD DE,0102h
A'=00 BC'=0000 DE'=0000 HL'=0000 IX=0000 IY=0000
CPU: C0Z0S0V0H0N0 A =00 BC =0000 DE =0102 HL =0000 S =0000 P =0003 LD A,(00F4h)
A'=00 BC'=0000 DE'=0000 HL'=0000 IX=0000 IY=0000
CPU: C0Z1S0V1H1N0 A =00 BC =0000 DE =0102 HL =0000 S =0000 P =0006 AND A
A'=00 BC'=0000 DE'=0000 HL'=0000 IX=0000 IY=0000
CPU: C0Z1S0V1H1N0 A =00 BC =0000 DE =0102 HL =0000 S =0000 P =0007 JR NZ,0011h
A'=00 BC'=0000 DE'=0000 HL'=0000 IX=0000 IY=0000
CPU: C0Z1S0V1H1N0 A =10 BC =0000 DE =0102 HL =0000 S =0000 P =0009 LD A,10h
And at the bottom:
CPU: C0Z0S1V0H0N0 A =07 BC =0074 DE =074A HL =810A S =010B P =9A9C NOP
A'=03 BC'=0000 DE'=0006 HL'=097D IX=030F IY=003D
CPU: C0Z0S1V0H0N0 A =07 BC =0074 DE =074A HL =810A S =010B P =9A9D NOP
A'=03 BC'=0000 DE'=0006 HL'=097D IX=030F IY=003D
CPU: C0Z0S1V0H0N0 A =07 BC =0074 DE =074A HL =810A S =010B P =9A9E NOP
A'=03 BC'=0000 DE'=0006 HL'=097D IX=030F IY=003D
CPU: C0Z0S1V0H0N0 A =07 BC =0074 DE =074A HL =810A S =010B P =9A9F NOP
A'=03 BC'=0000 DE'=0006 HL'=097D IX=030F IY=003D
CPU: C0Z0S1V0H0N0 A =07 BC =0074 DE =074A HL =810A S =010B P =9AA0 NOP
A'=03 BC'=0000 DE'=0006 HL'=097D IX=030F IY=003D
CPU: C0Z0S1V0H0N0 A =07 BC =0074 DE =074A HL =810A S =010B P =9AA1 NOP
A'=03 BC'=0000 DE'=0006 HL'=097D IX=030F IY=003D
CPU: C0Z0S1V0H0N0 A =07 BC =0074 DE =074A HL =810A S =010B P =9AA2 NOP
A'=03 BC'=0000 DE'=0006 HL'=097D IX=030F IY=003D
CPU: C0Z0S1V0H0N0 A =07 BC =0074 DE =074A HL =810A S =010B P =9AA3 NOP
A'=03 BC'=0000 DE'=0006 HL'=097D IX=030F IY=003D
Goodbye
The script annotate.sh processes the trace file and merges the assembler listing file into it:
./annotate.sh < zip.debug > zip.debug.annotate
Editing "zip.debug.annotate", we see:
0000 _start:
0000 11 02 01 ld de, rstmsg ; restart message address to WA
CPU: C0Z0S0V0H0N0 A =00 BC =0000 DE =0102 HL =0000 S =0000 P =0000 LD DE,0102h
A'=00 BC'=0000 DE'=0000 HL'=0000 IX=0000 IY=0000
The lines from the listing file corresponding to the address of the executed instructions have been pre-pended to each instruction trace.
Tracing the inner interpreter
The inner interpreter starts at the label next:
; WA <= @IR
; IR += 2
next: ld a,(bc) ; low byte of *IR
ld l,a ; into L
inc bc ; IR ++
ld a,(bc) ; high byte of *IR
ld h,a ; into H
inc bc ; IR ++
; CA <= @WA
; WA += 2
; PC <= CA
run: ld e,(hl)
inc hl
ld d,(hl)
inc hl
ex de,hl
; the 'go' label is for the debugging tools
go: jp (hl)
At the label run, HL points to the code field of the word to be executed. Typically, there are coded like:
db 3,"DUP"
__link__
dup: dw $+2
pop hl
push hl
push hl
nxt
In this case, the code word of the DUP word is labeled “dup”; most of the code words in the ZIP source are labeled with a assembler-legal label. The annotate.sh script can leverage that to trace the inner interpreter. The script reads the symbol table (zip.sym) generated by the assembler, and when it sees in the instruction trace that the instruction at the label run is executed, it extracts the HL value from the trace, looks that value up in the symbol table and reports the symbol name:
$ ./annotate.sh < zip.debug | grep "^inner"
inner 0057 outer
inner 0B49 type
inner 0075 inline
inner 086C aspace
inner 0B12 token
inner 080F qsearch
inner 08EB context
inner 0847 at
inner 0847 at
inner 0A8D search
inner 0991 dup
inner 0734 p_if
inner 0031 semi
inner 0734 p_if
inner 07A2 q_execute
inner 08EB context
inner 0847 at
inner 0847 at
inner 0A8D search
inner 0991 dup
inner 0734 p_if
inner 0031 semi
inner 0748 p_while
At the same time, we can examine the stack pointer and indent the labels to help keep track of nesting:
$ grep "^inner" zip.debug.annotate
inner 0057 outer
inner 0B49 type
inner 0075 inline
inner 086C aspace
inner 0B12 token
inner 080F qsearch
inner 08EB context
inner 0847 at
inner 0847 at
inner 0A8D search
inner 0991 dup
inner 0734 p_if
inner 0031 semi
inner 0734 p_if
inner 07A2 q_execute
inner 08EB context
inner 0847 at
inner 0847 at
inner 0A8D search
inner 0991 dup
inner 0734 p_if
inner 0031 semi
inner 0748 p_while
Looking at the source for outer:
outer: dw p_colon
dw type
dw inline
outer1: dw aspace
dw token
dw qsearch
dw p_if
db outer3-$
outer2: dw qnumber
dw p_end
db outer1-$
dw question
dw p_while
db outer-$
outer3: dw q_execute
dw p_while
db outer1-$
It did the TYPE, INPUT, ASPACE, TOKEN and ?SEARCH. The search succened, so the IF jumped down to the ?EXECUTE:
; : ?EXECUTE
; CONTEXT @ @
; SEARCH
; DUP IF
; MODE C@ IF
; DROP
; COMPILER @
; SEARCH
; DUP IF
; 0
; ELSE
; 1
; THEN
; STATE
; C!
; THEN
; THEN ;
; headerless
q_execute:
dw p_colon
dw context
dw at
dw at
dw search
dw dup
dw p_if
db .q3-$
dw cat
dw p_if
db .q3-$
dw drop
dw compiler
dw at
dw search
dw dup
dw p_if
db .q1-$
dw cliteral
db 0
dw p_else
db .q2-$
.q1: dw cliteral
db 1
.q2: dw state
dw cstore
.q3: dw semi
Cross checking with TIL; I did a transcription error, missed the MODE word.
dw p_if
db .q3-$
dw mode
dw cat
Oh, wait. I transcribed the wrong code; ?EXECUTE is completely wrong. Re-transcribing.
Adding *STACK, =, and C0SET needed by ?EXECUTE.
Fixed typos in *ELSE and *WHILE.
Now it gets hard; it doesn’t crash, it just goes crazy.
Posted on February 7, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.