Saturday, July 14, 2012

JMON Trace Function


I've implemented one of the features missing from JMON, and one that I had been relying on Krusader's mini monitor for: an instruction trace function. This function allows you to single step through a program, see a disassembly of the current instruction, and see the values of the hardware registers. This feature is almost indispensable for debugging code. Krusader does this but I wanted to write my own.

I can think of at least three ways to single step code. One is a hardware solution. The Apple 1 manual actually shows a circuit that does this, and presumably Woz used this circuit to debug the Apple 1. This approach is very useful for bringing up new hardware that may not yet be working, but it is complex and expensive.

A second approach is the use the breakpoint instruction (BRK). By inserting a BRK instruction in code, when executed it can jump to the break vector which can run code which shows the current values of the registers. You can then replace the BRK with the original instruction and place a BRK at the next instruction, and continue execution. A disadvantage of this approach is that it only works for code in RAM and not ROM, since the program memory must be written to with the BRK instruction.

A third approach, and the one I used, is to look at the next instruction to be executed, copy it to a buffer, and execute it in place. The instruction can be followed by code that jumps back into the trace code in the monitor program. This approach has the advantage of working even for code that is in ROM.

To make this work there are a couple of wrinkles. Some instructions change the flow of control (e.g. JMP and JSR) and would jump out of the trace code. To trace these I simulate instead of execute them, doing the equivalent of what the instruction would do. For example. a JMP instruction just needs to update the value of the program counter. These special instructions are JMP, JSR, RTS, RTI, and BRK. Similar are the branch instructions. Initially I thought I would simulate these, looking at the CPU registers to determine whether the branch was taken or not. This would get a little complex as there are about 8 branch instructions to simulate, each checking a different condition. Instead I use a simpler approach that can use the same code for all branches. I execute the branch instruction in the buffer, but I adjust the branch destination so it jumps to a known location in the trace code. Whether the branch is taken or not, it stays under control of the trace code.

My trace function produces similar output to Krusader's mini monitor. You can set the register values use the Register command, including the Program Counter. The "." command traces/executes one instruction.

The breakpoint feature of JMON works in conjunction with this. If a breakpoint is hit, it updates the register values and you can single step as desired. You can also use the Go command to execute from the current program counter address.

It took a little work to get all of this correct for all instructions. It leveraged the previous work on the disassembler and mini assembler. For example, I needed to know the length of each instruction and this information was already available in tables used by the disassembler.

A sample session is shown below. Commands entered by the user are in bold. The actual trace commands "." are not echoed.

JMON MONITOR 0.99 BY JEFF TRANTER
? R
A-00 X-00 Y-00 S-0140 P-00 ........
0000   00          BRK
A-00 X-00 Y-00 S-0140 P-00 ........
PC-6000
? R
A-00 X-00 Y-B0 S-0140 P-00 ........
6000   EA          NOP
A-Esc
? A-00 X-00 Y-B0 S-0140 P-30 ..-B....
6001   A9 01       LDA   #$01
? A-01 X-00 Y-B0 S-0140 P-30 ..-B....
6003   A0 01       LDY   #$01
? A-01 X-00 Y-01 S-0140 P-30 ..-B....
6005   A2 01       LDX   #$01
? A-01 X-01 Y-01 S-0140 P-30 ..-B....
6007   08          PHP
? A-01 X-01 Y-01 S-013F P-30 ..-B....
6008   68          PLA
? A-30 X-01 Y-01 S-0140 P-30 ..-B....
6009   38          SEC
? A-30 X-01 Y-01 S-0140 P-31 ..-B...C
600A   18          CLC
? A-30 X-01 Y-01 S-0140 P-30 ..-B....
600B   69 01       ADC   #$01
? A-31 X-01 Y-01 S-0140 P-30 ..-B....
600D   F8          SED
? A-31 X-01 Y-01 S-0140 P-38 ..-BD...
600E   69 10       ADC   #$10
? A-41 X-01 Y-01 S-0140 P-38 ..-BD...
6010   D8          CLD
? A-41 X-01 Y-01 S-0140 P-30 ..-B....
6011   4C 17 60    JMP   $6017
? A-41 X-01 Y-01 S-0140 P-30 ..-B....
6017   20 15 60    JSR   $6015
? A-41 X-01 Y-01 S-013E P-30 ..-B....
6015   EA          NOP
? A-41 X-01 Y-01 S-013E P-30 ..-B....
6016   60          RTS
? A-41 X-01 Y-01 S-0140 P-30 ..-B....
601A   A9 28       LDA   #$28


As part of my testing I modified the code to stay in trace mode and ran the Woz monitor and Apple BASIC under the trace code. They executed correctly, although a little slower than normal.

The code supports 65C02 instructions without any extra coding except the BBR and BBS commands which would need some additional branch instruction support. Some 65816 instructions should work but the new instructions which change flow of control would fail and it doesn't understand the variable length of instructions depending on the CPU mode (but it would be quite straightforward to add).

This new trace function should prove useful for debugging future code I write. As always, the code can be found here.

No comments: