Introducing jzIntv's Debugger
jzIntv offers a simple command-line oriented debugger. It should be familiar in style to anyone who has used the Apple ][ Monitor or DOS's DEBUG.EXE.
Contents
Debugger Overview
Invoking the Debugger
To invoke the debugger, add the "-d" flag to jzIntv's command line. For example, using "hello2.rom" from the Hello World Tutorial:
jzintv -d hello2.rom
This will invoke jzIntv, and present you with a prompt:
Loading:
hello2.rom
jzintv: Initializing Master Component and peripherals...
gfx: Searching for video modes near 320x200x8 with:
gfx: Hardware surf, Double buf, Sync blit, Software pal, Windowed
gfx: Selected: 320x200x8 with:
gfx: Software surf, Single buf, Sync blit, Hardware pal, Windowed
snd: buf_size: wanted 2048, got 2048
ay8910: Automatic sliding-window setting: 10
CP-1610 [0x0000...0x0000]
PSG0 AY8914 [0x01F0...0x01FF]
[Graphics] [0x0000...0x0000]
[Sound] [0x0000...0x0000]
Scratch RAM [0x0100...0x01EF]
System RAM [0x0200...0x035F]
EXEC ROM [0x1000...0x1FFF]
Pad Pair 0 [0x01F0...0x01FF]
STIC [0x0000...0x007F]
STIC [0x4000...0x403F]
STIC [0x8000...0x803F]
STIC [0xC000...0xC03F]
STIC (BTAB) [0x0200...0x02EF]
STIC (GRAM) [0x3000...0x3FFF]
[Event] [0x0000...0x0000]
[Rate Ctrl] [0x0000...0x0000]
ICart [R ] [0x5000...0x50FF]
CP-1610 Snoop [0x0200...0x035F]
[Debugger] [0x0000...0xFFFF]
0000 0000 0000 0000 0000 0000 0000 1000 -------Q JSRD R5,$1026 0
>
Most of this output is jzIntv's initialization. The last portion is the debugger prompt.
The Debugger Prompt
0000 0000 0000 0000 0000 0000 0000 1000 -------Q JSRD R5,$1026 0
>
The prompt shown above is the debugger's input prompt. From here, you can tell the debugger what to do next. Before each prompt, jzIntv reports specific information about the state of the machine. The following diagram illustrates:
R0 through R7 are the CPU's 8 registers. Each register is 16 bits wide. jzIntv's debugger shows their values in hexadecimal.
The "flags" field shows what CPU flags are currently set. jzIntv tracks 8 separate flags:
S | Sign Flag |
C | Carry Flag |
O | Overflow Flag |
Z | Zero Flag |
I | Interrupt Enable Flag |
D | Double Byte Data Flag |
i | Previous instruction was interruptible |
When a flag is set, jzIntv shows the letter for that flag. When the flag is clear, jzIntv shows a dash. This makes it easy to see what flags are currently set, without having to remember their exact order. The last flag position is special. It shows the current interrupt status:
q | Interrupt asserted |
b | BUSRQ asserted |
Q | Interrupt being taken |
B | CPU halted by BUSRQ |
In the example above, jzIntv shows the CPU as taking an interrupt. Really, it's coming out of reset, which is similar. Don't worry too much about interrupts for the moment.
Introducing the Registers
The CPU's registers act as a scratch pad, holding values for instructions to operate on. Some registers have special purposes. All the registers can be used for general purpose computation. Here's a quick reference to what each register can be used for.
Register | General Purpose | Shift Instructions | Indirect Pointer | Return Address |
---|---|---|---|---|
R0 | X | X | ||
R1 | X | X | X | |
R2 | X | X | X | |
R3 | X | X | X | |
R4 | X | Auto-increment | X | |
R5 | X | Auto-increment | X | |
R6 | X | Stack | X | |
R7 | * | Program Counter |
R6 and R7 are special. R6 is the stack pointer. R7 is the program counter. The assembler accepts SP and PC as aliases for R6 and R7. You can perform arbitrary arithmetic on either, although performing math on the program counter usually is a bad idea unless you really know what you're doing.
You will want to pay attention to R7 to know where you are at in your program. You can use the listing file, as described in the Hello World tutorial to relate what you wrote to what the debugger shows you.
Debugger Commands
The debugger offers a series of single-letter commands. You can find a full summary here. For this tutorial, we will focus on a small subset of these.
Command | Description |
---|---|
R <#> | Run for <#> cycles. If no argument given, runs "forever" |
S <#> | Step for <#> cycles. If no argument given, steps "forever" |
B <#> | set Breakpoint at location <#> |
M <A> <B> | show Memory at location <A>. Show at least <B> locations. |
U <A> <B> | Unassemble memory at location <A>. Show at least <B> instrs. |
Q | Quit jzIntv |
The commands are case-insensitive. That is, "R 100" (run 100 instructions) is the same as "r 100". The debugger also offers a short-cut: Pressing enter alone on a line is the same as "s 1". That is, it steps a single instruction.
First Steps: Watching Things Happen
Start the debugger using "hello2.rom" from the Hello World Tutorial's Example 2:
jzintv -d hello2.rom
Once at the debugger prompt, press [Enter]
a couple of times. You should see output similar to the following:
0000 0000 0000 0000 0000 0000 0000 1000 -------- JSRD R5,$1026 0 > Starting jzIntv... RD a=1000 d=0004 CP-1610 (PC = $1000) t=0 RD a=1001 d=0112 CP-1610 (PC = $1000) t=0 RD a=1002 d=0026 CP-1610 (PC = $1000) t=0 0000 0000 0000 0000 0000 1003 0000 1026 -------- MVII #$02f1,R6 12 > RD a=1026 d=02BE CP-1610 (PC = $1026) t=12 RD a=1027 d=02F1 CP-1610 (PC = $1026) t=12 0000 0000 0000 0000 0000 1003 02f1 1028 ------i- JSR R5,$1a83 21 > RD a=1028 d=0004 CP-1610 (PC = $1028) t=21 RD a=1029 d=0118 CP-1610 (PC = $1028) t=21 RD a=102A d=0283 CP-1610 (PC = $1028) t=21 0000 0000 0000 0000 0000 102B 02f1 1A83 ------i- PSHR R5 33 > RD a=1A83 d=0275 CP-1610 (PC = $1A83) t=33 WR a=02F1 d=102B CP-1610 (PC = $1A83) t=33 0000 0000 0000 0000 0000 102B 02f2 1A84 -------- PSHR R0 42 >
As you can see, jzIntv shows you the current status and the next instruction it will execute at each prompt. As each instruction executes, it also outputs memory transactions as they go by. This allows you to watch what the CPU is reading or writing. The following diagram illustrates how to interpret each of these lines.
These lines show you every read and write the CPU makes. The debugger normally suppresses these unless you step through code as we have above.
In this particular code sequence, we can see the EXEC begin to initialize itself. First, it jumps from the start of ROM to the first real bit of code. Then it sets up the stack pointer, and jumps to yet another subroutine. Notice how the register values, particularly the program counter (R7), stack pointer (R6) and return address (R5) change values. Click on the instructions themselves to get a description of what each one does.
Running Ahead to Our Code
At this point, it would be useful to jump ahead to our own program code. The simulated Intellivision needs to run the EXEC code that leads up our program, but we want the simulation to stop at the first instruction of our code. We accomplish this with a breakpoint.
Take a look at hello2.lst
from the Hello World's example 2. Below is just the first portion:
00005000 ROMHDR 0000500d ZERO
00005022 MAIN 0000500f ONES
00005014 TITLE 0000503d PRINT.FLS
0000503b here 0000503d PRINT
0000503e PRINT.LS 00005048 PRINT.S
00005041 PRINT.FLP 00005042 PRINT.LP
00005043 PRINT.P 00005044 PRINT.R
00005053 PRINT.1st 0000504d PRINT.tloop
� ROMW 16
0x5000 ORG $5000
;------------------------------------------------------------------------------
; EXEC-friendly ROM header.
;------------------------------------------------------------------------------
5000 000d 0050 ROMHDR: BIDECLE ZERO ; MOB picture base (points to NULL list)
5002 000d 0050 BIDECLE ZERO ; Process table (points to NULL list)
5004 0022 0050 BIDECLE MAIN ; Program start address
5006 000d 0050 BIDECLE ZERO ; Bkgnd picture base (points to NULL list)
5008 000f 0050 BIDECLE ONES ; GRAM pictures (points to NULL list)
500a 0014 0050 BIDECLE TITLE ; Cartridge title/date
500c 03c0 DECLE $03C0 ; Flags: No ECS title, run code after title,
; ... no clicks
500d 0000 ZERO: DECLE $0000 ; Screen border control
500e 0000 DECLE $0000 ; 0 = color stack, 1 = f/b mode
500f 0001 0001 0001 ONES: DECLE 1, 1, 1, 1, 1 ; Color stack initialization
5012 0001 0001
;------------------------------------------------------------------------------
5014 006b 0048 0065 TITLE DECLE 107, "Hello World!", 0
5017 006c 006c 006f 0020 0057 006f 0072 006c
501f 0064 0021 0000
5022 0002 MAIN EIS ; Enable interrupts
5023 0004 0150 003d CALL PRINT.FLS
5026 0007 DECLE 7 ; 7 is the color number for "white"
5027 02c9 DECLE $200 + 10*20 + 1
5028 0020 0020 0043 DECLE " Copyright 2007 ", 0
502b 006f 0070 0079 0072 0069 0067 0068 0074
5033 0020 0032 0030 0030 0037 0020 0020 0000
503b 0220 0001 here B here ; Spin forever.
From this, you can see that the first instruction (the "EIS" instruction) is at location $5022. Use the "B" command to set a breakpoint here, and the "R" command to tell jzintv to run. jzIntv will stop when it reaches the breakpoint. The boldface portions below illustrate the commands you should type:
0000 0000 0000 0000 0000 102B 02f2 1A84 -------- PSHR R0 42 > b 5022 Set breakpoint at $5022 > r Hit breakpoint at $5022 0000 C0C0 0290 8007 5022 1E87 02f2 5022 ------i- EIS 54475 >
Pulling It Apart
Now we can look ahead at the next couple of instructions that will execute. The "U" command will Unassemble the next several instructions. "U" by itself starts at the current instruction and outputs the next several:
> u $5022: 0002 EIS $5023: 0004 0150 003d JSR R5,$503d $5026: 0007 SETC $5027: 02c9 ADD@ R1,R1 $5028: 0020 NEGR R0 $5029: 0020 NEGR R0 $502A: 0043 SWAP R3 $502B: 006f SAR R3,2 $502C: 0070 RRC R0 $502D: 0079 SARC R1 $502E: 0072 RRC R2 $502F: 0069 SAR R1 $5030: 0067 SLR R3,2 $5031: 0068 SAR R0 $5032: 0074 RRC R0,2 $5033: 0020 NEGR R0 $5034: 0032 GSWD R2 >
You can clearly see our EIS instruction. Looking back at the listing, the next instruction should be a CALL. The disassembly shows "JSR R5". This is correct: CALL is an alias for JSR R5. What about the rest of this?
If you look in Example 2's source code, you'll see that the CALL was followed by some data:
CALL PRINT.FLS
DECLE 7 ; 7 is the color number for "white"
DECLE $200 + 10*20 + 1
DECLE " Copyright 2007 ", 0
jzIntv's debugger doesn't know that there is data after the CALL instruction, so it assumes it's code when it disassembles it. This gives amusing results such as we saw above. We can verify, however, that the data is what we expect it to be by using the "M" command to display a memory dump.
> m5022 5020: 0021 0000 0002 0004 0150 003D 0007 02C9 # .........P...... 5028: 0020 0020 0043 006F 0070 0079 0072 0069 # .....C.o.p.y.r.i 5030: 0067 0068 0074 0020 0032 0030 0030 0037 # .g.h.t...2.0.0.7 5038: 0020 0020 0000 0220 0001 02A9 02AC 0200 # .............¼.. 5040: 0007 02A9 02AC 02A8 0275 0085 0007 000F # .....¼.¿.u...... 5048: 0006 0049 0071 0200 0006 0338 0020 004C # ...I.q.....8...L 5050: 0048 00C8 0260 02A8 0080 022C 0009 0059 # .H.╚...¿.......Y 5058: 0028 0061 00C7 00AF 02B7 0000 0000 0000 # ...a.╟.».╖...... 5060: 0000 0000 0000 0000 0000 0000 0000 0000 # ................ >
The format of the memory dump is simple. At the left is the starting address. In the middle are hexadecimal values for 8 locations. At the right is an ASCII representation of the data. As you can see, starting on the second row at location $5028, the phrase " Copyright 2007 " is right where we expect it. The extra '.' characters between letters are due to the fact that memory locations are 16-bits wide, but characters are only 8 bits. Additional information could be in those other bits.