Page 1 of 2
Good strategies for software single stepping
Posted: Fri May 26, 2023 2:26 pm
by hjalfi
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?
Re: Good strategies for software single stepping
Posted: Fri May 26, 2023 3:49 pm
by barrym95838
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.
Re: Good strategies for software single stepping
Posted: Fri May 26, 2023 5:34 pm
by vbc
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.
I think the most common implementation for software-single-stepping is to analyze the current instruction and set a breakpoint to the next instruction that will be executed. Depending on the architecture that can be relatively simple or quite tricky. Continuing from a breakpoint without removing it can be done by doing one single-step, restore the breakpoint, then go again.
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
Posted: Fri May 26, 2023 7:06 pm
by hjalfi
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.
Re: Good strategies for software single stepping
Posted: Fri May 26, 2023 7:41 pm
by drogon
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.
I don't think you need to trap BRK as there is code in the Woz stepper to check for it.
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
Re: Good strategies for software single stepping
Posted: Sat May 27, 2023 2:45 pm
by teamtempest
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.
Is it? On the C64 (and therefore presumably the VIC-20 and the PET), the IRQ routine saves the registers on the stack and then checks to see if the BRK flag is set. If so, it jumps through an indirect vector at $0316. If not (a normal IRQ), through an indirect vector at $0314.
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
Posted: Sat May 27, 2023 6:22 pm
by hjalfi
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: Good strategies for software single stepping
Posted: Sun May 28, 2023 7:06 am
by drogon
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?)
I'd look again about how Woz did it on the Apple II. You don't need to handle BRK at all.
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
Re: Good strategies for software single stepping
Posted: Sun May 28, 2023 9:33 am
by Arlet
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
Posted: Sun May 28, 2023 9:35 am
by cjs
Keep in mind that the Woz code assumes an NMOS device. For the 65C02, there are a few more instructions that require custom handling.
Which ones are these?
Re: Good strategies for software single stepping
Posted: Sun May 28, 2023 10:56 am
by Arlet
BRA and JMP(IND,X)
Re: Good strategies for software single stepping
Posted: Mon May 29, 2023 6:19 am
by rudla.kudla
I'd look again about how Woz did it on the Apple II. You don't need to handle BRK at all.
Interesting method, especially for debugging ROM... However, it works only for single stepping instructions. It doesn't provide support for 'normal' breakpoints, right?
Re: Good strategies for software single stepping
Posted: Mon May 29, 2023 8:36 am
by drogon
I'd look again about how Woz did it on the Apple II. You don't need to handle BRK at all.
Interesting method, especially for debugging ROM... However, it works only for single stepping instructions. It doesn't provide support for 'normal' breakpoints, right?
Well.. Define "normal".
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
Re: Good strategies for software single stepping
Posted: Tue May 30, 2023 5:39 am
by rudla.kudla
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.
Re: Good strategies for software single stepping
Posted: Fri Jun 02, 2023 8:47 am
by White Flame
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.