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.
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment