6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Wed Jun 05, 2024 7:19 am

All times are UTC




Post new topic Reply to topic  [ 20 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Fri May 26, 2023 2:26 pm 
Offline
User avatar

Joined: Thu Oct 12, 2017 10:51 pm
Posts: 87
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?


Top
 Profile  
Reply with quote  
PostPosted: Fri May 26, 2023 3:49 pm 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1933
Location: Sacramento, CA, USA
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)


Top
 Profile  
Reply with quote  
PostPosted: Fri May 26, 2023 5:34 pm 
Offline

Joined: Thu Apr 23, 2020 5:04 pm
Posts: 45
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.

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?


Top
 Profile  
Reply with quote  
PostPosted: Fri May 26, 2023 7:06 pm 
Offline
User avatar

Joined: Thu Oct 12, 2017 10:51 pm
Posts: 87
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.


Top
 Profile  
Reply with quote  
PostPosted: Fri May 26, 2023 7:41 pm 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1432
Location: Scotland
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.


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

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
PostPosted: Sat May 27, 2023 2:45 pm 
Offline

Joined: Sun Nov 08, 2009 1:56 am
Posts: 390
Location: Minnesota
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.



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.


Top
 Profile  
Reply with quote  
PostPosted: Sat May 27, 2023 6:22 pm 
Offline
User avatar

Joined: Thu Oct 12, 2017 10:51 pm
Posts: 87
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?)


Top
 Profile  
Reply with quote  
PostPosted: Sun May 28, 2023 7:06 am 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1432
Location: Scotland
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?)


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

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
PostPosted: Sun May 28, 2023 9:33 am 
Offline
User avatar

Joined: Tue Nov 16, 2010 8:00 am
Posts: 2353
Location: Gouda, The Netherlands
Keep in mind that the Woz code assumes an NMOS device. For the 65C02, there are a few more instructions that require custom handling.


Top
 Profile  
Reply with quote  
PostPosted: Sun May 28, 2023 9:35 am 
Offline
User avatar

Joined: Sat Dec 01, 2018 1:53 pm
Posts: 727
Location: Tokyo, Japan
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.

Which ones are these?

_________________
Curt J. Sampson - github.com/0cjs


Top
 Profile  
Reply with quote  
PostPosted: Sun May 28, 2023 10:56 am 
Offline
User avatar

Joined: Tue Nov 16, 2010 8:00 am
Posts: 2353
Location: Gouda, The Netherlands
BRA and JMP(IND,X)


Top
 Profile  
Reply with quote  
PostPosted: Mon May 29, 2023 6:19 am 
Offline

Joined: Tue Apr 20, 2010 4:02 pm
Posts: 31
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.



Interesting method, especially for debugging ROM... However, it works only for single stepping instructions. It doesn't provide support for 'normal' breakpoints, right?


Top
 Profile  
Reply with quote  
PostPosted: Mon May 29, 2023 8:36 am 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1432
Location: Scotland
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.



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

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
PostPosted: Tue May 30, 2023 5:39 am 
Offline

Joined: Tue Apr 20, 2010 4:02 pm
Posts: 31
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.


Top
 Profile  
Reply with quote  
PostPosted: Fri Jun 02, 2023 8:47 am 
Offline

Joined: Tue Jul 24, 2012 2:27 am
Posts: 672
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.

_________________
WFDis Interactive 6502 Disassembler
AcheronVM: A Reconfigurable 16-bit Virtual CPU for the 6502 Microprocessor


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 20 posts ]  Go to page 1, 2  Next

All times are UTC


Who is online

Users browsing this forum: Google [Bot] and 35 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: