From 45a5e63cf9de98886fb6c7bb4dbc986be81f014f Mon Sep 17 00:00:00 2001 From: Will Scullin Date: Fri, 24 Nov 2023 10:12:19 -0800 Subject: [PATCH] Use debugger from submodule --- js/apple2.ts | 3 +- js/components/util/files.ts | 4 +- js/debugger.ts | 380 ------------------------------------ submodules/cpu6502 | 2 +- test/js/debugger.spec.ts | 103 ---------- test/util/cpu.ts | 22 --- 6 files changed, 5 insertions(+), 509 deletions(-) delete mode 100644 js/debugger.ts delete mode 100644 test/js/debugger.spec.ts delete mode 100644 test/util/cpu.ts diff --git a/js/apple2.ts b/js/apple2.ts index c12e84e..9a494d5 100644 --- a/js/apple2.ts +++ b/js/apple2.ts @@ -12,6 +12,8 @@ import { Apple2IOState } from './apple2io'; import { CPU6502, CpuState, + Debugger, + DebuggerContainer, FLAVOR_6502, FLAVOR_ROCKWELL_65C02, } from '@whscullin/cpu6502'; @@ -19,7 +21,6 @@ import MMU, { MMUState } from './mmu'; import RAM, { RAMState } from './ram'; import SYMBOLS from './symbols'; -import Debugger, { DebuggerContainer } from './debugger'; import { ReadonlyUint8Array, Restorable, rom } from './types'; import { processGamepad } from './ui/gamepad'; diff --git a/js/components/util/files.ts b/js/components/util/files.ts index 7ff283e..358cb5a 100644 --- a/js/components/util/files.ts +++ b/js/components/util/files.ts @@ -1,6 +1,6 @@ +import { Debugger } from '@whscullin/cpu6502'; import Disk2 from 'js/cards/disk2'; import SmartPort from 'js/cards/smartport'; -import Debugger from 'js/debugger'; import { BlockFormat, BLOCK_FORMATS, @@ -245,7 +245,7 @@ export class SmartStorageBroker implements MassStorage { constructor( private disk2: Disk2, private smartPort: SmartPort - ) {} + ) { } setBinary( driveNo: DriveNumber, diff --git a/js/debugger.ts b/js/debugger.ts deleted file mode 100644 index 42ccb35..0000000 --- a/js/debugger.ts +++ /dev/null @@ -1,380 +0,0 @@ -import { debug, toHex } from './util'; -import { byte, word } from './types'; - -import { CPU6502, DebugInfo, flags, sizes } from '@whscullin/cpu6502'; - -export interface DebuggerContainer { - run: () => void; - stop: () => void; - isRunning: () => boolean; -} - -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 verbose = false; - private maxTrace = 256; - private trace: DebugInfo[] = []; - private breakpoints: Map = new Map(); - private symbols: symbols = {}; - - constructor( - private cpu: CPU6502, - private container: DebuggerContainer - ) {} - - 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(); - return true; - } - if (this.verbose) { - debug(this.printDebugInfo(info)); - } else { - this.updateTrace(info); - } - }); - } - - break = () => { - this.container.stop(); - }; - - step = () => { - this.cpu.step(() => { - const info = this.cpu.getDebugInfo(); - debug(this.printDebugInfo(info)); - this.updateTrace(info); - }); - }; - - continue = () => { - this.container.run(); - }; - - /** - * Restart at a given memory address. - * - * @param address Address to start execution - */ - - runAt = (address: word) => { - this.cpu.reset(); - this.cpu.setPC(address); - }; - - isRunning = () => this.container.isRunning(); - - setVerbose = (verbose: boolean) => { - this.verbose = verbose; - }; - - setMaxTrace = (maxTrace: number) => { - this.maxTrace = maxTrace; - }; - - getTrace = (count?: number) => { - return this.trace - .slice(count ? -count : undefined) - .map(this.printDebugInfo) - .join('\n'); - }; - - printTrace = (count?: number) => { - debug(this.getTrace(count)); - }; - - getStack = (size?: number) => { - const { sp } = this.cpu.getDebugInfo(); - const stack = []; - - let max = 255; - let min = 0; - if (size) { - if (sp - 3 >= 255 - size) { - min = Math.max(255 - size + 1, 0); - } else { - max = Math.min(sp + size - 4, 255); - min = Math.max(sp - 3, 0); - } - } - - for (let addr = max; addr >= min; addr--) { - const isSP = addr === sp ? '*' : ' '; - const addrStr = `$${toHex(0x0100 + addr)}`; - const valStr = toHex(this.cpu.read(0x01, addr)); - if (!size || (sp + size > addr && addr > sp - size)) { - stack.push(`${isSP} ${addrStr} ${valStr}`); - } - } - - return stack.join('\n'); - }; - - setBreakpoint = (addr: word, exp?: breakpointFn) => { - this.breakpoints.set(addr, exp || alwaysBreak); - }; - - clearBreakpoint = (addr: word) => { - this.breakpoints.delete(addr); - }; - - listBreakpoints = () => { - for (const [addr, fn] of this.breakpoints.entries()) { - debug(toHex(addr, 4), fn); - } - }; - - addSymbols = (symbols: symbols) => { - this.symbols = { ...this.symbols, ...symbols }; - }; - - 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(''); - }; - - 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(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; - }; - - dumpRegisters = (debugInfo?: DebugInfo) => { - if (debugInfo === undefined) { - debugInfo = this.cpu.getDebugInfo(); - } - const { ar, xr, yr, sr, sp } = debugInfo; - return [ - 'A=' + toHex(ar), - ' X=' + toHex(xr), - ' Y=' + toHex(yr), - ' P=' + toHex(sr), - ' S=' + toHex(sp), - ' ', - dumpStatusRegister(sr), - ].join(''); - }; - - 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; - }; - - /** - * Reads a range of memory. Will wrap at memory limit. - * - * @param address Starting address to read memory - * @param length Length of memory to read. - * @returns Byte array containing memory - */ - getMemory(address: word, length: word) { - const bytes = new Uint8Array(length); - for (let idx = 0; idx < length; idx++) { - address &= 0xffff; - bytes[idx] = this.cpu.read(address++); - } - return bytes; - } - - /** - * Writes a range of memory. Will wrap at memory limit. - * - * @param address Starting address to write memory - * @param bytes Data to write - */ - setMemory(address: word, bytes: Uint8Array) { - for (const byte of bytes) { - address &= 0xffff; - this.cpu.write(address++, byte); - } - } - - 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; - }; - - 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; - } - 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; - } -} diff --git a/submodules/cpu6502 b/submodules/cpu6502 index 4e1031d..b14cb8a 160000 --- a/submodules/cpu6502 +++ b/submodules/cpu6502 @@ -1 +1 @@ -Subproject commit 4e1031d1bfeaa930b95292269e02d5c31a3440a8 +Subproject commit b14cb8a0e495d99877c2bef9f31bfadea6e6b68a diff --git a/test/js/debugger.spec.ts b/test/js/debugger.spec.ts deleted file mode 100644 index 86dc2a3..0000000 --- a/test/js/debugger.spec.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { CPU6502 } from '@whscullin/cpu6502'; -import Debugger, { DebuggerContainer } from '../../js/debugger'; -import { MemoryPages } from '../../js/types'; -import { TestMemory } from '../util/memory'; -import { bios } from '../util/bios'; - -describe('Debugger', () => { - let cpu: CPU6502; - let debuggerContainer: DebuggerContainer; - let theDebugger: Debugger; - let memory: MemoryPages; - - beforeEach(() => { - cpu = new CPU6502(); - memory = new TestMemory(4); - - cpu.addPageHandler(memory); - cpu.addPageHandler(bios); - - debuggerContainer = { - run: jest.fn(), - stop: jest.fn(), - isRunning: jest.fn(), - }; - theDebugger = new Debugger(cpu, debuggerContainer); - }); - - describe('#utility', () => { - it('should list without symbols', () => { - const listing = theDebugger.list(0xff00); - expect(listing[0]).toEqual('FF00- 00 00 BRK #$00'); - }); - - it('should list with symbols', () => { - theDebugger.addSymbols({ 0x00: 'ZERO', 0xff00: 'ENTRY' }); - - const listing = theDebugger.list(0xff00); - expect(listing[0]).toEqual('FF00- ENTRY 00 00 BRK #ZERO'); - }); - - it('should dump page', () => { - const page = theDebugger.dumpPage(0xff); - expect(page).toContain( - 'FF80: 48 45 4C 4C 4F 0D 00 00 00 00 00 00 00 00 00 00 HELLO...........' - ); - }); - - it('should dump registers', () => { - const regs = theDebugger.dumpRegisters(); - expect(regs).toEqual('A=00 X=00 Y=00 P=20 S=FF --X-----'); - }); - - it('should dump the stack,', () => { - const stack = theDebugger.getStack(); - const lines = stack.split('\n'); - expect(lines).toHaveLength(256); - expect(lines[0]).toMatch('* $01FF 00'); - expect(lines[1]).toMatch(' $01FE 00'); - expect(lines[254]).toMatch(' $0101 00'); - expect(lines[255]).toMatch(' $0100 00'); - }); - - it('should dump the stack with size', () => { - const stack = theDebugger.getStack(32); - const lines = stack.split('\n'); - expect(lines).toHaveLength(32); - expect(lines[0]).toMatch('* $01FF 00'); - expect(lines[1]).toMatch(' $01FE 00'); - expect(lines[30]).toMatch(' $01E1 00'); - expect(lines[31]).toMatch(' $01E0 00'); - }); - - it('should dump the stack within size', () => { - const registers = cpu.getState(); - registers.sp = 0xe3; - cpu.setState(registers); - const stack = theDebugger.getStack(32); - const lines = stack.split('\n'); - expect(lines).toHaveLength(32); - expect(lines[0]).toMatch(' $01FF 00'); - expect(lines[1]).toMatch(' $01FE 00'); - expect(lines[28]).toMatch('* $01E3 00'); - expect(lines[29]).toMatch(' $01E2 00'); - expect(lines[30]).toMatch(' $01E1 00'); - expect(lines[31]).toMatch(' $01E0 00'); - }); - - it('should dump the stack with size and move the window', () => { - const registers = cpu.getState(); - registers.sp = 0xc3; - cpu.setState(registers); - const stack = theDebugger.getStack(32); - const lines = stack.split('\n'); - expect(lines).toHaveLength(32); - expect(lines[0]).toMatch(' $01DF 00'); - expect(lines[1]).toMatch(' $01DE 00'); - expect(lines[28]).toMatch('* $01C3 00'); - expect(lines[29]).toMatch(' $01C2 00'); - expect(lines[30]).toMatch(' $01C1 00'); - expect(lines[31]).toMatch(' $01C0 00'); - }); - }); -}); diff --git a/test/util/cpu.ts b/test/util/cpu.ts deleted file mode 100644 index ba2f83b..0000000 --- a/test/util/cpu.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { CpuState } from '@whscullin/cpu6502'; -import { toHex } from 'js/util'; -import { dumpStatusRegister } from 'js/debugger'; - -const detail = !!process.env.JEST_DETAIL; - -export function toReadableState(state: CpuState) { - if (detail) { - const { pc, sp, a, x, y, s } = state; - - return { - pc: toHex(pc, 4), - sp: toHex(sp), - a: toHex(a), - x: toHex(x), - y: toHex(y), - s: dumpStatusRegister(s), - }; - } else { - return state; - } -}