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)