-
Notifications
You must be signed in to change notification settings - Fork 68
Debugger
jsbeeb features a simple debugger.
Bring up the debugger with ctrl-HOME.
Once in the debugger the following keys are active:
-
g
- resume normal running -
n
- single step to the next instruction -
m
- step "over" that is, single step but don't follow JSRs -
o
- step "out" that is, run until an RTS -
j
/k
- scroll up and down in the disassembler view -
u
/i
- scroll up and down in the memory view. Hold shift to scroll faster -
b
- go "back" in the disassembler stack. clicking the blue highlighted addresses takes you to the destination of JSR etc. 'b' pops back to where you were disassembling before. -
t
- toggle breakpoint on the current instruction (also you can click in the margin)
CPU breakpoints can be toggled by clicking in the margin of the disassembly. Memory watches and more sophisticated breakpoints are also supported, but require some JavaScript.
To set conditional breakpoints, or to instrument memory reads and writes, bring up the Javascript console. You can do this from the Chrome/Firefox menu, or by pressing the hotkey associated with it. In Chrome it's ctrl-shift-J
but note you must already be in the debugger (ctrl-HOME
) else jsbeeb will interpret the ctrl-shift-J
as a BBC keypress.
In the Javascript console, most interaction is via the processor
object. It has readmem
and writemem
functions which read and write the BBC memory map.
processor.readmem(0x2b99)
255
processor.writemem(0x2b99,0)
undefined
To set breakpoints, there are are processor.debugInstruction
, processor.debugRead
and processor.debugWrite
hooks. To add a hook you supply a function which is run on every instruction, or read, or write respectively. It may then choose to stop the emulation on a breakpoint, or simply just print something out. You add
the function to the relevant debug hook, and if the instruction debug returns true
, a breakpoint is entered.
The instruction hook gets the program counter as a parameter, the read gets the address, and the write gets the address and value to be written. The CPU registers are available as processor.a
etc.
A very simple example to breakpoint on any instruction executing at $e00 with an accumulator of 12 (will break due to the return true
)
processor.debugInstruction.add(function(pc) { return pc == 0xe00 && processor.a == 12; });
Or how about instrumenting every write to MODE 7 memory?
processor.debugWrite.add(function(addr, val) { if (addr >= 0x7c00 && addr < 0x8000) console.log("Wrote " + val, " to screen RAM at pc=" + processor.pc); });
To remove your function from a hook:
processor.debugInstruction.clear;
As a printing convenience, there's a utils package that supplies hexbyte
and hexword
functions:
utils = require('utils');
console.log(utils.hexword(1234)); // prints 04d2
Returning a truthy value from any of the debug functions will break into the debugger.
Also of use is a debug function on the processor processor.dumpTrace()
which prints the last 256 values of PC, A, X and Y.
processor.dumpTrace()
26be EOR ($86),Y ; $ 04 01 05
26c0 STA ($86),Y ; $ 04 01 05
26c2 DEY 04 01 04
26c3 BPL $26b9 04 01 04
26b9 LDA $2058,Y 2c 01 04
...
As the console is a full javascript REPL, new variables and functions can be declared and used to work with the jsbeeb objects. This declares a new 64K array, representing the BBC micro address space and increments the element for on each instruction as it is executed. This will not break on each instruction.
counts = new Uint32Array(65536); // 64K address space
processor.debugInstruction.add((pc) => { counts[pc]++; })
// After running for some time, the data can be output to the console
for (let x = 0x2600; x < 0x2B00; ++x)
if ( counts[x] > 0 ) {
console.log(`${utils.hexword(x)}, "${processor.opcodes.opcodes[instructs[x]]}", ${counts[x]}, ${cycles[x]}`);};