Good strategies for software single stepping
Good strategies for software single stepping
I'm curious about the feasibility of writing a simple debugger for my 6502 CP/M port.
Without hardware support, the only way of doing single-stepping or breakpoints is by patching the code. I can't hook BRK in my environment, so to do breakpoint I'd have to copy three bytes from the program, replace them with a JSR to my breakpoint routine, save all the registers etc, and then when resuming, write the three bytes back into the program and jump to them. That bit's relatively straightforward, but only if I'm planning on removing the breakpoint. If I want to either not remove the breakpoint, or single step, I think I'll end up having to emulate instructions.
This is easier than it looks, because any non-program-counter-changing instruction can be trivially emulated by patching the instruction into a routine, restoring registers, calling it, then saving registers again. But for JMP, JSR, B**, BRK, RTS and RTI I'll need to do more work. I'm sure there are nasty edge conditions. (Because my breakpoints are three bytes, I may have to emulate three instructions, for example.)
It occurs to me that someone is bound to have done this already. Any pointers?
Without hardware support, the only way of doing single-stepping or breakpoints is by patching the code. I can't hook BRK in my environment, so to do breakpoint I'd have to copy three bytes from the program, replace them with a JSR to my breakpoint routine, save all the registers etc, and then when resuming, write the three bytes back into the program and jump to them. That bit's relatively straightforward, but only if I'm planning on removing the breakpoint. If I want to either not remove the breakpoint, or single step, I think I'll end up having to emulate instructions.
This is easier than it looks, because any non-program-counter-changing instruction can be trivially emulated by patching the instruction into a routine, restoring registers, calling it, then saving registers again. But for JMP, JSR, B**, BRK, RTS and RTI I'll need to do more work. I'm sure there are nasty edge conditions. (Because my breakpoints are three bytes, I may have to emulate three instructions, for example.)
It occurs to me that someone is bound to have done this already. Any pointers?
- barrym95838
- Posts: 2056
- Joined: 30 Jun 2013
- Location: Sacramento, CA, USA
Re: Good strategies for software single stepping
I'm a fan of studying prior art for my own use. The pertinent section starts around address $FA43 and continues for 200 bytes or so.
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!
Mike B. (about me) (learning how to github)
Mike B. (about me) (learning how to github)
Re: Good strategies for software single stepping
hjalfi wrote:
Without hardware support, the only way of doing single-stepping or breakpoints is by patching the code. I can't hook BRK in my environment, so to do breakpoint I'd have to copy three bytes from the program, replace them with a JSR to my breakpoint routine, save all the registers etc, and then when resuming, write the three bytes back into the program and jump to them. That bit's relatively straightforward, but only if I'm planning on removing the breakpoint. If I want to either not remove the breakpoint, or single step, I think I'll end up having to emulate instructions.
However I think I have only ever seen this in situations where there was no instruction shorter than the breakpoint-instruction. Even then there can be some tricky cases like single-instruction loops. In your case I see much bigger problems as your breakpoint effectively overwrites up to three instructions. Single-stepping etc. aside, what if a breakpoint is not hit, but there is a jump to one of the instructions that have been overwritten?
Re: Good strategies for software single stepping
I'm relieved to see the Apple did basically what I was suggesting, so I'm presumably on the right track.
With regard to problems with three-byte breakpoints: damn, I hadn't thought of that. Without being able to use BRK I think I'm out of luck. The original 8080 version used single-byte system call instructions, but the system call addresses were under the application's control.
I think that short of some breakthrough the only thing I can do now is to try and find a way to hook the BRK vector. This is tougher than it looks because on some platforms (VIC-20, PET, BBC Micro) the original OS is still present; and at least on the VIC-20 and PET the OS vectors are somewhere really awkward and it was easier to just turn interrupts off and ignore them.
With regard to problems with three-byte breakpoints: damn, I hadn't thought of that. Without being able to use BRK I think I'm out of luck. The original 8080 version used single-byte system call instructions, but the system call addresses were under the application's control.
I think that short of some breakthrough the only thing I can do now is to try and find a way to hook the BRK vector. This is tougher than it looks because on some platforms (VIC-20, PET, BBC Micro) the original OS is still present; and at least on the VIC-20 and PET the OS vectors are somewhere really awkward and it was easier to just turn interrupts off and ignore them.
Re: Good strategies for software single stepping
hjalfi wrote:
I'm relieved to see the Apple did basically what I was suggesting, so I'm presumably on the right track.
With regard to problems with three-byte breakpoints: damn, I hadn't thought of that. Without being able to use BRK I think I'm out of luck. The original 8080 version used single-byte system call instructions, but the system call addresses were under the application's control.
I think that short of some breakthrough the only thing I can do now is to try and find a way to hook the BRK vector. This is tougher than it looks because on some platforms (VIC-20, PET, BBC Micro) the original OS is still present; and at least on the VIC-20 and PET the OS vectors are somewhere really awkward and it was easier to just turn interrupts off and ignore them.
With regard to problems with three-byte breakpoints: damn, I hadn't thought of that. Without being able to use BRK I think I'm out of luck. The original 8080 version used single-byte system call instructions, but the system call addresses were under the application's control.
I think that short of some breakthrough the only thing I can do now is to try and find a way to hook the BRK vector. This is tougher than it looks because on some platforms (VIC-20, PET, BBC Micro) the original OS is still present; and at least on the VIC-20 and PET the OS vectors are somewhere really awkward and it was easier to just turn interrupts off and ignore them.
In essence, it disassembles the instruction, copies it to an area in ZP, checking for special instructions before the copy - e.g. BRK, RTS, RTI and deals with them separately as well as "adjusting" JMP, and relative branch destinations then jumps to it.
It uses the existing BRK code as that neatly prints the saved register contents then returns to the command prompt.
But if you have to, intercepting BRK on a BBC Micro is utterly trivial and it's regularly done by language ROMs, etc.
-Gordon
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/
-
teamtempest
- Posts: 443
- Joined: 08 Nov 2009
- Location: Minnesota
- Contact:
Re: Good strategies for software single stepping
Quote:
This is tougher than it looks because on some platforms (VIC-20, PET, BBC Micro) the original OS is still present; and at least on the VIC-20 and PET the OS vectors are somewhere really awkward and it was easier to just turn interrupts off and ignore them.
When I did something like that I pointed the indirect vector at $0316 to point to my BRK routine. Then replaced whatever instruction where I wanted to look at the registers with a BRK. So all instructions could be replaced by one byte.
When I first read your post I was wondering if you planned to replace your three-byte sequence with three NOPs once you were finished with them.
You could, I suppose, write any instruction you wanted to replace with a macro and use that. The macro would check a flag to see if this was a replaceable instruction and if so, write your three-byte sequence. If not, it would write the original instruction. That way it wouldn't matter how long what was being replaced was, though you might have to re-assemble your code every time you set or cleared a "check this" flag.
Re: Good strategies for software single stepping
The problem is that I have code at 0x316... and my BIOS only just fits in the space from 0x200 and 0x1000 below the video memory, so rearranging things to leave the vectors free will be annoying (llvm-ld doesn't support areas with holes). Plus I need to figure out if any zero page addresses are used by the ROM code and reserve them, etc etc. It's all doable, it's just work, which I then need to repeat for the other six supported platforms.
Re macro: I'm talking about a _binary_ debugger here. So to set a breakpoint, I'd copy whatever was already at that address elsewhere to replace it with the BRK. As vbc said, this just isn't going to work because code elsewhere might try to jump to one of the replaced bytes. (Unless someone has a good idea?)
Re macro: I'm talking about a _binary_ debugger here. So to set a breakpoint, I'd copy whatever was already at that address elsewhere to replace it with the BRK. As vbc said, this just isn't going to work because code elsewhere might try to jump to one of the replaced bytes. (Unless someone has a good idea?)
Re: Good strategies for software single stepping
hjalfi wrote:
Re macro: I'm talking about a _binary_ debugger here. So to set a breakpoint, I'd copy whatever was already at that address elsewhere to replace it with the BRK. As vbc said, this just isn't going to work because code elsewhere might try to jump to one of the replaced bytes. (Unless someone has a good idea?)
Firstly the instruction is disassembled in-situ, mnemonics are printed and importantly the instruction length is stored.
The instruction is then checked for certain opcodes - BRK being one - this is handled without executing the instruction, then JMP and relative branches. There are handlers for JSR, RTS too. The instruction is copied to a zero page location (we know its length from the disassembly) and the JMP targets are changed - relative branches are changed to point to a JMP that's been placed next to the area in ZP the code is copied to.
Then registers restored and it's executed. The instruction runs and JMPs back to the monitor - to the place which would normally handle a real BRK so it saves the registers prints them and does it again.
Is this the best way to do it? I've no idea, but I'm looking to adopt it into my little system. One advantage is that it would let you step through ROMs if needed...
-Gordon
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/
Re: Good strategies for software single stepping
Keep in mind that the Woz code assumes an NMOS device. For the 65C02, there are a few more instructions that require custom handling.
Re: Good strategies for software single stepping
Arlet wrote:
Keep in mind that the Woz code assumes an NMOS device. For the 65C02, there are a few more instructions that require custom handling.
Curt J. Sampson - github.com/0cjs
Re: Good strategies for software single stepping
BRA and JMP(IND,X)
-
rudla.kudla
- Posts: 41
- Joined: 20 Apr 2010
Re: Good strategies for software single stepping
drogon wrote:
hjalfi wrote:
I'd look again about how Woz did it on the Apple II. You don't need to handle BRK at all.
Re: Good strategies for software single stepping
rudla.kudla wrote:
drogon wrote:
hjalfi wrote:
I'd look again about how Woz did it on the Apple II. You don't need to handle BRK at all.
You can't alter ROMs, but you can implement a plan B...
Thinking more - with a bit more code space (Which Woz was short of) and a re-write it would be easy to check the PC address against a list of addresses and "brk" or just stop when the "interpreted" code hit that address. You could do this for data operations as well as PC operations too, so from that point if view I think it's potentially a very powerful tool... You just need to write more code to maintain the list of PC breakpoints and data checkpoints.
Then you could single step or run a ROM with a list of breakpoint and data checkpoints if you needed to. I think it could be potentially be a very powerful tool - the trade off is speed.
-Gordon
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/
-
rudla.kudla
- Posts: 41
- Joined: 20 Apr 2010
Re: Good strategies for software single stepping
By 'normal' i meant having the code run and just stop at the specified address - as opposed to the breakpoint that is inserted after the single stepped instruction.
But you are right, Woz's method may be easily updated to support this, even if for the price of speed.
But you are right, Woz's method may be easily updated to support this, even if for the price of speed.
-
White Flame
- Posts: 704
- Joined: 24 Jul 2012
Re: Good strategies for software single stepping
Another option for single-stepping (not breakpoints): If you don't have any SEIs in the code, and have a CIA or equivalent, you could set a timer for 10 or so cycles, tuned to and have it hit 1 cycle after the debugger returns to your main code. That would trigger after the next instruction. If it's tied to NMI, even better.
Also note that BRK replacement for breakpoints can mung up in the presence of selfmod code.
Also note that BRK replacement for breakpoints can mung up in the presence of selfmod code.