hoglet wrote:How would it then single step 10 instructions (with disassembly)?
Dave, I realised I hadn't addresses the question of getting the cpu_debug_t structure. In B-Em which doesn't really support multiple instances of anything it would be possible to just have an array of these. It would probably still be better for the actual definition to live in the CPU module concerned and the debugger could then have an array of pointers with a NULL to indicate the end of the list. That means responding to command to enable/disable debugging or a command line option to do the same it could search this array, check the name and called the debug_enable function for the appropriate CPU. That doesn't address dynamically loadable CPUs - is that a feature of PiTubeDirect?
That does mean there would have to be separate structures for the main (I/O) 6502 and the tube 6502 though they could share the disassemble function.
So my thinking was that CPU would either be in debug mode or free run mode. When in debug mode it would call the debug_ functions instead of its native ones. So as an example, assume running this on the core 6502 and b-em has just been started with the -debug flag so the sequence should be:
debugger finds cpu_debug_t core 6502 in table of processors.
debugger calls debug_enable(1) on that 6502.
6502 calls debug_memread(cpu, 0xfffc)
debugger checks for "break on read" for that address - finds none.
debugger calls cpu->memread(0xfffc) and returns the return value from that as the return value from debug_memread
repeat the three lines above for the byte of the start address (0xfffd)
6502 calls debug_preexec(cpu, 0xe364) (this is on a master)
debugger starts in single step mode so calls cpu->dissassemble(0xe364, ...) and prints the disassembled instruction and waits for a command.
Let's assume the user asks to trace 10 instructions - debugger keeps this count internally and returns from debug_preexec.
So the next nine iterations go something like:
CPU calls debug_memfetch to fetch the opcode.
debugger checks for "break on read" for the instruction address - finds none, calls cpu->memfetch and returns its return value.
CPU calls debug_memfetch for any immediate operand/address/offset
same checks as for opcode.
CPU calls debug_memread for any memory operand
debugger checks for "break on read" for the data address - finds none, calls cpu->memread and returns its return value.
If result is to go back to memory rather than a register, CPU calls debug_writeread(cpu, ...)
debugger checks for "break on write", finds none, calls cpu->memwrite.
CPU moves on to the next instruction, calls debug_preexec again.
debugger calls cpu->disassemble, prints result.
debugger calls cpu->reg_print for each register and prints result.
debugger decrements count, not zero, returns from debug_preexec
the tenth iteration is obviously similar but debug_preexec will cause the debugger to prompt for what to do next.
The intention of the debug_memread/debug_memfetch/debug_memwrite functions is that the debugger can implement breakpoints on memory reads/write which could check only the address being read/written to or could check the value read/written too. Something similar could be done with post-read, where the CPU provides both the address and the value read, and pre-write, where the CPU provides the address and the value due to be written, but post-read in particular doesn't allow a breakpoint just before an I/O register is read which might have side effects such as clearing a pending interrupt etc.
Thinking of I/O it occurs to me the Z80 has a separate I/O address space. That could be accommodated by a couple of IO port functions similar to the memory ones. CPUs that don't use these could leave them NULL.