Files
8bitworkshop/gen/tools/debugservice.js
T

265 lines
11 KiB
JavaScript

"use strict";
// The debugger as a service: everything a debug adapter asks of the machine,
// with plain-data arguments and results so it can sit behind an RPC. The
// VS Code emulator worker and `8bws dap` each host one.
//
// Every stop, whatever caused it, goes out through `onStop`. Forward commands
// (continue, step) only set a goal; the host's loop calls advance() to work
// toward it, a few frames at a time, so it can still take a pause request.
Object.defineProperty(exports, "__esModule", { value: true });
exports.DebugService = void 0;
const breakpoints_1 = require("../common/breakpoints");
const breakcond_1 = require("../common/breakcond");
const debugtree_1 = require("../common/debugtree");
const stackwalk_1 = require("../common/stackwalk");
const debugcontroller_1 = require("../common/debugcontroller");
const symbolfile_1 = require("../common/symbols/symbolfile");
const symbolvalue_1 = require("../common/symbols/symbolvalue");
const util_1 = require("../common/util");
const runscript_1 = require("./runscript");
// how far before an address to start disassembling, per instruction wanted
const MAX_INSN_BYTES = 4;
class DebugService {
constructor(target, onStop) {
this.target = target;
this.onStop = onStop;
this.scriptOut = '';
this.debug = new debugcontroller_1.DebugController(target);
this.script = new runscript_1.RunScript(target, s => { this.scriptOut += s; }, this.debug);
}
/** A new build: its listings and symbols. */
setBuild(build) {
this.script.setDebugContext((0, debugcontroller_1.buildDebugContext)(build));
}
capabilities() {
const t = this.target;
return {
step: t.supportsStep,
rewind: t.supportsRewind,
granularity: t.history ? t.history.core.granularity : (t.supportsStep ? 'insn' : 'frame'),
write: t.supportsWrite,
tree: !!t.platform.getDebugTree,
signals: !!t.platform.getSignals,
};
}
setBreakpoints(bps) {
return this.debug.setBreakpoints(bps).map(r => {
const res = { id: r.bp.id, verified: !r.error && r.pc >= 0 };
if (r.error)
res.message = r.error;
if (r.pc >= 0) {
res.pc = r.pc;
const src = this.debug.sourceAt(r.pc);
if (src)
res.line = src.line;
}
return res;
});
}
get running() { return this.debug.running; }
continue() { this.debug.continue(); }
step(kind, granularity = 'line') {
if (kind === 'in')
this.debug.stepIn(granularity);
else if (kind === 'over')
this.debug.stepOver(granularity);
else
this.debug.stepOut();
}
pause() { this.onStop(this.debug.pause()); }
/** Work toward the goal for up to `frames` frames. */
advance(frames) {
const stop = this.debug.run(frames);
if (stop)
this.onStop(stop);
return stop;
}
stepBack(granularity = 'line') {
const stop = this.debug.stepBack(granularity);
if (stop)
return this.onStop(stop);
// nothing further back: say where it still is
const { at, pc } = this.debug.location();
this.onStop({ reason: 'entry', at, pc, message: 'start of recording' });
}
reverseContinue() { this.onStop(this.debug.reverseContinue()); }
seekFrame(frame) { this.onStop(this.debug.seekFrame(frame)); }
location() {
const loc = this.debug.location();
const clock = this.target.getClockCount();
if (clock != null)
loc.clock = clock;
return loc;
}
/**
* Where it is and how it got there: the PC, then each call found on the
* stack (see stackwalk.ts), innermost first.
*/
callStack(maxFrames = 64) {
var _a;
const t = this.target;
const pc = t.getPC();
if (pc == null)
return [];
const regs = t.getCPUState() || {};
const frames = (0, stackwalk_1.walkStack)({
arch: t.arch,
sp: (_a = regs.SP) !== null && _a !== void 0 ? _a : 0,
read: a => t.read(a & 0xffff),
disassemble: a => t.disassemble(a & 0xffff),
isCode: a => { var _a, _b; return !!this.debug.sourceAt(a) || ((_b = (_a = this.debug.symbolAt(a)) === null || _a === void 0 ? void 0 : _a.offset) !== null && _b !== void 0 ? _b : Infinity) < 0x1000; },
}, pc, maxFrames);
return frames.map(f => {
const frame = { pc: f.pc };
const source = this.debug.sourceAt(f.pc);
if (source)
frame.source = { path: source.path, line: source.line };
const symbol = this.debug.symbolAt(f.pc);
if (symbol)
frame.symbol = symbol;
if (f.matched === false)
frame.unsure = true;
return frame;
});
}
timeline() {
const h = this.target.history;
if (!h)
return null;
return { first: h.first().frame, last: h.last().frame, now: h.now(), past: h.isInPast() };
}
registers() {
const state = this.target.getCPUState();
if (!state)
return [];
return (0, runscript_1.cpuRegisters)(state).map(({ name, value }) => {
const wide = /^(E?PC|SP|AF|BC|DE|HL|IX|IY)$/.test(name) || value > 0xff;
return { name, value, text: '$' + (0, util_1.hex)(value, wide ? 4 : 2) };
});
}
readMemory(addr, count) {
const bytes = [];
for (let i = 0; i < count; i++)
bytes.push(this.target.read((addr + i) & 0xffff) & 0xff);
return bytes;
}
/** Write bytes; returns how many. */
writeMemory(addr, bytes) {
bytes.forEach((b, i) => this.target.write((addr + i) & 0xffff, b & 0xff));
return bytes.length;
}
/** The debug tree's entries under `path` (names from earlier calls). */
debugTree(path) {
const tree = this.target.getDebugTree();
if (!tree)
throw new Error('this platform has no debug tree');
return (0, debugtree_1.treeChildren)(tree, path);
}
/** The signal tree's entries under `path` (names from earlier calls). */
signalTree(path) {
const tree = this.target.getSignals();
if (!tree)
throw new Error('this platform has no signals');
return (0, debugtree_1.treeChildren)(tree, path);
}
/** One symbol with the bytes at it (the first byte if its size is unknown). */
symbolValue(name) {
var _a;
const addr = this.debug.context.symbols[name];
const size = ((_a = this.debug.context.symbolsizes) === null || _a === void 0 ? void 0 : _a[name]) > 0 ? this.debug.context.symbolsizes[name] : undefined;
const bytes = this.readMemory(addr, Math.min(size || 1, symbolvalue_1.MAX_SYMBOL_BYTES));
return { name, addr, value: bytes[0], size, bytes };
}
/** The build's symbols, by name. */
symbols() {
return Object.keys(this.debug.context.symbols || {})
.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }))
.map(name => this.symbolValue(name));
}
/**
* `count` instructions starting `insnOffset` instructions from `addr`
* (which may be negative). Instructions vary in length, so going back
* tries each start that disassembles into `addr` and keeps the one that
* lands on it.
*/
disassemble(addr, insnOffset, count) {
let start = addr;
if (insnOffset < 0)
start = this.alignBack(addr, -insnOffset);
else
for (let i = 0; i < insnOffset; i++)
start += this.insnSize(start);
const out = [];
let a = start;
for (let i = 0; i < count; i++) {
const d = this.target.disassemble(a & 0xffff);
const n = d ? d.nbytes : 1;
let bytes = '';
for (let b = 0; b < n; b++)
bytes += (0, util_1.hex)(this.target.read((a + b) & 0xffff), 2) + ' ';
const line = { addr: a & 0xffff, bytes: bytes.trim(), text: d ? d.line : '???' };
const sym = this.debug.symbolAt(a & 0xffff);
if (sym && sym.offset === 0)
line.symbol = sym.name;
const src = this.debug.sourceAt(a & 0xffff);
if (src)
line.source = { path: src.path, line: src.line };
out.push(line);
a += n;
}
return out;
}
insnSize(addr) {
const d = this.target.disassemble(addr & 0xffff);
return d ? d.nbytes : 1;
}
alignBack(addr, n) {
for (let start = addr - n * MAX_INSN_BYTES; start < addr; start++) {
let a = start, count = 0;
while (a < addr) {
a += this.insnSize(a);
count++;
}
if (a === addr && count >= n) {
// skip the extra instructions at the front
for (let i = 0; i < count - n; i++)
start += this.insnSize(start);
return start;
}
}
return addr - n;
}
/**
* `repl`: a run-script command line (`mem $200 16`, `back 3`, `help`).
* Anything else: an expression, as in a breakpoint condition (`[score]`,
* `A`, `#mem16[ptr]`), or a symbol (shown with its contents).
*/
evaluate(expr, context) {
expr = expr.trim();
if (context === 'repl')
return this.runScript(expr);
// a symbol shows what is stored there, with `value` its address
const name = (0, symbolfile_1.resolveSymbolName)(this.debug.context.symbols, expr);
if (name != null) {
const sym = this.symbolValue(name);
return { result: (0, symbolvalue_1.formatSymbolValue)(sym), value: sym.addr };
}
const ctx = (0, breakpoints_1.makeCondContext)(this.debug.context);
const value = (0, breakcond_1.compileExpression)(expr, ctx)(this.target.getCPUState());
if (typeof value !== 'number' || isNaN(value))
throw new Error(`'${expr}' has no value`);
return { result: `$${(0, util_1.hex)(value, value > 0xff ? 4 : 2)} (${value})`, value };
}
runScript(line) {
if (line === 'help' || line === '?')
return { result: runscript_1.RUN_SCRIPT_HELP };
const before = this.target.now();
this.scriptOut = '';
this.script.run(line);
const after = this.target.now();
const moved = before.frame !== after.frame || before.step !== after.step;
return { result: this.scriptOut.replace(/\n$/, ''), moved };
}
}
exports.DebugService = DebugService;
//# sourceMappingURL=debugservice.js.map