apple2js/js/debugger.ts

304 lines
8.5 KiB
TypeScript
Raw Normal View History

import { debug, toHex } from './util';
import { byte, word } from './types';
import CPU6502, { DebugInfo, flags, sizes } from './cpu6502';
export interface DebuggerContainer {
run: () => void;
stop: () => void;
getCPU: () => CPU6502;
}
type symbols = { [key: number]: string };
type breakpointFn = (info: DebugInfo) => boolean;
const alwaysBreak = (_info: DebugInfo) => { return true; };
export const dumpStatusRegister = (sr: byte) =>
[
(sr & flags.N) ? 'N' : '-',
(sr & flags.V) ? 'V' : '-',
(sr & flags.X) ? 'X' : '-',
(sr & flags.B) ? 'B' : '-',
(sr & flags.D) ? 'D' : '-',
(sr & flags.I) ? 'I' : '-',
(sr & flags.Z) ? 'Z' : '-',
(sr & flags.C) ? 'C' : '-',
].join('');
export default class Debugger {
private cpu: CPU6502;
private verbose = false;
private maxTrace = 256;
private trace: DebugInfo[] = [];
private breakpoints: Map<word, breakpointFn> = new Map();
private symbols: symbols = {};
constructor(private container: DebuggerContainer) {
this.cpu = container.getCPU();
}
stepCycles(cycles: number) {
this.cpu.stepCyclesDebug(this.verbose ? 1 : cycles, () => {
const info = this.cpu.getDebugInfo();
if (this.breakpoints.get(info.pc)?.(info)) {
debug('breakpoint', this.printDebugInfo(info));
this.container.stop();
2021-03-14 00:08:24 +00:00
return true;
}
if (this.verbose) {
debug(this.printDebugInfo(info));
} else {
2021-03-14 00:08:24 +00:00
this.updateTrace(info);
}
});
}
2021-03-14 00:08:24 +00:00
break = () => {
this.container.stop();
2021-11-29 00:20:25 +00:00
};
2021-03-14 00:08:24 +00:00
step = () => {
this.cpu.step(() => {
const info = this.cpu.getDebugInfo();
debug(this.printDebugInfo(info));
this.updateTrace(info);
});
2021-11-29 00:20:25 +00:00
};
2021-03-14 00:08:24 +00:00
2021-03-13 20:36:19 +00:00
continue = () => {
this.container.run();
2021-11-29 00:20:25 +00:00
};
2021-03-13 20:36:19 +00:00
setVerbose = (verbose: boolean) => {
this.verbose = verbose;
2021-11-29 00:20:25 +00:00
};
2021-03-13 20:36:19 +00:00
setMaxTrace = (maxTrace: number) => {
this.maxTrace = maxTrace;
2021-11-29 00:20:25 +00:00
};
2021-03-13 20:36:19 +00:00
getTrace = () => {
return this.trace.map(this.printDebugInfo).join('\n');
2021-11-29 00:20:25 +00:00
};
2021-03-13 20:36:19 +00:00
printTrace = () => {
debug(this.getTrace());
2021-11-29 00:20:25 +00:00
};
2021-03-13 20:36:19 +00:00
setBreakpoint = (addr: word, exp?: breakpointFn) => {
this.breakpoints.set(addr, exp || alwaysBreak);
2021-11-29 00:20:25 +00:00
};
2021-03-13 20:36:19 +00:00
clearBreakpoint = (addr: word) => {
this.breakpoints.delete(addr);
2021-11-29 00:20:25 +00:00
};
2021-03-13 20:36:19 +00:00
listBreakpoints = () => {
for(const [addr, fn] of this.breakpoints.entries()) {
debug(toHex(addr, 4), fn);
}
2021-11-29 00:20:25 +00:00
};
2021-03-13 20:36:19 +00:00
addSymbols = (symbols: symbols) => {
this.symbols = { ...this.symbols, ...symbols };
2021-11-29 00:20:25 +00:00
};
2021-03-13 20:36:19 +00:00
printDebugInfo = (info: DebugInfo) => {
const { pc, cmd } = info;
const symbol = this.padWithSymbol(pc);
return [
toHex(pc, 4),
'- ', symbol,
this.dumpRegisters(info),
' ',
this.dumpRawOp(cmd),
' ',
this.dumpOp(pc, cmd)
].join('');
2021-11-29 00:20:25 +00:00
};
2021-03-13 20:36:19 +00:00
dumpPC = (pc: word) => {
const b = this.cpu.read(pc);
const op = this.cpu.getOpInfo(b);
const size = sizes[op.mode];
let result = toHex(pc, 4) + '- ';
result += this.padWithSymbol(pc);
const cmd = new Array<number>(size);
for (let idx = 0, jdx = pc; idx < size; idx++, jdx++) {
cmd[idx] = this.cpu.read(jdx);
}
result += this.dumpRawOp(cmd) + ' ' + this.dumpOp(pc, cmd);
return result;
2021-11-29 00:20:25 +00:00
};
2021-03-13 20:36:19 +00:00
dumpRegisters = (debugInfo?: DebugInfo) => {
if (debugInfo === undefined) {
debugInfo = this.cpu.getDebugInfo();
}
2021-03-14 00:08:24 +00:00
const { ar, xr, yr, sr, sp } = debugInfo;
return [
2021-03-14 00:54:09 +00:00
'A=' + toHex(ar),
' X=' + toHex(xr),
' Y=' + toHex(yr),
' P=' + toHex(sr),
' S=' + toHex(sp),
' ',
dumpStatusRegister(sr),
].join('');
2021-11-29 00:20:25 +00:00
};
2021-03-13 20:36:19 +00:00
dumpPage = (start: byte, end?: byte) => {
let result = '';
if (end === undefined) {
end = start;
}
for (let page = start; page <= end; page++) {
for (let idx = 0; idx < 16; idx++) {
result += toHex(page) + toHex(idx << 4) + ': ';
for (let jdx = 0; jdx < 16; jdx++) {
const b = this.cpu.read(page, idx * 16 + jdx);
result += toHex(b) + ' ';
}
result += ' ';
for (let jdx = 0; jdx < 16; jdx++) {
const b = this.cpu.read(page, idx * 16 + jdx) & 0x7f;
if (b >= 0x20 && b < 0x7f) {
result += String.fromCharCode(b);
} else {
result += '.';
}
}
result += '\n';
}
}
return result;
2021-11-29 00:20:25 +00:00
};
2021-03-13 20:36:19 +00:00
list = (pc: word) => {
const results = [];
for (let idx = 0; idx < 20; idx++) {
const b = this.cpu.read(pc);
const op = this.cpu.getOpInfo(b);
results.push(this.dumpPC(pc));
pc += sizes[op.mode];
}
return results;
2021-11-29 00:20:25 +00:00
};
2021-03-14 00:08:24 +00:00
private updateTrace(info: DebugInfo) {
this.trace.push(info);
if (this.trace.length > this.maxTrace) {
this.trace.shift();
}
}
private padWithSymbol(pc: word): string {
const padding = ' ';
const symbol = this.symbols[pc];
let result: string = padding;
if (symbol) {
result = `${symbol}${padding.substring(symbol.length)}`;
}
return result;
}
private dumpRawOp(parts: byte[]) {
const result = new Array(4);
for (let idx = 0; idx < 4; idx++) {
if (idx < parts.length) {
result[idx] = toHex(parts[idx]);
} else {
result[idx] = ' ';
}
}
return result.join(' ');
}
private dumpOp(pc: word, parts: byte[]) {
const op = this.cpu.getOpInfo(parts[0]);
const lsb = parts[1];
const msb = parts[2];
const addr = (msb << 8) | lsb;
let val;
let off;
const toHexOrSymbol = (v: word, n?: number) => (
this.symbols[v] || ('$' + toHex(v, n))
);
let result = op.name + ' ';
switch (op.mode) {
case 'implied':
break;
case 'immediate':
result += `#${toHexOrSymbol(lsb)}`;
break;
case 'absolute':
result += `${toHexOrSymbol(addr, 4)}`;
break;
case 'zeroPage':
result += `${toHexOrSymbol(lsb)}`;
break;
case 'relative':
{
off = lsb;
if (off > 127) {
off -= 256;
}
2021-03-14 00:08:24 +00:00
pc += off + 2;
result += `${toHexOrSymbol(pc, 4)} (${off})`;
}
break;
case 'absoluteX':
result += `${toHexOrSymbol(addr, 4)},X`;
break;
case 'absoluteY':
result += `${toHexOrSymbol(addr, 4)},Y`;
break;
case 'zeroPageX':
result += `${toHexOrSymbol(lsb)},X`;
break;
case 'zeroPageY':
result += `${toHexOrSymbol(lsb)},Y`;
break;
case 'absoluteIndirect':
result += `(${toHexOrSymbol(addr, 4)})`;
break;
case 'zeroPageXIndirect':
result += `(${toHexOrSymbol(lsb)},X)`;
break;
case 'zeroPageIndirectY':
result += `(${toHexOrSymbol(lsb)},),Y`;
break;
case 'accumulator':
result += 'A';
break;
case 'zeroPageIndirect':
result += `(${toHexOrSymbol(lsb)})`;
break;
case 'absoluteXIndirect':
result += `(${toHexOrSymbol(addr, 4)},X)`;
break;
case 'zeroPage_relative':
val = lsb;
off = msb;
if (off > 127) {
off -= 256;
}
pc += off + 2;
result += `${toHexOrSymbol(val)},${toHexOrSymbol(pc, 4)} (${off})`;
break;
default:
break;
}
return result;
}
}