"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