Skip to content
qurm edited this page Jul 3, 2021 · 6 revisions

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.

Advanced

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]}`);}; 
Clone this wiki locally