DEV Community

Charles Anthony
Charles Anthony

Posted on

2024-01-20 Debugging ZIP

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 
Enter fullscreen mode Exit fullscreen mode

"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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

The script annotate.sh processes the trace file and merges the assembler listing file into it:

./annotate.sh < zip.debug > zip.debug.annotate 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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) 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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-$ 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

Cross checking with TIL; I did a transcription error, missed the MODE word.

 dw p_if db .q3-$ dw mode dw cat 
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)