/** * @fileoverview Implements the PDP11 Debugger component. * @author Jeff Parsons * @copyright © Jeff Parsons 2012-2017 * * This file is part of PCjs, a computer emulation software project at . * * PCjs is free software: you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with PCjs. If not, * see . * * You are required to include the above copyright notice in every modified copy of this work * and to display that copyright notice when the software starts running; see COPYRIGHT in * . * * Some PCjs files also attempt to load external resource files, such as character-image files, * ROM files, and disk image files. Those external resource files are not considered part of PCjs * for purposes of the GNU General Public License, and the author does not claim any copyright * as to their contents. */ "use strict"; if (NODE) { var Str = require("../../shared/es6/strlib"); var Usr = require("../../shared/es6/usrlib"); var Web = require("../../shared/es6/weblib"); var Component = require("../../shared/es6/component"); var Debugger = require("../../shared/es6/debugger"); var Keys = require("../../shared/es6/keys"); var State = require("../../shared/es6/state"); var PDP11 = require("./defines"); var BusPDP11 = require("./bus"); var MemoryPDP11 = require("./memory"); var MessagesPDP11 = require("./messages"); } /** * DebuggerPDP11 Address Object * * addr address * fPhysical true if this is a physical address * fTemporary true if this is a temporary breakpoint address * nBase set if the address contained an explicit base (eg, 16, 10, 8, etc) * sCmd set for breakpoint addresses if there's an associated command string * aCmds preprocessed commands (from sCmd) * * @typedef {{ * addr:(number|null), * fPhysical:(boolean), * fTemporary:(boolean), * nBase:(number|undefined), * sCmd:(string|undefined), * aCmds:(Array.|undefined) * }} DbgAddrPDP11 */ var DbgAddrPDP11; class DebuggerPDP11 extends Debugger { /** * DebuggerPDP11(parmsDbg) * * The DebuggerPDP11 component supports the following optional (parmsDbg) properties: * * commands: string containing zero or more commands, separated by ';' * * messages: string containing zero or more message categories to enable; * multiple categories must be separated by '|' or ';'. Parsed by messageInit(). * * The DebuggerPDP11 component is an optional component that implements a variety of user * commands for controlling the CPU, dumping and editing memory, etc. * * @param {Object} parmsDbg */ constructor(parmsDbg) { if (DEBUGGER) { super(parmsDbg); /* * Since this Debugger doesn't use replaceRegs(), we can use parentheses instead of braces. */ this.fInit = false; this.fParens = true; /* * Most commands that require an address call parseAddr(), which defaults to dbgAddrNextCode * or dbgAddrNextData when no address has been given. doDump() and doUnassemble(), in turn, * update dbgAddrNextData and dbgAddrNextCode, respectively, when they're done. * * For TEMPORARY breakpoint addresses, we set fTemporary to true, so that they can be automatically * cleared when they're hit. */ this.dbgAddrNextCode = this.newAddr(); this.dbgAddrNextData = this.newAddr(); this.dbgAddrAssemble = this.newAddr(); /* * aSymbolTable is an array of SymbolTable objects, one per ROM or other chunk of address space, * where each object contains the following properties: * * sModule * addr (physical address, if any; eg, symbols for a ROM) * len * aSymbols * aOffsets * * See addSymbols() for more details, since that's how callers add sets of symbols to the table. */ this.aSymbolTable = []; /* * clearBreakpoints() initializes the breakpoints lists: aBreakExec is a list of addresses * to halt on whenever attempting to execute an instruction at the corresponding address, * and aBreakRead and aBreakWrite are lists of addresses to halt on whenever a read or write, * respectively, occurs at the corresponding address. * * NOTE: Curiously, after upgrading the Google Closure Compiler from v20141215 to v20150609, * the resulting compiled code would crash in clearBreakpoints(), because the (renamed) aBreakRead * property was already defined. To eliminate whatever was confusing the Closure Compiler, I've * explicitly initialized all the properties that clearBreakpoints() (re)initializes. */ this.aBreakExec = this.aBreakRead = this.aBreakWrite = []; this.clearBreakpoints(); /* * The new "bn" command allows you to specify a number of instructions to execute and then stop; * "bn 0" disables any outstanding count. */ this.nBreakInstructions = 0; /* * Execution history is allocated by historyInit() whenever checksEnabled() conditions change. * Execution history is updated whenever the CPU calls checkInstruction(), which will happen * only when checksEnabled() returns true (eg, whenever one or more breakpoints have been set). * This ensures that, by default, the CPU runs as fast as possible. */ this.iInstructionHistory = 0; this.aInstructionHistory = []; this.nextHistory = undefined; this.historyInit(); /* * Initialize DebuggerPDP11 message support. */ this.dbg = this; this.afnDumpers = {}; this.bitsMessage = this.bitsWarning = 0; this.sMessagePrev = null; this.aMessageBuffer = []; this.messageInit(parmsDbg['messages']); this.sInitCommands = parmsDbg['commands']; /* * Define remaining miscellaneous DebuggerPDP11 properties. */ this.opTable = DebuggerPDP11.OPTABLE; this.aOpReserved = []; this.nStep = 0; this.sCmdTracePrev = null; this.sCmdDumpPrev = null; this.fIgnoreNextCheckFault = false; // TODO: Does this serve any purpose on a PDP-11? this.nSuppressBreaks = 0; this.cInstructions = this.cInstructionsStart = 0; this.nCycles = this.nCyclesStart = this.msStart = 0; this.controlDebug = null; this.panel = null; /* * Make it easier to access DebuggerPDP11 commands from an external REPL (eg, the WebStorm * "live" console window); eg: * * pdp11('r') * pdp11('dw 0:0') * pdp11('h') * ... */ var dbg = this; if (window) { if (window[PDP11.APPCLASS] === undefined) { window[PDP11.APPCLASS] = function(s) { return dbg.doCommands(s); }; } } else { if (global[PDP11.APPCLASS] === undefined) { global[PDP11.APPCLASS] = function(s) { return dbg.doCommands(s); }; } } } // endif DEBUGGER } /** * getAddr(dbgAddr, fWrite, nb) * * @this {DebuggerPDP11} * @param {DbgAddrPDP11|null} [dbgAddr] * @param {boolean} [fWrite] * @param {number} [nb] number of bytes to check (1 or 2); default is 1 * @return {number} is the corresponding linear address, or PDP11.ADDR_INVALID */ getAddr(dbgAddr, fWrite, nb) { var addr = dbgAddr && dbgAddr.addr; if (addr == null) addr = PDP11.ADDR_INVALID; return addr; } /** * newAddr(addr, fPhysical, nBase) * * Returns a NEW DbgAddrPDP11 object, initialized with specified values and/or defaults. * * @this {DebuggerPDP11} * @param {number|null} [addr] * @param {boolean} [fPhysical] * @param {number} [nBase] * @return {DbgAddrPDP11} */ newAddr(addr = null, fPhysical = false, nBase) { return {addr: addr, fPhysical: fPhysical, fTemporary: false, nBase: nBase}; } /** * setAddr(dbgAddr, addr) * * Updates an EXISTING DbgAddrPDP11 object, initialized with specified values and/or defaults. * * @this {DebuggerPDP11} * @param {DbgAddrPDP11} dbgAddr * @param {number} addr * @return {DbgAddrPDP11} */ setAddr(dbgAddr, addr) { dbgAddr.addr = addr; dbgAddr.fTemporary = false; dbgAddr.nBase = undefined; return dbgAddr; } /** * packAddr(dbgAddr) * * Packs a DbgAddrPDP11 object into an Array suitable for saving in a machine state object. * * @this {DebuggerPDP11} * @param {DbgAddrPDP11} dbgAddr * @return {Array} */ packAddr(dbgAddr) { return [dbgAddr.addr, dbgAddr.fPhysical, dbgAddr.nBase, dbgAddr.fTemporary, dbgAddr.sCmd]; } /** * unpackAddr(aAddr) * * Unpacks a DbgAddrPDP11 object from an Array created by packAddr() and restored from a saved machine state. * * @this {DebuggerPDP11} * @param {Array} aAddr * @return {DbgAddrPDP11} */ unpackAddr(aAddr) { var dbgAddr = this.newAddr(aAddr[0], aAddr[1], aAddr[2]); dbgAddr.fTemporary = aAddr[3]; if (aAddr[4]) { dbgAddr.aCmds = this.parseCommand(dbgAddr.sCmd = aAddr[4]); } return dbgAddr; } /** * initBus(bus, cpu, dbg) * * @this {DebuggerPDP11} * @param {ComputerPDP11} cmp * @param {BusPDP11} bus * @param {CPUStatePDP11} cpu * @param {DebuggerPDP11} dbg */ initBus(cmp, bus, cpu, dbg) { this.bus = bus; this.cmp = cmp; this.cpu = cpu; this.panel = cmp.panel; /* * Re-initialize Debugger message support if necessary */ var sMessages = /** @type {string|undefined} */ (cmp.getMachineParm('messages')); if (sMessages) this.messageInit(sMessages); if (this.cpu.model < PDP11.MODEL_1140) { this.aOpReserved = this.aOpReserved.concat(DebuggerPDP11.OP1140); } if (this.cpu.model < PDP11.MODEL_1145) { this.aOpReserved = this.aOpReserved.concat(DebuggerPDP11.OP1145); } this.messageDump(MessagesPDP11.BUS, function onDumpBus(asArgs) { dbg.dumpBus(asArgs); }); this.setReady(); } /** * setBinding(sType, sBinding, control, sValue) * * @this {DebuggerPDP11} * @param {string|null} sType is the type of the HTML control (eg, "button", "textarea", "register", "flag", "rled", etc) * @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "debugInput") * @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement) * @param {string} [sValue] optional data value * @return {boolean} true if binding was successful, false if unrecognized binding request */ setBinding(sType, sBinding, control, sValue) { var dbg = this; switch (sBinding) { case "debugInput": this.bindings[sBinding] = control; this.controlDebug = control; /* * For halted machines, this is fine, but for auto-start machines, it can be annoying. * * control.focus(); */ control.onkeydown = function onKeyDownDebugInput(event) { var sCmd; if (event.keyCode == Keys.KEYCODE.CR) { sCmd = control.value; control.value = ""; dbg.doCommands(sCmd, true); } else if (event.keyCode == Keys.KEYCODE.ESC) { control.value = sCmd = ""; } else { if (event.keyCode == Keys.KEYCODE.UP) { sCmd = dbg.getPrevCommand(); } else if (event.keyCode == Keys.KEYCODE.DOWN) { sCmd = dbg.getNextCommand(); } if (sCmd != null) { var cch = sCmd.length; control.value = sCmd; control.setSelectionRange(cch, cch); } } if (sCmd != null && event.preventDefault) event.preventDefault(); }; return true; case "debugEnter": this.bindings[sBinding] = control; Web.onClickRepeat( control, 500, 100, function onClickDebugEnter(fRepeat) { if (dbg.controlDebug) { var sCmd = dbg.controlDebug.value; dbg.controlDebug.value = ""; dbg.doCommands(sCmd, true); return true; } if (DEBUG) dbg.log("no debugger input buffer"); return false; } ); return true; case "step": this.bindings[sBinding] = control; Web.onClickRepeat( control, 500, 100, function onClickStep(fRepeat) { var fCompleted = false; if (!dbg.isBusy(true)) { dbg.setBusy(true); fCompleted = dbg.stepCPU(fRepeat? 1 : 0, null); dbg.setBusy(false); } return fCompleted; } ); return true; default: break; } return false; } /** * setFocus(fScroll) * * @this {DebuggerPDP11} * @param {boolean} [fScroll] (true if you really want the control scrolled into view) */ setFocus(fScroll) { if (this.controlDebug) { /* * This is the recommended work-around to prevent the browser from scrolling the focused element * into view. The CPU is not a visual component, so when the CPU wants to set focus, the primary intent * is to ensure that keyboard input is fielded properly. */ var x = 0, y = 0; if (!fScroll && window) { x = window.scrollX; y = window.scrollY; } this.controlDebug.focus(); if (!fScroll && window) { window.scrollTo(x, y); } } } /** * mapUnibus(addr) * * @this {DebuggerPDP11} * @param {number} addr * @return {number} */ mapUnibus(addr) { return this.cpu.mapUnibus(addr); } /** * getByte(dbgAddr, inc) * * We must route all our memory requests through the CPU now, in case paging is enabled. * * @this {DebuggerPDP11} * @param {DbgAddrPDP11} dbgAddr * @param {number} [inc] * @return {number} */ getByte(dbgAddr, inc) { var b = 0xff; var addr = this.getAddr(dbgAddr, false, 1); if (addr !== PDP11.ADDR_INVALID) { b = (dbgAddr.fPhysical || addr > 0xffff)? this.bus.getByteDirect(this.mapUnibus(addr)) : this.cpu.getByteSafe(addr); if (inc) this.incAddr(dbgAddr, inc); } return b; } /** * getWord(dbgAddr, inc) * * @this {DebuggerPDP11} * @param {DbgAddrPDP11} dbgAddr * @param {number} [inc] * @return {number} */ getWord(dbgAddr, inc) { var w = 0xffff; var addr = this.getAddr(dbgAddr, false, 2); if (addr !== PDP11.ADDR_INVALID) { w = (dbgAddr.fPhysical || addr > 0xffff)? this.bus.getWordDirect(this.mapUnibus(addr)) : this.cpu.getWordSafe(addr); if (inc) this.incAddr(dbgAddr, inc); } return w; } /** * setByte(dbgAddr, b, inc) * * @this {DebuggerPDP11} * @param {DbgAddrPDP11} dbgAddr * @param {number} b * @param {number} [inc] */ setByte(dbgAddr, b, inc) { var addr = this.getAddr(dbgAddr, true, 1); if (addr !== PDP11.ADDR_INVALID) { if (dbgAddr.fPhysical || addr > 0xffff) { this.bus.setByteDirect(this.mapUnibus(addr), b); } else { this.cpu.setByteSafe(addr, b); } if (inc) this.incAddr(dbgAddr, inc); this.cmp.updateDisplays(-1); } } /** * setWord(dbgAddr, w, inc) * * @this {DebuggerPDP11} * @param {DbgAddrPDP11} dbgAddr * @param {number} w * @param {number} [inc] */ setWord(dbgAddr, w, inc) { var addr = this.getAddr(dbgAddr, true, 2); if (addr !== PDP11.ADDR_INVALID) { if (dbgAddr.fPhysical || addr > 0xffff) { this.bus.setWordDirect(this.mapUnibus(addr), w); } else { this.cpu.setWordSafe(addr, w); } if (inc) this.incAddr(dbgAddr, inc); this.cmp.updateDisplays(-1); } } /** * parseAddr(sAddr, fCode, fNoChecks, fPrint) * * Address evaluation and validation (eg, range checks) are no longer performed at this stage. That's * done later, by getAddr(), which returns PDP11.ADDR_INVALID for invalid segments, out-of-range offsets, * etc. The Debugger's low-level get/set memory functions verify all getAddr() results, but even if an * invalid address is passed through to the Bus memory interfaces, the address will simply be masked with * bus.nBusMask; in the case of PDP11.ADDR_INVALID, that will generally refer to the top of the physical * address space. * * @this {DebuggerPDP11} * @param {string|undefined} sAddr * @param {boolean} [fCode] (true if target is code, false if target is data) * @param {boolean} [fNoChecks] (true when setting breakpoints that may not be valid now, but will be later) * @param {boolean} [fPrint] * @return {DbgAddrPDP11|null|undefined} */ parseAddr(sAddr, fCode, fNoChecks, fPrint) { var dbgAddr; var dbgAddrNext = (fCode? this.dbgAddrNextCode : this.dbgAddrNextData); var addr = dbgAddrNext.addr; var fPhysical, nBase; if (sAddr !== undefined) { sAddr = this.parseReference(sAddr); var ch = sAddr.charAt(0); if (ch == '%') { fPhysical = true; sAddr = sAddr.substr(1); } dbgAddr = this.findSymbolAddr(sAddr); if (dbgAddr) return dbgAddr; if (sAddr.indexOf("0x") >= 0) { nBase = 16 } else if (sAddr.indexOf("0o") >= 0) { nBase = 8; } else if (sAddr.indexOf('.') >= 0) { nBase = 10; } addr = this.parseExpression(sAddr, fPrint); } if (addr != null) { dbgAddr = this.newAddr(addr, fPhysical, nBase); } return dbgAddr; } /** * parseAddrOptions(dbdAddr, sOptions) * * @this {DebuggerPDP11} * @param {DbgAddrPDP11} dbgAddr * @param {string} [sOptions] */ parseAddrOptions(dbgAddr, sOptions) { if (sOptions) { var a = sOptions.match(/(['"])(.*?)\1/); if (a) { dbgAddr.aCmds = this.parseCommand(dbgAddr.sCmd = a[2]); } } } /** * incAddr(dbgAddr, inc) * * @this {DebuggerPDP11} * @param {DbgAddrPDP11} dbgAddr * @param {number} [inc] contains value to increment dbgAddr by (default is 1) */ incAddr(dbgAddr, inc) { if (dbgAddr.addr != null) { dbgAddr.addr += (inc || 1); } } /** * toStrOffset(off) * * @this {DebuggerPDP11} * @param {number|null|undefined} [off] * @return {string} the hex representation of off */ toStrOffset(off) { return this.toStrBase(off); } /** * toStrAddr(dbgAddr) * * @this {DebuggerPDP11} * @param {DbgAddrPDP11} dbgAddr * @return {string} the hex representation of the address */ toStrAddr(dbgAddr) { return (dbgAddr.fPhysical? '%' : '') + this.toStrOffset(dbgAddr.addr); } /** * getSZ(dbgAddr, cchMax) * * Gets zero-terminated (aka "ASCIIZ") string from dbgAddr. It also stops at the first '$', in case this is * a '$'-terminated string -- mainly because I'm lazy and didn't feel like writing a separate get() function. * Yes, a zero-terminated string containing a '$' will be prematurely terminated, and no, I don't care. * * @this {DebuggerPDP11} * @param {DbgAddrPDP11} dbgAddr * @param {number} [cchMax] (default is 256) * @return {string} (and dbgAddr advanced past the terminating zero) */ getSZ(dbgAddr, cchMax) { var s = ""; cchMax = cchMax || 256; while (s.length < cchMax) { var b = this.getByte(dbgAddr, 1); if (!b || b == 0x24 || b >= 127) break; s += (b >= 32? String.fromCharCode(b) : '.'); } return s; } /** * dumpBlocks(aBlocks, sAddr) * * @this {DebuggerPDP11} * @param {Array} aBlocks * @param {string} [sAddr] (optional block address) */ dumpBlocks(aBlocks, sAddr) { var addr = 0, i = 0, n = aBlocks.length; if (sAddr) { addr = this.getAddr(this.parseAddr(sAddr)); if (addr === PDP11.ADDR_INVALID) { this.println("invalid address: " + sAddr); return; } i = addr >>> this.bus.nBlockShift; n = 1; } this.println("blockid physical blockaddr used size type"); this.println("-------- --------- --------- ------ ------ ----"); var typePrev = -1, cPrev = 0; while (n--) { var block = aBlocks[i]; if (block.type == typePrev) { if (!cPrev++) this.println("..."); } else { typePrev = block.type; var sType = MemoryPDP11.TYPE_NAMES[typePrev]; if (block) { this.println(Str.toHex(block.id, 8) + " %" + Str.toHex(i << this.bus.nBlockShift, 8) + " %" + Str.toHex(block.addr, 8) + " " + Str.toHexWord(block.used) + " " + Str.toHexWord(block.size) + " " + sType); } if (typePrev != MemoryPDP11.TYPE.NONE) typePrev = -1; cPrev = 0; } addr += this.bus.nBlockSize; i++; } } /** * dumpBus(asArgs) * * Dumps Bus allocations. * * @this {DebuggerPDP11} * @param {Array.} asArgs (asArgs[0] is an optional block address) */ dumpBus(asArgs) { this.dumpBlocks(this.bus.aBusBlocks, asArgs[0]); } /** * dumpHistory(sPrev, sLines) * * If sLines is not a number, it can be a instruction filter. However, for the moment, the only * supported filter is "call", which filters the history buffer for all CALL and RET instructions * from the specified previous point forward. * * @this {DebuggerPDP11} * @param {string} [sPrev] is a (decimal) number of instructions to rewind to (default is 10) * @param {string} [sLines] is a (decimal) number of instructions to print (default is, again, 10) */ dumpHistory(sPrev, sLines) { var sMore = ""; var cHistory = 0; var iHistory = this.iInstructionHistory; var aHistory = this.aInstructionHistory; if (aHistory.length) { var nPrev = +sPrev || this.nextHistory; var nLines = +sLines || 10; if (isNaN(nPrev)) { nPrev = nLines; } else { sMore = "more "; } if (nPrev > aHistory.length) { this.println("note: only " + aHistory.length + " available"); nPrev = aHistory.length; } iHistory -= nPrev; if (iHistory < 0) { /* * If the dbgAddr of the last aHistory element contains a valid selector, wrap around. */ if (aHistory[aHistory.length - 1].addr == null) { nPrev = iHistory + nPrev; iHistory = 0; } else { iHistory += aHistory.length; } } var aFilters = []; if (sLines == "call") { nLines = 100000; aFilters = ["CALL"]; } if (sPrev !== undefined) { this.println(nPrev + " instructions earlier:"); } /* * TODO: The following is necessary to prevent dumpHistory() from causing additional (or worse, recursive) * faults due to segmented addresses that are no longer valid, but the only alternative is to dramatically * increase the amount of memory used to store instruction history (eg, storing copies of all the instruction * bytes alongside the execution addresses). * * For now, we're living dangerously, so that our history dumps actually work. * * this.nSuppressBreaks++; * * If you re-enable this protection, be sure to re-enable the decrement below, too. */ while (nLines > 0 && iHistory != this.iInstructionHistory) { var dbgAddr = aHistory[iHistory++]; if (dbgAddr.addr == null) break; /* * We must create a new dbgAddr from the address in aHistory, because dbgAddr was * a reference, not a copy, and we don't want getInstruction() modifying the original. */ var dbgAddrNew = this.newAddr(dbgAddr.addr); var sComment = "history"; var nSequence = nPrev--; /* * TODO: Need to some UI to control whether cycle counts are displayed as part of the history. * It's currently disabled in checkInstruction(), so it's disable here, too. * if (DEBUG && dbgAddr.cycleCount != null) { sComment = "cycles"; nSequence = dbgAddr.cycleCount; } */ var sInstruction = this.getInstruction(dbgAddrNew, sComment, nSequence); if (!aFilters.length || sInstruction.indexOf(aFilters[0]) >= 0) { this.println(sInstruction); } /* * If there were OPERAND or ADDRESS overrides on the previous instruction, getInstruction() * will have automatically disassembled additional bytes, so skip additional history entries. */ if (dbgAddrNew.cOverrides) { iHistory += dbgAddrNew.cOverrides; nLines -= dbgAddrNew.cOverrides; nPrev -= dbgAddrNew.cOverrides; } if (iHistory >= aHistory.length) iHistory = 0; this.nextHistory = nPrev; cHistory++; nLines--; } /* * See comments above. * * this.nSuppressBreaks--; */ } if (!cHistory) { this.println("no " + sMore + "history available"); this.nextHistory = undefined; } } /** * messageInit(sEnable) * * @this {DebuggerPDP11} * @param {string|undefined} sEnable contains zero or more message categories to enable, separated by '|' */ messageInit(sEnable) { this.dbg = this; this.bitsMessage = this.bitsWarning = MessagesPDP11.WARN; this.sMessagePrev = null; this.aMessageBuffer = []; /* * Internally, we use "key" instead of "keys", since the latter is a method on JavasScript objects, * but externally, we allow the user to specify "keys"; "kbd" is also allowed as shorthand for "keyboard". */ var aEnable = this.parseCommand(sEnable.replace("keys","key").replace("kbd","keyboard"), false, '|'); if (aEnable.length) { for (var m in MessagesPDP11.CATEGORIES) { if (Usr.indexOf(aEnable, m) >= 0) { this.bitsMessage |= MessagesPDP11.CATEGORIES[m]; this.println(m + " messages enabled"); } } } } /** * messageDump(bitMessage, fnDumper) * * @this {DebuggerPDP11} * @param {number} bitMessage is one Messages category flag * @param {function(Array.)} fnDumper is a function the Debugger can use to dump data for that category * @return {boolean} true if successfully registered, false if not */ messageDump(bitMessage, fnDumper) { for (var m in MessagesPDP11.CATEGORIES) { if (bitMessage == MessagesPDP11.CATEGORIES[m]) { this.afnDumpers[m] = fnDumper; return true; } } return false; } /** * getRegIndex(sReg, off) * * @this {DebuggerPDP11} * @param {string} sReg * @param {number} [off] optional offset into sReg * @return {number} register index, or -1 if not found */ getRegIndex(sReg, off) { sReg = sReg.toUpperCase(); var iReg = DebuggerPDP11.REGS[sReg]; if (iReg == null) { iReg = -1; if (sReg.charAt(0) == "R") { iReg = +sReg.charAt(1); if (iReg < 0 || iReg > 7) iReg = -1; } } return iReg; } /** * getRegName(iReg) * * @this {DebuggerPDP11} * @param {number} iReg (0-7; not used for other registers) * @return {string} */ getRegName(iReg) { return (iReg < 6? ("R" + iReg) : (iReg == 6? "SP" : "PC")); } /** * getRegValue(iReg) * * Register numbers 0-7 are reserved for cpu.regsGen, 8-15 are reserved for cpu.regsAlt, * 16-19 for cpu.regsAltStack, 20 for regPSW, etc. * * @this {DebuggerPDP11} * @param {number} iReg * @return {number|undefined} */ getRegValue(iReg) { var value; if (iReg >= 0) { if (iReg < 8) { value = this.cpu.regsGen[iReg]; } else if (iReg < 16) { value = this.cpu.regsAlt[iReg-8]; } else if (iReg < 20) { value = this.cpu.regsAltStack[iReg-16]; } else { var cpu = this.cpu; var panel = this.panel; switch(iReg) { case DebuggerPDP11.REG_PS: value = this.cpu.getPSW(); break; case DebuggerPDP11.REG_PI: value = cpu.getPIR(); break; case DebuggerPDP11.REG_ER: value = cpu.regErr; break; case DebuggerPDP11.REG_SL: value = cpu.getSLR(); break; case DebuggerPDP11.REG_M0: value = cpu.getMMR0(); break; case DebuggerPDP11.REG_M1: value = cpu.getMMR1(); break; case DebuggerPDP11.REG_M2: value = cpu.getMMR2(); break; case DebuggerPDP11.REG_M3: value = cpu.getMMR3(); break; case DebuggerPDP11.REG_AR: if (panel) value = panel.getAR(); break; case DebuggerPDP11.REG_DR: if (panel) value = panel.getDR(); break; case DebuggerPDP11.REG_SR: if (panel && panel.hasSwitches()) { value = panel.getSR(); } break; } } } return value; } /** * replaceRegs(s) * * TODO: Implement or eliminate. * * @this {DebuggerPDP11} * @param {string} s * @return {string} */ replaceRegs(s) { return s; } /** * message(sMessage, fAddress) * * @this {DebuggerPDP11} * @param {string} sMessage is any caller-defined message string * @param {boolean} [fAddress] is true to display the current address */ message(sMessage, fAddress) { if (fAddress) { sMessage += " @" + this.toStrAddr(this.newAddr(this.cpu.getLastPC())); } if (this.sMessagePrev && sMessage == this.sMessagePrev) return; this.sMessagePrev = sMessage; if (this.bitsMessage & MessagesPDP11.BUFFER) { this.aMessageBuffer.push(sMessage); return; } var fRunning; if ((this.bitsMessage & MessagesPDP11.HALT) && this.cpu && (fRunning = this.cpu.isRunning()) || this.isBusy(true)) { this.stopCPU(); if (fRunning) sMessage += " (cpu halted)"; } this.println(sMessage); // + " (" + this.cpu.getCycles() + " cycles)" /* * We have no idea what the frequency of println() calls might be; all we know is that they easily * screw up the CPU's careful assumptions about cycles per burst. So we call yieldCPU() after every * message, to effectively end the current burst and start fresh. * * TODO: See CPUPDP11.calcStartTime() for a discussion of why we might want to call yieldCPU() *before* * we display the message. */ if (this.cpu) this.cpu.yieldCPU(); } /** * init() * * @this {DebuggerPDP11} * @param {boolean} [fAutoStart] */ init(fAutoStart) { this.fInit = true; this.println("Type ? for help with PDPjs Debugger commands"); this.updateStatus(); if (!fAutoStart) this.setFocus(); if (this.sInitCommands) { var sCmds = this.sInitCommands; this.sInitCommands = null; this.doCommands(sCmds); } } /** * historyInit(fQuiet) * * This function is intended to be called by the constructor, reset(), addBreakpoint(), findBreakpoint() * and any other function that changes the checksEnabled() criteria used to decide whether checkInstruction() * should be called. * * That is, if the history arrays need to be allocated and haven't already been allocated, then allocate them, * and if the arrays are no longer needed, then deallocate them. * * @this {DebuggerPDP11} * @param {boolean} [fQuiet] */ historyInit(fQuiet) { var i; if (!this.checksEnabled()) { if (this.aInstructionHistory && this.aInstructionHistory.length && !fQuiet) { this.println("instruction history buffer freed"); } this.iInstructionHistory = 0; this.aInstructionHistory = []; return; } if (!this.aInstructionHistory || !this.aInstructionHistory.length) { this.aInstructionHistory = new Array(DebuggerPDP11.HISTORY_LIMIT); for (i = 0; i < this.aInstructionHistory.length; i++) { /* * Preallocate dummy Addr (Array) objects in every history slot, so that * checkInstruction() doesn't need to call newAddr() on every slot update. */ this.aInstructionHistory[i] = this.newAddr(); } this.iInstructionHistory = 0; if (!fQuiet) { this.println("instruction history buffer allocated"); } } } /** * startCPU(fUpdateFocus, fQuiet) * * @this {DebuggerPDP11} * @param {boolean} [fUpdateFocus] is true to update focus * @param {boolean} [fQuiet] * @return {boolean} true if run request successful, false if not */ startCPU(fUpdateFocus, fQuiet) { if (!this.checkCPU(fQuiet)) return false; this.cpu.startCPU(fUpdateFocus); return true; } /** * stepCPU(nCycles, fRegs, fUpdateDisplays) * * @this {DebuggerPDP11} * @param {number} nCycles (0 for one instruction without checking breakpoints) * @param {boolean|null} [fRegs] is true to display registers after step (default is false; use null for previous setting) * @param {boolean} [fUpdateDisplays] is false to disable Computer display updates (default is true) * @return {boolean} */ stepCPU(nCycles, fRegs, fUpdateDisplays) { if (!this.checkCPU()) return false; var sCmd = ""; if (fRegs === null) { fRegs = (!this.sCmdTracePrev || this.sCmdTracePrev == "tr"); sCmd = fRegs? "tr" : "t"; } this.nCycles = 0; if (!nCycles) { /* * When single-stepping, the CPU won't call checkInstruction(), which is good for * avoiding breakpoints, but bad for instruction data collection if checks are enabled. * So we call checkInstruction() ourselves. */ if (this.checksEnabled()) this.checkInstruction(this.cpu.getPC(), 0); } /* * For our typically tiny bursts (usually single instructions), mimic what runCPU() does. */ try { nCycles = this.cpu.getBurstCycles(nCycles); var nCyclesStep = this.cpu.stepCPU(nCycles); if (nCyclesStep > 0) { this.cpu.updateTimers(nCyclesStep); this.nCycles += nCyclesStep; this.cpu.addCycles(nCyclesStep, true); this.cpu.updateChecksum(nCyclesStep); this.cInstructions++; } } catch(exception) { /* * We assume that any numeric exception was explicitly thrown by the CPU to interrupt the * current instruction. For all other exceptions, we attempt a stack dump. */ if (typeof exception != "number") { var e = exception; this.nCycles = 0; this.cpu.setError(e.stack || e.message); } } /* * Because we called cpu.stepCPU() and not cpu.startCPU(), we must nudge the Computer's update code, * and then update our own state. Normally, the only time fUpdateDisplays will be false is when doTrace() * is calling us in a loop, in which case it will perform its own updateDisplays() when it's done. */ if (fUpdateDisplays !== false) { if (this.panel) this.panel.stop(); this.cmp.updateDisplays(-1); } this.updateStatus(fRegs || false, sCmd); return (this.nCycles > 0); } /** * stopCPU() * * @this {DebuggerPDP11} * @param {boolean} [fComplete] */ stopCPU(fComplete) { if (this.cpu) this.cpu.stopCPU(fComplete); } /** * updateStatus(fRegs, sCmd) * * @this {DebuggerPDP11} * @param {boolean} [fRegs] (default is true) * @param {string} [sCmd] */ updateStatus(fRegs, sCmd) { if (!this.fInit) return; if (fRegs === undefined) fRegs = true; if (sCmd) { this.println(DebuggerPDP11.PROMPT + sCmd); } var trapStatus = this.cpu.getTrapStatus(); if (trapStatus) { var reason = trapStatus >> 8; var sReason = reason < 0? PDP11.REASONS[-reason] : this.toStrBase(reason); this.println("trapped to " + this.toStrBase(trapStatus & 0xff, 1) + " (" + sReason + ")"); } this.dbgAddrNextCode = this.newAddr(this.cpu.getPC()); /* * this.nStep used to be a simple boolean, but now it's 0 (or undefined) * if inactive, 1 if stepping over an instruction without a register dump, or 2 * if stepping over an instruction with a register dump. */ if (!fRegs || this.nStep == 1) { this.doUnassemble(); } else { this.doRegisters(); } } /** * checkCPU(fQuiet) * * Make sure the CPU is ready (finished initializing), powered, not already running, and not in an error state. * * @this {DebuggerPDP11} * @param {boolean} [fQuiet] * @return {boolean} */ checkCPU(fQuiet) { if (!this.cpu || !this.cpu.isReady() || !this.cpu.isPowered() || this.cpu.isRunning()) { if (!fQuiet) this.println("cpu busy or unavailable, command ignored"); return false; } return !this.cpu.isError(); } /** * powerUp(data, fRepower) * * @this {DebuggerPDP11} * @param {Object|null} data * @param {boolean} [fRepower] * @return {boolean} true if successful, false if failure */ powerUp(data, fRepower) { if (!fRepower) { /* * Because Debugger save/restore support is somewhat limited (and didn't always exist), * we deviate from the typical save/restore design pattern: instead of reset OR restore, * we always reset and then perform a (potentially limited) restore. */ this.reset(true); // this.println(data? "resuming" : "powering up"); if (data) { return this.restore(data); } } return true; } /** * powerDown(fSave, fShutdown) * * @this {DebuggerPDP11} * @param {boolean} [fSave] * @param {boolean} [fShutdown] * @return {Object|boolean} */ powerDown(fSave, fShutdown) { if (fShutdown) this.println(fSave? "suspending" : "shutting down"); return fSave? this.save() : true; } /** * reset(fQuiet) * * This is a notification handler, called by the Computer, to inform us of a reset. * * @this {DebuggerPDP11} * @param {boolean} fQuiet (true only when called from our own powerUp handler) */ reset(fQuiet) { this.historyInit(); this.cInstructions = this.cInstructionsStart = 0; this.sMessagePrev = null; this.nCycles = 0; this.dbgAddrNextCode = this.newAddr(this.cpu.getPC()); /* * fRunning is set by start() and cleared by stop(). In addition, we clear * it here, so that if the CPU is reset while running, we can prevent stop() * from unnecessarily dumping the CPU state. */ this.flags.running = false; this.clearTempBreakpoint(); if (!fQuiet) this.updateStatus(); } /** * save() * * This implements (very rudimentary) save support for the Debugger component. * * @this {DebuggerPDP11} * @return {Object} */ save() { var state = new State(this); state.set(0, this.packAddr(this.dbgAddrNextCode)); state.set(1, this.packAddr(this.dbgAddrAssemble)); state.set(2, [this.aPrevCmds, this.fAssemble, this.bitsMessage]); state.set(3, this.aSymbolTable); return state.data(); } /** * restore(data) * * This implements (very rudimentary) restore support for the Debugger component. * * @this {DebuggerPDP11} * @param {Object} data * @return {boolean} true if successful, false if failure */ restore(data) { var i = 0; if (data[2] !== undefined) { this.dbgAddrNextCode = this.unpackAddr(data[i++]); this.dbgAddrAssemble = this.unpackAddr(data[i++]); this.aPrevCmds = data[i][0]; if (typeof this.aPrevCmds == "string") this.aPrevCmds = [this.aPrevCmds]; this.fAssemble = data[i][1]; this.bitsMessage |= data[i][2]; // keep our current message bits set, and simply "add" any extra bits defined by the saved state } if (data[3]) this.aSymbolTable = data[3]; return true; } /** * start(ms, nCycles) * * This is a notification handler, called by the Computer, to inform us the CPU has started. * * @this {DebuggerPDP11} * @param {number} ms * @param {number} nCycles */ start(ms, nCycles) { if (!this.nStep) this.println("running"); this.flags.running = true; this.msStart = ms; this.nCyclesStart = nCycles; } /** * stop(ms, nCycles) * * This is a notification handler, called by the Computer, to inform us the CPU has now stopped. * * @this {DebuggerPDP11} * @param {number} ms * @param {number} nCycles */ stop(ms, nCycles) { if (this.flags.running) { this.flags.running = false; this.nCycles = nCycles - this.nCyclesStart; if (!this.nStep) { var sStopped = "stopped"; if (this.nCycles) { var msTotal = ms - this.msStart; var nCyclesPerSecond = (msTotal > 0? Math.round(this.nCycles * 1000 / msTotal) : 0); sStopped += " ("; if (this.checksEnabled()) { sStopped += this.cInstructions + " instructions, "; /* * $ops displays progress by calculating cInstructions - cInstructionsStart, so before * zeroing cInstructions, we should subtract cInstructions from cInstructionsStart (since * we're effectively subtracting cInstructions from cInstructions as well). */ this.cInstructionsStart -= this.cInstructions; this.cInstructions = 0; } sStopped += this.nCycles + " cycles, " + msTotal + " ms, " + nCyclesPerSecond + " hz)"; } else { if (this.messageEnabled(MessagesPDP11.HALT)) { /* * It's possible the user is trying to 'g' past a fault that was blocked by helpCheckFault() * for the Debugger's benefit; if so, it will continue to be blocked, so try displaying a helpful * message (another helpful tip would be to simply turn off the "halt" message category). */ sStopped += " (use the 't' command to execute blocked faults)"; } } this.println(sStopped); } this.updateStatus(true); this.setFocus(); this.clearTempBreakpoint(this.cpu.getPC()); this.sMessagePrev = null; } } /** * checksEnabled(fRelease) * * This "check" function is called by the CPU; we indicate whether or not every instruction needs to be checked. * * Originally, this returned true even when there were only read and/or write breakpoints, but those breakpoints * no longer require the intervention of checkInstruction(); the Bus component automatically swaps in/out appropriate * "checked" Memory access functions to deal with those breakpoints in the corresponding Memory blocks. So I've * simplified the test below. * * @this {DebuggerPDP11} * @param {boolean} [fRelease] is true for release criteria only; default is false (any criteria) * @return {boolean} true if every instruction needs to pass through checkInstruction(), false if not */ checksEnabled(fRelease) { return ((DEBUG && !fRelease)? true : (this.aBreakExec.length > 1 || !!this.nBreakInstructions)); } /** * checkInstruction(addr, nState) * * This "check" function is called by the CPU to inform us about the next instruction to be executed, * giving us an opportunity to look for "exec" breakpoints and update opcode instruction history. * * @this {DebuggerPDP11} * @param {number} addr * @param {number} nState is < 0 if stepping, 0 if starting, or > 0 if running * @return {boolean} true if breakpoint hit, false if not */ checkInstruction(addr, nState) { var opCode = -1; var cpu = this.cpu; /* * If opHalt() calls our stopInstruction() function, it will effectively rewind the PC back to the HALT, * purely for our debugging benefit, so we must compensate for that here by advancing the PC past the HALT * when the machine starts up again. */ if (!nState) { opCode = this.cpu.getWordSafe(addr); /* * We have to be careful about this HALT-skipping code, because as fate would have it, I inadvertently * stopped the following diagnostic with a breakpoint *on* a HALT instruction: * * .R EKBEE1 * EKBEE1.BIC * * CEKBEE0 11/70 MEM MGMT * * CPU UNDER TEST FOUND TO BE A KB11-CM * bp 033330 hit * stopped (28339757 instructions, 123994176 cycles, 19177 ms, 6465775 hz) * R0=140000 R1=033330 R2=100143 R3=133260 R4=000000 R5=177700 * SP=000600 PC=033330 PS=140000 IR=000000 SL=000377 T0 N0 Z0 V0 C0 * 033330: 000000 HALT * * Since we haven't executed the HALT yet, it would be wrong (and would cause a diagnostic failure) to * skip over it. In this particular case, the PDR for the address of the HALT instruction was invalid, * so the HALT gets fetched but not executed. * * My first thought was that maybe we need to probe the address more thoroughly (getWordSafe() does * not), but it should be sufficient to simply confirm that the PC of the last opcode executed matches * the addr of this HALT. * * Yes, I could save myself this grief by eliminating these PC hacks, both here and in stopInstruction(), * but I still think it's a useful debugging aid. */ if (opCode == PDP11.OPCODE.HALT && this.cpu.getLastPC() == addr) { addr = this.cpu.advancePC(2); } } /* * If the CPU stopped on a breakpoint, we're not interested in stopping again if the machine is starting. */ if (nState > 0) { if (this.nBreakInstructions) { if (!--this.nBreakInstructions) return true; } if (this.checkBreakpoint(addr, 1, this.aBreakExec)) { return true; } } /* * The rest of the instruction tracking logic can only be performed if historyInit() has allocated the * necessary data structures. Note that there is no explicit UI for enabling/disabling history, other than * adding/removing breakpoints, simply because it's breakpoints that trigger the call to checkInstruction(); * well, OK, and a few other things now, like enabling MessagesPDP11.INT messages. */ if (nState >= 0 && this.aInstructionHistory.length) { this.cInstructions++; if (opCode < 0) { opCode = this.cpu.getWordSafe(addr); } if ((opCode & 0xffff) != PDP11.OPCODE.INVALID) { var dbgAddr = this.aInstructionHistory[this.iInstructionHistory]; this.setAddr(dbgAddr, addr); // if (DEBUG) dbgAddr.cycleCount = cpu.getCycles(); if (++this.iInstructionHistory == this.aInstructionHistory.length) this.iInstructionHistory = 0; } } return false; } /** * stopInstruction(sMessage) * * TODO: Currently, the only way to prevent this call from stopping the CPU is when you're single-stepping. * * @this {DebuggerPDP11} * @param {string} [sMessage] * @return {boolean} true if stopping is enabled, false if not */ stopInstruction(sMessage) { var cpu = this.cpu; if (cpu.isRunning()) { cpu.setPC(this.cpu.getLastPC()); if (sMessage) this.println(sMessage); this.stopCPU(); /* * TODO: Review the appropriate-ness of throwing a bogus vector number in order to immediately stop * the instruction. It's handy, but it also means that we no longer actually return true, so callers * of either stopInstruction() or undefinedInstruction() may have unreachable code paths. */ throw -1; } return false; } /** * undefinedInstruction(opCode) * * @this {DebuggerPDP11} * @param {number} opCode * @return {boolean} true if stopping is enabled, false if not */ undefinedInstruction(opCode) { if (this.messageEnabled(MessagesPDP11.CPU)) { this.printMessage("undefined opcode " + this.toStrBase(opCode), true, true); return this.stopInstruction(); // allow the caller to step over it if they really want a trap generated } return false; } /** * checkMemoryRead(addr, nb) * * This "check" function is called by a Memory block to inform us that a memory read occurred, giving us an * opportunity to track the read if we want, and look for a matching "read" breakpoint, if any. * * In the "old days", it would be an error for this call to fail to find a matching Debugger breakpoint, but now * Memory blocks have no idea whether the Debugger or the machine's Debug register(s) triggered this "checked" read. * * If we return true, we "trump" the machine's Debug register(s); false allows normal Debug register processing. * * @this {DebuggerPDP11} * @param {number} addr * @param {number} [nb] (# of bytes; default is 1) * @return {boolean} true if breakpoint hit, false if not */ checkMemoryRead(addr, nb) { if (this.checkBreakpoint(addr, nb || 1, this.aBreakRead)) { this.stopCPU(false); return true; } return false; } /** * checkMemoryWrite(addr, nb) * * This "check" function is called by a Memory block to inform us that a memory write occurred, giving us an * opportunity to track the write if we want, and look for a matching "write" breakpoint, if any. * * In the "old days", it would be an error for this call to fail to find a matching Debugger breakpoint, but now * Memory blocks have no idea whether the Debugger or the machine's Debug register(s) triggered this "checked" write. * * If we return true, we "trump" the machine's Debug register(s); false allows normal Debug register processing. * * @this {DebuggerPDP11} * @param {number} addr * @param {number} [nb] (# of bytes; default is 1) * @return {boolean} true if breakpoint hit, false if not */ checkMemoryWrite(addr, nb) { if (this.checkBreakpoint(addr, nb || 1, this.aBreakWrite)) { this.stopCPU(false); return true; } return false; } /** * clearBreakpoints() * * @this {DebuggerPDP11} */ clearBreakpoints() { var i, dbgAddr, addr; this.aBreakExec = ["bp"]; if (this.aBreakRead !== undefined) { for (i = 1; i < this.aBreakRead.length; i++) { dbgAddr = this.aBreakRead[i]; addr = this.getAddr(dbgAddr); if (!dbgAddr.fPhysical) { this.cpu.removeMemBreak(addr, false); } else { this.bus.removeMemBreak(addr, false); } } } this.aBreakRead = ["br"]; if (this.aBreakWrite !== undefined) { for (i = 1; i < this.aBreakWrite.length; i++) { dbgAddr = this.aBreakWrite[i]; addr = this.getAddr(dbgAddr); if (!dbgAddr.fPhysical) { this.cpu.removeMemBreak(addr, true); } else { this.bus.removeMemBreak(addr, true); } } } this.aBreakWrite = ["bw"]; /* * nSuppressBreaks ensures we can't get into an infinite loop where a breakpoint lookup * requires reading memory that triggers more memory reads, which triggers more breakpoint checks. */ this.nSuppressBreaks = 0; this.nBreakInstructions = 0; } /** * addBreakpoint(aBreak, dbgAddr, fTemporary) * * In case you haven't already figured this out, all our breakpoint commands use the address * to identify a breakpoint, not an incrementally assigned breakpoint index like other debuggers; * see doBreak() for details. * * This has a few implications, one being that you CANNOT set more than one kind of breakpoint * on a single address. In practice, that's rarely a problem, because you can almost always set * a different breakpoint on a neighboring address. * * Also, there is one exception to the "one address, one breakpoint" rule, and that involves * temporary breakpoints (ie, one-time execution breakpoints that either a "p" or "g" command * may create to step over a chunk of code). Those breakpoints automatically clear themselves, * so there usually isn't any need to refer to them using breakpoint commands. * * TODO: Consider supporting the more "traditional" breakpoint index syntax; the current * address-based syntax was implemented solely for expediency and consistency. At the same time, * also consider a more WDEB386-like syntax, where "br" is used to set a variety of access-specific * breakpoints, using modifiers like "r1", "r2", "w1", "w2, etc. * * @this {DebuggerPDP11} * @param {Array} aBreak * @param {DbgAddrPDP11} dbgAddr * @param {boolean} [fTemporary] * @return {boolean} true if breakpoint added, false if already exists */ addBreakpoint(aBreak, dbgAddr, fTemporary) { var fSuccess = true; // this.nSuppressBreaks++; /* * Instead of complaining that a breakpoint already exists (as we used to do), we now * allow breakpoints to be re-set; this makes it easier to update any commands that may * be associated with the breakpoint. * * The only exception: we DO allow a temporary breakpoint at an address where there may * already be a breakpoint, so that you can easily step ("p" or "g") over such addresses. */ if (!fTemporary) { this.findBreakpoint(aBreak, dbgAddr, true, false, true); } if (aBreak != this.aBreakExec) { var addr = this.getAddr(dbgAddr); if (addr === PDP11.ADDR_INVALID) { this.println("invalid address: " + this.toStrAddr(dbgAddr)); fSuccess = false; } else { var fWrite = (aBreak == this.aBreakWrite); /* * We automatically promote any read/write breakpoint address to fPhysical if it's * outside the 16-bit virtual address range. */ if (addr > 0xffff) dbgAddr.fPhysical = true; if (!dbgAddr.fPhysical) { this.cpu.addMemBreak(addr, fWrite); } else { this.bus.addMemBreak(addr, fWrite); } } } if (fSuccess) { aBreak.push(dbgAddr); if (fTemporary) { dbgAddr.fTemporary = true; } else { this.printBreakpoint(aBreak, aBreak.length-1, "set"); this.historyInit(); } } // this.nSuppressBreaks--; return fSuccess; } /** * findBreakpoint(aBreak, dbgAddr, fRemove, fTemporary, fQuiet) * * @this {DebuggerPDP11} * @param {Array} aBreak * @param {DbgAddrPDP11} dbgAddr * @param {boolean} [fRemove] * @param {boolean} [fTemporary] * @param {boolean} [fQuiet] * @return {boolean} true if found, false if not */ findBreakpoint(aBreak, dbgAddr, fRemove, fTemporary, fQuiet) { var fFound = false; var addr = this.getAddr(dbgAddr); for (var i = 1; i < aBreak.length; i++) { var dbgAddrBreak = aBreak[i]; if (addr == this.getAddr(dbgAddrBreak)) { if (!fTemporary || dbgAddrBreak.fTemporary) { fFound = true; if (fRemove) { if (!dbgAddrBreak.fTemporary && !fQuiet) { this.printBreakpoint(aBreak, i, "cleared"); } aBreak.splice(i, 1); if (aBreak != this.aBreakExec) { var fWrite = (aBreak == this.aBreakWrite); if (!dbgAddrBreak.fPhysical) { this.cpu.removeMemBreak(addr, fWrite); } else { this.bus.removeMemBreak(addr, fWrite); } } /* * We'll mirror the logic in addBreakpoint() and leave the history buffer alone if this * was a temporary breakpoint. */ if (!dbgAddrBreak.fTemporary) { this.historyInit(); } break; } if (!fQuiet) this.printBreakpoint(aBreak, i, "exists"); break; } } } return fFound; } /** * listBreakpoints(aBreak) * * @this {DebuggerPDP11} * @param {Array} aBreak * @return {number} of breakpoints listed, 0 if none */ listBreakpoints(aBreak) { for (var i = 1; i < aBreak.length; i++) { this.printBreakpoint(aBreak, i); } return aBreak.length - 1; } /** * printBreakpoint(aBreak, i, sAction) * * @this {DebuggerPDP11} * @param {Array} aBreak * @param {number} i * @param {string} [sAction] */ printBreakpoint(aBreak, i, sAction) { var dbgAddr = aBreak[i]; this.println(aBreak[0] + ' ' + this.toStrAddr(dbgAddr) + (sAction? (' ' + sAction) : (dbgAddr.sCmd? (' "' + dbgAddr.sCmd + '"') : ''))); } /** * setTempBreakpoint(dbgAddr) * * @this {DebuggerPDP11} * @param {DbgAddrPDP11} dbgAddr of new temp breakpoint */ setTempBreakpoint(dbgAddr) { this.addBreakpoint(this.aBreakExec, dbgAddr, true); } /** * clearTempBreakpoint(addr) * * @this {DebuggerPDP11} * @param {number|undefined} [addr] clear all temp breakpoints if no address specified */ clearTempBreakpoint(addr) { if (addr !== undefined) { this.checkBreakpoint(addr, 1, this.aBreakExec, true); this.nStep = 0; } else { for (var i = 1; i < this.aBreakExec.length; i++) { var dbgAddrBreak = this.aBreakExec[i]; if (dbgAddrBreak.fTemporary) { if (!this.findBreakpoint(this.aBreakExec, dbgAddrBreak, true, true)) break; i = 0; } } } } /** * checkBreakpoint(addr, nb, aBreak, fTemporary) * * @this {DebuggerPDP11} * @param {number} addr * @param {number} nb (# of bytes) * @param {Array} aBreak * @param {boolean} [fTemporary] * @return {boolean} true if breakpoint has been hit, false if not */ checkBreakpoint(addr, nb, aBreak, fTemporary) { /* * Time to check for breakpoints; note that this should be done BEFORE updating history data * (see checkInstruction), since we might not actually execute the current instruction. */ var fBreak = false; if (!this.nSuppressBreaks++) { for (var i = 1; !fBreak && i < aBreak.length; i++) { var dbgAddrBreak = aBreak[i]; if (fTemporary && !dbgAddrBreak.fTemporary) continue; /* * If we're checking an execution address, which is always virtual, and virtual * addresses are always restricted to 16 bits, let's mask the breakpoint address to match * (the user should know better, but we'll be nice). */ var addrBreak = this.getAddr(dbgAddrBreak) & (aBreak == this.aBreakExec? 0xffff : -1); for (var n = 0; n < nb; n++) { if ((addr + n) != addrBreak) continue; var a; fBreak = true; if (dbgAddrBreak.fTemporary) { this.findBreakpoint(aBreak, dbgAddrBreak, true, true); fTemporary = true; } if (a = dbgAddrBreak.aCmds) { /* * When one or more commands are attached to a breakpoint, we don't halt by default. * Instead, we set fBreak to true only if, at the completion of all the commands, the * CPU is halted; in other words, you should include "h" as one of the breakpoint commands * if you want the breakpoint to stop execution. * * Another useful command is "if", which will return false if the expression is false, * at which point we'll jump ahead to the next "else" command, and if there isn't an "else", * we abort. */ fBreak = false; for (var j = 0; j < a.length; j++) { if (!this.doCommand(a[j], true)) { if (a[j].indexOf("if")) { fBreak = true; // the failed command wasn't "if", so abort break; } var k = j + 1; for (; k < a.length; k++) { if (!a[k].indexOf("else")) break; j++; } if (k == a.length) { // couldn't find an "else" after the "if", so abort fBreak = true; break; } /* * If we're still here, we'll execute the "else" command (which is just a no-op), * followed by any remaining commands. */ } } if (!this.cpu.isRunning()) fBreak = true; } if (fBreak) { if (!fTemporary) this.printBreakpoint(aBreak, i, "hit"); break; } } } } this.nSuppressBreaks--; return fBreak; } /** * getInstruction(dbgAddr, sComment, nSequence) * * Get the next instruction, by decoding the opcode and any operands. * * @this {DebuggerPDP11} * @param {DbgAddrPDP11} dbgAddr * @param {string} [sComment] is an associated comment * @param {number|null} [nSequence] is an associated sequence number, undefined if none * @return {string} (and dbgAddr is updated to the next instruction) */ getInstruction(dbgAddr, sComment, nSequence) { var opNames = DebuggerPDP11.OPNAMES; var dbgAddrOp = this.newAddr(dbgAddr.addr); var opCode = this.getWord(dbgAddr, 2); var opDesc; for (var mask in this.opTable) { var opMasks = this.opTable[mask]; opDesc = opMasks[opCode & mask]; if (opDesc) break; } if (!opDesc) { opDesc = DebuggerPDP11.OPNONE; } var opNum = opDesc[0]; if (this.aOpReserved.indexOf(opNum) >= 0) { opDesc = DebuggerPDP11.OPNONE; opNum = opDesc[0]; } var sOperands = "", sTarget = ""; var sOpName = opNames[opNum]; var cOperands = opDesc.length - 1; if (!opNum && !cOperands) { sOperands = this.toStrBase(opCode); } for (var iOperand = 1; iOperand <= cOperands; iOperand++) { var opType = opDesc[iOperand]; if (opType === undefined) continue; var sOperand = this.getOperand(opCode, opType, dbgAddr); if (!sOperand || !sOperand.length) { sOperands = "INVALID"; break; } /* * If getOperand() returns an Array rather than a string, then the first element is the original * operand, and the second element contains additional information (eg, the target) of the operand. */ if (typeof sOperand != "string") { sTarget = sOperand[1]; sOperand = sOperand[0]; } if (sOperands.length > 0) sOperands += ','; sOperands += (sOperand || "???"); } var sOpCodes = ""; var sLine = this.toStrAddr(dbgAddrOp) + ":"; if (dbgAddrOp.addr !== PDP11.ADDR_INVALID && dbgAddr.addr !== PDP11.ADDR_INVALID) { do { sOpCodes += ' ' + this.toStrBase(this.getWord(dbgAddrOp, 2)); if (dbgAddrOp.addr == null) break; } while (dbgAddrOp.addr != dbgAddr.addr); } sLine += Str.pad(sOpCodes, 24); sLine += Str.pad(sOpName, 5); if (sOperands) sLine += ' ' + sOperands; if (sComment || sTarget) { sLine = Str.pad(sLine, 60) + ';' + (sComment || ""); if (!this.cpu.flags.checksum) { sLine += (nSequence != null? '=' + nSequence.toString() : ""); } else { var nCycles = this.cpu.getCycles(); sLine += "cycles=" + nCycles.toString() + " cs=" + Str.toHex(this.cpu.nChecksum); } if (sTarget) { if (sLine.slice(-1) != ';') sLine += ' '; sLine += sTarget; } } return sLine; } /** * getOperand(opCode, opType, dbgAddr) * * If getOperand() returns an Array rather than a string, then the first element is the original * operand, and the second element is a comment containing additional information (eg, the target) * of the operand. * * @this {DebuggerPDP11} * @param {number} opCode * @param {number} opType * @param {DbgAddrPDP11} dbgAddr * @return {string|Array.} */ getOperand(opCode, opType, dbgAddr) { var sOperand = "", disp, addr; /* * Take care of OP_OTHER opcodes first; then all we'll have to worry about * next are OP_SRC or OP_DST opcodes. */ var opTypeOther = opType & DebuggerPDP11.OP_OTHER; if (opTypeOther == DebuggerPDP11.OP_BRANCH) { disp = ((opCode & 0xff) << 24) >> 23; addr = (dbgAddr.addr + disp) & 0xffff; sOperand = this.toStrBase(addr); } else if (opTypeOther == DebuggerPDP11.OP_DSTOFF) { disp = (opCode & 0x3f) << 1; addr = (dbgAddr.addr - disp) & 0xffff; sOperand = this.toStrBase(addr); } else if (opTypeOther == DebuggerPDP11.OP_DSTNUM3) { disp = (opCode & 0x07); sOperand = this.toStrBase(disp, 1); } else if (opTypeOther == DebuggerPDP11.OP_DSTNUM6) { disp = (opCode & 0x3f); sOperand = this.toStrBase(disp, 1); } else if (opTypeOther == DebuggerPDP11.OP_DSTNUM8) { disp = (opCode & 0xff); sOperand = this.toStrBase(disp, 1); } else { /* * Isolate all OP_SRC or OP_DST bits from opcode in the opMode variable. */ var opMode = opCode & opType; /* * Convert OP_SRC bits into OP_DST bits, since they use the same format. */ if (opType & DebuggerPDP11.OP_SRC) { opMode >>= 6; opType >>= 6; } if (opType & DebuggerPDP11.OP_DST) { var wIndex; var sTarget = null; var reg = opMode & DebuggerPDP11.OP_DSTREG; /* * Note that opcodes that specify only REG bits in the opType mask (ie, no MOD bits) * will automatically default to OPMODE_REG below. */ switch((opMode & DebuggerPDP11.OP_DSTMODE)) { case PDP11.OPMODE.REG: // 0x0: REGISTER sOperand = this.getRegName(reg); break; case PDP11.OPMODE.REGD: // 0x1: REGISTER DEFERRED sOperand = '@' + this.getRegName(reg); sTarget = this.getTarget(this.cpu.regsGen[reg]); break; case PDP11.OPMODE.POSTINC: // 0x2: POST-INCREMENT if (reg < 7) { sOperand = '(' + this.getRegName(reg) + ")+"; } else { /* * When using R7 (aka PC), POST-INCREMENT is known as IMMEDIATE */ wIndex = this.getWord(dbgAddr, 2); sOperand = '#' + this.toStrBase(wIndex, 0, true); } break; case PDP11.OPMODE.POSTINCD: // 0x3: POST-INCREMENT DEFERRED if (reg < 7) { sOperand = "@(" + this.getRegName(reg) + ")+"; } else { /* * When using R7 (aka PC), POST-INCREMENT DEFERRED is known as ABSOLUTE */ wIndex = this.getWord(dbgAddr, 2); sOperand = "@#" + this.toStrBase(wIndex, 0, true); sTarget = this.getTarget(wIndex); } break; case PDP11.OPMODE.PREDEC: // 0x4: PRE-DECREMENT sOperand = "-(" + this.getRegName(reg) + ")"; break; case PDP11.OPMODE.PREDECD: // 0x5: PRE-DECREMENT DEFERRED sOperand = "@-(" + this.getRegName(reg) + ")"; break; case PDP11.OPMODE.INDEX: // 0x6: INDEX wIndex = this.getWord(dbgAddr, 2); sOperand = this.toStrBase(wIndex, 0, true) + '(' + this.getRegName(reg) + ')'; if (reg == 7) { /* * When using R7 (aka PC), INDEX is known as RELATIVE. However, instead of displaying * such an instruction like this: * * 016156: 010167 001300 MOV R1,1300(PC) ; @017462 * * with the effective address display to the far right, let's display it like this instead: * * 016156: 010167 001300 MOV R1,017462 * * because you can still clearly see PC-relative offset (eg, 001300) as part of the disassembly. * * sOperand = [sOperand, this.toStrBase((wIndex + dbgAddr.addr) & 0xffff)]; */ sOperand = this.toStrBase(wIndex = (wIndex + dbgAddr.addr) & 0xffff); sTarget = this.getTarget(wIndex); } break; case PDP11.OPMODE.INDEXD: // 0x7: INDEX DEFERRED wIndex = this.getWord(dbgAddr, 2); sOperand = '@' + this.toStrBase(wIndex) + '(' + this.getRegName(reg) + ')'; if (reg == 7) { /* * When using R7 (aka PC), INDEX DEFERRED is known as RELATIVE DEFERRED. And for the same * reasons articulated above, we now display the effective address inline. * * sOperand = [sOperand, this.toStrBase((wIndex + dbgAddr.addr) & 0xffff)]; */ sOperand = '@' + this.toStrBase(wIndex = (wIndex + dbgAddr.addr) & 0xffff); sTarget = this.getTarget(this.cpu.getWordSafe(wIndex)); } break; default: this.assert(false); break; } if (sTarget) sOperand = [sOperand, sTarget]; } else { this.assert(false); } } return sOperand; } /** * getTarget(addr) * * @this {DebuggerPDP11} * @param {number} addr * @return {string|null} */ getTarget(addr) { var sTarget = null; var a = this.cpu.getAddrInfo(addr); var addrPhysical = a[0]; if (addrPhysical >= this.cpu.addrIOPage && addrPhysical < this.bus.addrIOPage) { addrPhysical = (addrPhysical - this.cpu.addrIOPage) + this.bus.addrIOPage; } return this.bus.getAddrInfo(addrPhysical); } /** * parseInstruction(sOp, sOperand, addr) * * TODO: Unimplemented. See parseInstruction() in modules/c1pjs/lib/debugger.js for a sample implementation. * * @this {DebuggerPDP11} * @param {string} sOp * @param {string|undefined} sOperand * @param {DbgAddrPDP11} dbgAddr of memory where this instruction is being assembled * @return {Array.} of opcode bytes; if the instruction can't be parsed, the array will be empty */ parseInstruction(sOp, sOperand, dbgAddr) { var aOpBytes = []; this.println("not supported yet"); return aOpBytes; } /** * getFlagOutput(sFlag) * * @this {DebuggerPDP11} * @param {string} sFlag * @return {string} value of flag */ getFlagOutput(sFlag) { var b; switch (sFlag) { case 'N': b = this.cpu.getNF(); break; case 'Z': b = this.cpu.getZF(); break; case 'V': b = this.cpu.getVF(); break; case 'C': b = this.cpu.getCF(); break; default: b = 0; break; } return sFlag.charAt(0) + (b? '1' : '0') + ' '; } /** * getRegOutput(iReg) * * @this {DebuggerPDP11} * @param {number} iReg * @return {string} */ getRegOutput(iReg) { var sReg = ""; var cpu = this.cpu; if (iReg < 8) { sReg = this.getRegName(iReg); sReg += '=' + this.toStrBase(cpu.regsGen[iReg]); } else if (iReg < 13) { sReg = "A" + (iReg - 8) + '=' + this.toStrBase(cpu.regsAlt[iReg - 8]); } else if (iReg >= 16 && iReg < 20) { sReg = "S" + (iReg - 16) + '=' + this.toStrBase(cpu.regsAltStack[iReg - 16]); } else { switch(iReg) { case DebuggerPDP11.REG_PS: sReg = "PS=" + this.toStrBase(cpu.getPSW()); break; case DebuggerPDP11.REG_PI: sReg = "PI=" + this.toStrBase(cpu.getPIR()); break; case DebuggerPDP11.REG_ER: sReg = "ER=" + this.toStrBase(cpu.regErr); break; case DebuggerPDP11.REG_SL: sReg = "SL=" + this.toStrBase(cpu.getSLR()); break; case DebuggerPDP11.REG_M0: sReg = "M0=" + this.toStrBase(cpu.getMMR0()); break; case DebuggerPDP11.REG_M1: sReg = "M1=" + this.toStrBase(cpu.getMMR1()); break; case DebuggerPDP11.REG_M2: sReg = "M2=" + this.toStrBase(cpu.getMMR2()); break; case DebuggerPDP11.REG_M3: sReg = "M3=" + this.toStrBase(cpu.getMMR3()); break; case DebuggerPDP11.REG_AR: if (this.panel) { sReg = "AR=" + this.toStrBase(this.panel.getAR(), 3); } break; case DebuggerPDP11.REG_DR: if (this.panel) { sReg = "DR=" + this.toStrBase(this.panel.getDR()); } break; case DebuggerPDP11.REG_SR: if (this.panel && this.panel.hasSwitches()) { sReg = "SR=" + this.toStrBase(this.panel.getSR(), 3); } break; } } if (sReg) sReg += ' '; return sReg; } /** * getMiscDump() * * Sample register dump: * * M0=xxxxxx M1=xxxxxx M2=xxxxxx M3=xxxxxx ER=xxxxxx * * @this {DebuggerPDP11} * @return {string} */ getMiscDump() { var sDump = ""; sDump += this.getRegOutput(DebuggerPDP11.REG_M0) + this.getRegOutput(DebuggerPDP11.REG_M1); sDump += this.getRegOutput(DebuggerPDP11.REG_M2) + this.getRegOutput(DebuggerPDP11.REG_M3) + this.getRegOutput(DebuggerPDP11.REG_ER); sDump += '\n'; sDump += this.getRegOutput(DebuggerPDP11.REG_SR) + this.getRegOutput(DebuggerPDP11.REG_AR) + this.getRegOutput(DebuggerPDP11.REG_DR); return sDump; } /** * getRegDump(fMisc) * * Sample register dump: * * R0=xxxxxx R1=xxxxxx R2=xxxxxx R3=xxxxxx R4=xxxxxx R5=xxxxxx * SP=xxxxxx PC=xxxxxx PS=xxxxxx PI=xxxxxx SL=xxxxxx T0 N0 Z0 V0 C0 * * @this {DebuggerPDP11} * @param {boolean} [fMisc] (true to include misc registers) * @return {string} */ getRegDump(fMisc) { var i; var sDump = ""; for (i = 0; i < PDP11.REG.SP; i++) { sDump += this.getRegOutput(i); } sDump += '\n'; sDump += this.getRegOutput(PDP11.REG.SP) + this.getRegOutput(PDP11.REG.PC); sDump += this.getRegOutput(DebuggerPDP11.REG_PS) + this.getRegOutput(DebuggerPDP11.REG_PI) + this.getRegOutput(DebuggerPDP11.REG_SL); sDump += this.getFlagOutput('T') + this.getFlagOutput('N') + this.getFlagOutput('Z') + this.getFlagOutput('V') + this.getFlagOutput('C'); if (fMisc) sDump += '\n' + this.getMiscDump(); return sDump; } /** * comparePairs(p1, p2) * * @this {DebuggerPDP11} * @param {number|string|Array|Object} p1 * @param {number|string|Array|Object} p2 * @return {number} */ comparePairs(p1, p2) { return p1[0] > p2[0]? 1 : p1[0] < p2[0]? -1 : 0; } /** * addSymbols(sModule, addr, len, aSymbols) * * As filedump.js (formerly convrom.php) explains, aSymbols is a JSON-encoded object whose properties consist * of all the symbols (in upper-case), and the values of those properties are objects containing any or all of * the following properties: * * 'v': the value of an absolute (unsized) value * 'b': either 1, 2, 4 or undefined if an unsized value * 's': either a hard-coded segment or undefined * 'o': the offset of the symbol within the associated address space * 'l': the original-case version of the symbol, present only if it wasn't originally upper-case * 'a': annotation for the specified offset; eg, the original assembly language, with optional comment * * To that list of properties, we also add: * * 'p': the physical address (calculated whenever both 's' and 'o' properties are defined) * * Note that values for any 'v', 'b', 's' and 'o' properties are unquoted decimal values, and the values * for any 'l' or 'a' properties are quoted strings. Also, if double-quotes were used in any of the original * annotation ('a') values, they will have been converted to two single-quotes, so we're responsible for * converting them back to individual double-quotes. * * For example: * { * 'HF_PORT': { * 'v':800 * }, * 'HDISK_INT': { * 'b':4, 's':0, 'o':52 * }, * 'ORG_VECTOR': { * 'b':4, 's':0, 'o':76 * }, * 'CMD_BLOCK': { * 'b':1, 's':64, 'o':66 * }, * 'DISK_SETUP': { * 'o':3 * }, * '.40': { * 'o':40, 'a':"MOV AX,WORD PTR ORG_VECTOR ;GET DISKETTE VECTOR" * } * } * * If a symbol only has an offset, then that offset value can be assigned to the symbol property directly: * * 'DISK_SETUP': 3 * * The last property is an example of an "anonymous" entry, for offsets where there is no associated symbol. * Such entries are identified by a period followed by a unique number (usually the offset of the entry), and * they usually only contain offset ('o') and annotation ('a') properties. I could eliminate the leading * period, but it offers a very convenient way of quickly discriminating among genuine vs. anonymous symbols. * * We add all these entries to our internal symbol table, which is an array of 4-element arrays, each of which * look like: * * [addr, len, aSymbols, aOffsets] * * There are two basic symbol operations: findSymbol(), which takes an address and finds the symbol, if any, * at that address, and findSymbolAddr(), which takes a string and attempts to match it to a non-anonymous * symbol with a matching offset ('o') property. * * To implement findSymbol() efficiently, addSymbols() creates an array of [offset, sSymbol] pairs * (aOffsets), one pair for each symbol that corresponds to an offset within the specified address space. * * We guarantee the elements of aOffsets are in offset order, because we build it using binaryInsert(); * it's quite likely that the MAP file already ordered all its symbols in offset order, but since they're * hand-edited files, we can't assume that, and we need to ensure that findSymbol()'s binarySearch() operates * properly. * * @this {DebuggerPDP11} * @param {string|null} sModule * @param {number|null} addr (physical address where the symbols are located, if the memory is physical; eg, ROM) * @param {number} len (the size of the region, in bytes) * @param {Object} aSymbols (collection of symbols in this group; the format of this collection is described below) */ addSymbols(sModule, addr, len, aSymbols) { var dbgAddr = {}; var aOffsets = []; for (var sSymbol in aSymbols) { var symbol = aSymbols[sSymbol]; if (typeof symbol == "number") { aSymbols[sSymbol] = symbol = {'o': symbol}; } var offSymbol = symbol['o']; var sAnnotation = symbol['a']; if (offSymbol !== undefined) { Usr.binaryInsert(aOffsets, [offSymbol >>> 0, sSymbol], this.comparePairs); } if (sAnnotation) symbol['a'] = sAnnotation.replace(/''/g, "\""); } var symbolTable = { sModule: sModule, addr: addr, len: len, aSymbols: aSymbols, aOffsets: aOffsets }; this.aSymbolTable.push(symbolTable); } /** * dumpSymbols() * * TODO: Add "numerical" and "alphabetical" dump options. This is simply dumping them in whatever * order they appeared in the original MAP file. * * @this {DebuggerPDP11} */ dumpSymbols() { for (var iTable = 0; iTable < this.aSymbolTable.length; iTable++) { var symbolTable = this.aSymbolTable[iTable]; for (var sSymbol in symbolTable.aSymbols) { if (sSymbol.charAt(0) == '.') continue; var symbol = symbolTable.aSymbols[sSymbol]; var offSymbol = symbol['o']; if (offSymbol === undefined) continue; var sSymbolOrig = symbolTable.aSymbols[sSymbol]['l']; if (sSymbolOrig) sSymbol = sSymbolOrig; this.println(this.toStrOffset(offSymbol) + ' ' + sSymbol); } } } /** * findSymbol(dbgAddr, fNearest) * * Search aSymbolTable for dbgAddr, and return an Array for the corresponding symbol (empty if not found). * * If fNearest is true, and no exact match was found, then the Array returned will contain TWO sets of * entries: [0]-[3] will refer to closest preceding symbol, and [4]-[7] will refer to the closest subsequent symbol. * * @this {DebuggerPDP11} * @param {DbgAddrPDP11} dbgAddr * @param {boolean} [fNearest] * @return {Array} where [0] == symbol name, [1] == symbol value, [2] == any annotation, and [3] == any associated comment */ findSymbol(dbgAddr, fNearest) { var aSymbol = []; var addrSymbol = this.getAddr(dbgAddr) >>> 0; for (var iTable = 0; iTable < this.aSymbolTable.length; iTable++) { var symbolTable = this.aSymbolTable[iTable]; var addr = symbolTable.addr >>> 0; var len = symbolTable.len; if (addrSymbol >= addr && addrSymbol < addr + len) { var offSymbol = addrSymbol - addr; var result = Usr.binarySearch(symbolTable.aOffsets, [offSymbol], this.comparePairs); if (result >= 0) { this.returnSymbol(iTable, result, aSymbol); } else if (fNearest) { result = ~result; this.returnSymbol(iTable, result-1, aSymbol); this.returnSymbol(iTable, result, aSymbol); } break; } } return aSymbol; } /** * findSymbolAddr(sSymbol) * * Search our symbol tables for sSymbol, and if found, return a dbgAddr (same as parseAddr()). * * @this {DebuggerPDP11} * @param {string} sSymbol * @return {DbgAddrPDP11|undefined} */ findSymbolAddr(sSymbol) { var dbgAddr; var offSymbol = this.bus.getAddrByName(sSymbol); if (offSymbol == null && sSymbol.match(/^[a-z_][a-z0-9_]*$/i)) { var sUpperCase = sSymbol.toUpperCase(); for (var iTable = 0; iTable < this.aSymbolTable.length; iTable++) { var symbolTable = this.aSymbolTable[iTable]; var symbol = symbolTable.aSymbols[sUpperCase]; if (symbol != null) { offSymbol = symbol['o']; /* * If the symbol matched but there's no 'o' offset (ie, it wasn't for an address), there's * no point looking any farther, since each symbol appears only once. * * NOTE: We assume that every ROM is ORG'ed at 0x0000, and therefore unless the symbol has an * explicitly-defined segment, we return the segment associated with the entire group; for a ROM, * that segment is normally "addrROM >>> 4". Down the road, we may want/need to support a special * symbol entry (eg, ".ORG") that defines an alternate origin. */ break; } } } if (offSymbol != null) { dbgAddr = this.newAddr(offSymbol); } return dbgAddr; } /** * returnSymbol(iTable, iOffset, aSymbol) * * Helper function for findSymbol(). * * @param {number} iTable * @param {number} iOffset * @param {Array} aSymbol is updated with the specified symbol, if it exists */ returnSymbol(iTable, iOffset, aSymbol) { var symbol = {}; var aOffsets = this.aSymbolTable[iTable].aOffsets; var offset = 0, sSymbol = null; if (iOffset >= 0 && iOffset < aOffsets.length) { offset = aOffsets[iOffset][0]; sSymbol = aOffsets[iOffset][1]; } if (sSymbol) { symbol = this.aSymbolTable[iTable].aSymbols[sSymbol]; sSymbol = (sSymbol.charAt(0) == '.'? null : (symbol['l'] || sSymbol)); } aSymbol.push(sSymbol); aSymbol.push(offset); aSymbol.push(symbol['a']); aSymbol.push(symbol['c']); } /** * doHelp() * * @this {DebuggerPDP11} */ doHelp() { var s = "commands:"; for (var sCommand in DebuggerPDP11.COMMANDS) { s += '\n' + Str.pad(sCommand, 9) + DebuggerPDP11.COMMANDS[sCommand]; } if (!this.checksEnabled()) s += "\nnote: history disabled if no exec breakpoints"; this.println(s); } /** * doAssemble(asArgs) * * This always receives the complete argument array, where the order of the arguments is: * * [0]: the assemble command (assumed to be "a") * [1]: the target address (eg, "200") * [2]: the operation code, aka instruction name (eg, "adc") * [3]: the operation mode operand, if any (eg, "14", "[1234]", etc) * * The Debugger enters "assemble mode" whenever only the first (or first and second) arguments are present. * As long as "assemble mode is active, the user can omit the first two arguments on all later assemble commands * until "assemble mode" is cancelled with an empty command line; the command processor automatically prepends "a" * and the next available target address to the argument array. * * Entering "assemble mode" is optional; one could enter a series of fully-qualified assemble commands; eg: * * a ff00 cld * a ff01 ldx 28 * ... * * without ever entering "assemble mode", but of course, that requires more typing and doesn't take advantage * of automatic target address advancement (see dbgAddrAssemble). * * NOTE: As the previous example implies, you can even assemble new instructions into ROM address space; * as our setByte() function explains, the ROM write-notification handlers only refuse writes from the CPU. * * @this {DebuggerPDP11} * @param {Array.} asArgs is the complete argument array, beginning with the "a" command in asArgs[0] */ doAssemble(asArgs) { var dbgAddr = this.parseAddr(asArgs[1], true); if (!dbgAddr) return; this.dbgAddrAssemble = dbgAddr; if (asArgs[2] === undefined) { this.println("begin assemble at " + this.toStrAddr(dbgAddr)); this.fAssemble = true; this.cmp.updateDisplays(); return; } var aOpBytes = this.parseInstruction(asArgs[2], asArgs[3], dbgAddr); if (aOpBytes.length) { for (var i = 0; i < aOpBytes.length; i++) { this.setByte(dbgAddr, aOpBytes[i], 1); } /* * Since getInstruction() also updates the specified address, dbgAddrAssemble is automatically advanced. */ this.println(this.getInstruction(this.dbgAddrAssemble)); } } /** * doBreak(sCmd, sAddr, sOptions) * * As the "help" output below indicates, the following breakpoint commands are supported: * * bp # set exec breakpoint * br # set read breakpoint * bw # set write breakpoint * bc # clear breakpoint (* to clear all) * bl list all breakpoints * bn [#] break after # instruction(s) * * The "bn" command, like the "dh" command and all other commands that use an instruction count, * assumes a decimal value, regardless of the current base. Use "bn" without an argument to display * the break count, and use "bn 0" to clear the break count. * * @this {DebuggerPDP11} * @param {string} sCmd * @param {string|undefined} [sAddr] * @param {string} [sOptions] (the rest of the breakpoint command-line) */ doBreak(sCmd, sAddr, sOptions) { if (sAddr == '?') { this.println("breakpoint commands:"); this.println("\tbp #\tset exec breakpoint"); this.println("\tbr #\tset read breakpoint"); this.println("\tbw #\tset write breakpoint"); this.println("\tbc #\tclear breakpoint (* to clear all)"); this.println("\tbl\tlist all breakpoints"); this.println("\tbn [#]\tbreak after # instruction(s)"); return; } var sParm = sCmd.charAt(1); if (sParm == 'l') { var cBreaks = 0; cBreaks += this.listBreakpoints(this.aBreakExec); cBreaks += this.listBreakpoints(this.aBreakRead); cBreaks += this.listBreakpoints(this.aBreakWrite); if (!cBreaks) this.println("no breakpoints"); return; } if (sParm == 'n') { var n = +sAddr || 0; if (sAddr) this.nBreakInstructions = n; this.println("break after " + n + " instruction(s)"); return; } if (sAddr === undefined) { this.println("missing breakpoint address"); return; } var dbgAddr = this.newAddr(); if (sAddr != '*') { dbgAddr = this.parseAddr(sAddr, true, true); if (!dbgAddr) return; } if (sParm == 'c') { if (dbgAddr.addr == null) { this.clearBreakpoints(); this.println("all breakpoints cleared"); return; } if (this.findBreakpoint(this.aBreakExec, dbgAddr, true)) return; if (this.findBreakpoint(this.aBreakRead, dbgAddr, true)) return; if (this.findBreakpoint(this.aBreakWrite, dbgAddr, true)) return; this.println("breakpoint missing: " + this.toStrAddr(dbgAddr)); return; } if (dbgAddr.addr == null) return; this.parseAddrOptions(dbgAddr, sOptions); if (sParm == 'p') { this.addBreakpoint(this.aBreakExec, dbgAddr); return; } if (sParm == 'r') { this.addBreakpoint(this.aBreakRead, dbgAddr); return; } if (sParm == 'w') { this.addBreakpoint(this.aBreakWrite, dbgAddr); return; } this.println("unknown breakpoint command: " + sParm); } /** * doClear(sCmd) * * @this {DebuggerPDP11} * @param {string} [sCmd] (eg, "cls" or "clear") */ doClear(sCmd) { /* * TODO: There should be a clear() component method that the Control Panel overrides to perform this function. */ if (this.controlPrint) this.controlPrint.value = ""; } /** * doDump(asArgs) * * The length parameter is interpreted as a number of bytes (or words, or dwords) to dump, * and it is interpreted using the current base. * * @this {DebuggerPDP11} * @param {Array.} asArgs (formerly sCmd, [sAddr], [sLen] and [sBytes]) */ doDump(asArgs) { var m; var sCmd = asArgs[0]; var sAddr = asArgs[1]; var sLen = asArgs[2]; var sBytes = asArgs[3]; if (sAddr == '?') { var sDumpers = ""; for (m in MessagesPDP11.CATEGORIES) { if (this.afnDumpers[m]) { if (sDumpers) sDumpers += ','; sDumpers = sDumpers + m; } } sDumpers += ",state,symbols"; this.println("dump memory commands:"); this.println("\tda [a] dump info for address a"); this.println("\tdb [a] [n] dump n bytes at address a"); this.println("\tdw [a] [n] dump n words at address a"); this.println("\tdd [a] [n] dump n dwords at address a"); this.println("\tds [a] [n] dump n words at address a as JSON"); this.println("\tdh [p] [n] dump n instructions from history position p"); if (sDumpers.length) this.println("dump extension commands:\n\t" + sDumpers); return; } if (sAddr == "state") { var sState = this.cmp.powerOff(true); if (sLen == "console") { /* * Console buffers are notoriously small, and even the following code, which breaks the * data into parts (eg, "d state console 1", "d state console 2", etc) just isn't that helpful. * * var nPart = +sBytes; * if (nPart) sState = sState.substr(1000000 * (nPart-1), 1000000); * * So, the best way to capture a large machine state is to use the new "Save Machine" link * that downloads a machine's entire state. Alternatively, run your own local server and use * server-side storage. Take a look at the "Save" binding in computer.js, which binds an HTML * control to the computer.powerOff() and computer.saveServerState() functions. */ console.log(sState); } else { this.doClear(); if (sState) this.println(sState); } return; } if (sAddr == "symbols") { this.dumpSymbols(); return; } if (sCmd == "d") { for (m in MessagesPDP11.CATEGORIES) { if (asArgs[1] == m) { var fnDumper = this.afnDumpers[m]; if (fnDumper) { asArgs.shift(); asArgs.shift(); fnDumper(asArgs); } else { this.println("no dump registered for " + sAddr); } return; } } if (!sAddr) sCmd = this.sCmdDumpPrev || "dw"; } else { this.sCmdDumpPrev = sCmd; } if (sCmd == "dh") { this.dumpHistory(sAddr, sLen); return; } var dbgAddr = this.parseAddr(sAddr); if (!dbgAddr) return; if (sCmd == "da") { /* * Sample output for a virtual address ("da 23042"): * * 00,010,011,000,100,010 00023042 * OFFSET: 0,011,000,100,010 00003042 * + KIPAR1: 0,000,001,101,111,010,000,000 00157200 * & MMUMASK: 1,111,111,111,111,111,111,111 17777777 * = PHYSICAL: 0,000,001,110,010,010,100,010 00162242 * * and sample output for a physical address (eg, "da %37772"; note the % prefix): * * 0,000,000,011,111,111,111,010 00037772 * OFFSET: 1,111,111,111,010 00017772 * UNIMAP[01]: 1,111,100,001,110,111,000,000 17416700 * PHYSICAL: 1,111,100,011,110,110,111,010 17436672 * * TODO: Tweak this output to accommodate 18-bit machines as well as 22-bit machines. */ var fPhysical = (dbgAddr.fPhysical || dbgAddr.addr > 0xffff); var a = this.cpu.getAddrInfo(dbgAddr.addr || 0, fPhysical); this.println(Str.pad("", fPhysical? 12: 19) + Str.toBin(dbgAddr.addr, fPhysical? 22 : 17, 3) + " " + Str.toOct(dbgAddr.addr, 8)); if (a.length < 6) { if (a.length > 2) { this.println(" OFFSET: " + Str.toBin(a[3], 13, 3) + " " + Str.toOct(a[3], 8)); this.println("UNIMAP[" + Str.toDec(a[1], 2) + "]: " + Str.toBin(a[2], 22, 3) + " " + Str.toOct(a[2], 8)); } this.println(" PHYSICAL: " + Str.toBin(a[0], 22, 3) + " " + Str.toOct(a[0], 8)) } else { this.println(" OFFSET: " + Str.toBin(a[1], 13, 3) + " " + Str.toOct(a[1], 8)); this.println("+ " + DebuggerPDP11.MODES[a[2]] + "PAR" + a[3] + ": " + Str.toBin(a[4], 22, 3) + " " + Str.toOct(a[4], 8)); this.println("& MMUMASK: " + Str.toBin(a[5], 22, 3) + " " + Str.toOct(a[5], 8)); this.println("= PHYSICAL: " + Str.toBin(a[0], 22, 3) + " " + Str.toOct(a[0], 8)) } return; } var len = 0; var fJSON = (sCmd == "ds"); if (sLen) { if (sLen.charAt(0) == 'l') { sLen = sLen.substr(1) || sBytes; len = this.parseValue(sLen); } else { var dbgAddrEnd = this.parseAddr(sLen); if (dbgAddrEnd) len = dbgAddrEnd.addr - dbgAddr.addr; } if (len < 0) len = 0; if (len > 0x10000) len = 0x10000; } var nBase = this.nBase; if (dbgAddr.nBase) this.nBase = dbgAddr.nBase; /* * I've changed the code below to effectively make "dw" the default if only "d" is specified, * since this is primarily a word-oriented machine. */ var size = (sCmd == "dd"? 4 : (sCmd == "db"? 1 : 2)); var nBytes = (size * len) || 128; var nBytesPerLine = fJSON? 16 : this.nBase; var nLines = (((nBytes + nBytesPerLine - 1) / nBytesPerLine)|0) || 1; var sDump = ""; while (nLines-- && nBytes > 0) { var sData = "", sChars = ""; sAddr = this.toStrAddr(dbgAddr); /* * Dump 8 bytes per line when using base 8, and dump 16 bytes when using base 16. * * And while we used to always call getByte() and assemble them into words or dwords as appropriate, I've * changed the logic below to honor "dw" by calling getWord(), since the Bus interfaces have been updated * to prevent generating traps due to to Debugger access of unaligned memory and/or undefined IOPAGE addresses. * * Besides, it's nice for "db" and "dw" to generate the same Bus activity that typical byte and word reads do. */ var i = nBytesPerLine; var data = 0, shift = 0; while (i > 0 && nBytes > 0) { var n = 1; var v = size == 1? this.getByte(dbgAddr, n) : this.getWord(dbgAddr, (n = 2)); data |= (v << (shift << 3)); shift += n; if (shift == size) { if (fJSON) { if (sData) sData += ","; sData += "0x"+ Str.toHex(data, size * 2); } else { sData += this.toStrBase(data, size); sData += (size == 1? (i == 9? '-' : ' ') : " "); } data = shift = 0; } i -= n; nBytes -= n; while (size == 1 && n--) { var c = v & 0xff; sChars += (c >= 32 && c < 128? String.fromCharCode(c) : '.'); v >>= 8; } } if (sDump) sDump += "\n"; if (fJSON) { sDump += sData + ","; } else { sDump += sAddr + ": " + sData + ((i == 0)? (' ' + sChars) : ""); } } if (sDump) this.println(sDump); this.dbgAddrNextData = dbgAddr; this.nBase = nBase; } /** * doEdit(asArgs) * * @this {DebuggerPDP11} * @param {Array.} asArgs */ doEdit(asArgs) { var size, mask; var fnGet, fnSet; var sCmd = asArgs[0]; var sAddr = asArgs[1]; if (sCmd == "eb") { size = 1; mask = 0xff; fnGet = this.getByte; fnSet = this.setByte; } else if (sCmd == "e" || sCmd == "ew") { size = 2; mask = 0xffff; fnGet = this.getWord; fnSet = this.setWord; } else { sAddr = null; } if (sAddr == null) { this.println("edit memory commands:"); this.println("\teb [a] [...] edit bytes at address a"); this.println("\tew [a] [...] edit words at address a"); return; } var dbgAddr = this.parseAddr(sAddr); if (!dbgAddr) return; for (var i = 2; i < asArgs.length; i++) { var vNew = this.parseExpression(asArgs[i]); if (vNew === undefined) { this.println("unrecognized value: " + asArgs[i]); break; } if (vNew & ~mask) { this.println("warning: " + Str.toHex(vNew) + " exceeds " + size + "-byte value"); } this.println("changing " + this.toStrAddr(dbgAddr) + (this.messageEnabled(MessagesPDP11.BUS)? "" : (" from " + this.toStrBase(fnGet.call(this, dbgAddr), size))) + " to " + this.toStrBase(vNew, size)); //noinspection JSUnresolvedFunction fnSet.call(this, dbgAddr, vNew, size); } } /** * doHalt(fQuiet) * * @this {DebuggerPDP11} * @param {boolean} [fQuiet] */ doHalt(fQuiet) { var sMsg; if (this.flags.running) { if (!fQuiet) this.println("halting"); this.stopCPU(); } else { if (this.isBusy(true)) return; if (!fQuiet) this.println("already halted"); } } /** * doIf(sCmd, fQuiet) * * NOTE: Don't forget that the default base for all numeric constants is 16 (hex), so when you evaluate * an expression like "a==10", it will compare the value of the variable "a" to 0x10; use a trailing period * (eg, "10.") if you really intend decimal. * * Also, if no variable named "a" exists, "a" will evaluate to 0x0A, so the expression "a==10" becomes * "0x0A==0x10" (false), whereas the expression "a==10." becomes "0x0A==0x0A" (true). * * @this {DebuggerPDP11} * @param {string} sCmd * @param {boolean} [fQuiet] * @return {boolean} true if expression is non-zero, false if zero (or undefined due to a parse error) */ doIf(sCmd, fQuiet) { sCmd = Str.trim(sCmd); if (!this.parseExpression(sCmd)) { if (!fQuiet) this.println("false: " + sCmd); return false; } if (!fQuiet) this.println("true: " + sCmd); return true; } /** * doInfo(asArgs) * * @this {DebuggerPDP11} * @param {Array.} asArgs * @return {boolean} true only if the instruction info command ("n") is supported */ doInfo(asArgs) { if (DEBUG) { this.println("msPerYield: " + this.cpu.msPerYield); this.println("nCyclesPerYield: " + this.cpu.nCyclesPerYield); return true; } return false; } /** * doVar(sCmd) * * The command must be of the form "{variable} = [{expression}]", where expression may contain constants, * operators, registers, symbols, other variables, or nothing at all; in the latter case, the variable, if * any, is deleted. * * Other supported shorthand: "var" with no parameters prints the values of all variables, and "var {variable}" * prints the value of the specified variable. * * @this {DebuggerPDP11} * @param {string} sCmd * @return {boolean} true if valid "var" assignment, false if not */ doVar(sCmd) { var a = sCmd.match(/^\s*([A-Z_]?[A-Z0-9_]*)\s*(=?)\s*(.*)$/i); if (a) { if (!a[1]) { if (!this.printVariable()) this.println("no variables"); return true; // it's not considered an error to print an empty list of variables } if (!a[2]) { return this.printVariable(a[1]); } if (!a[3]) { this.delVariable(a[1]); return true; // it's not considered an error to delete a variable that didn't exist } var v = this.parseExpression(a[3]); if (v !== undefined) { this.setVariable(a[1], v); return true; } return false; } this.println("invalid assignment:" + sCmd); return false; } /** * doList(sAddr, fPrint) * * @this {DebuggerPDP11} * @param {string} sAddr * @param {boolean} [fPrint] * @return {string|null} */ doList(sAddr, fPrint) { var sSymbol = null; var dbgAddr = this.parseAddr(sAddr, true); if (dbgAddr) { var addr = this.getAddr(dbgAddr); var aSymbol = this.findSymbol(dbgAddr, true); if (aSymbol.length) { var nDelta, sDelta, s; if (aSymbol[0]) { sDelta = ""; nDelta = dbgAddr.addr - aSymbol[1]; if (nDelta) sDelta = " + " + Str.toHexWord(nDelta); s = aSymbol[0] + " (" + this.toStrOffset(aSymbol[1]) + ')' + sDelta; if (fPrint) this.println(s); sSymbol = s; } if (aSymbol.length > 4 && aSymbol[4]) { sDelta = ""; nDelta = aSymbol[5] - dbgAddr.addr; if (nDelta) sDelta = " - " + Str.toHexWord(nDelta); s = aSymbol[4] + " (" + this.toStrOffset(aSymbol[5]) + ')' + sDelta; if (fPrint) this.println(s); if (!sSymbol) sSymbol = s; } } else { if (fPrint) this.println("no symbols"); } } return sSymbol; } /** * doMessages(asArgs) * * @this {DebuggerPDP11} * @param {Array.} asArgs */ doMessages(asArgs) { var m; var fCriteria = null; var sCategory = asArgs[1]; if (sCategory == '?') sCategory = undefined; if (sCategory !== undefined) { var bitsMessage = 0; if (sCategory == "all") { bitsMessage = (0xffffffff|0) & ~(MessagesPDP11.HALT | MessagesPDP11.KEYS | MessagesPDP11.LOG); sCategory = null; } else if (sCategory == "on") { fCriteria = true; sCategory = null; } else if (sCategory == "off") { fCriteria = false; sCategory = null; } else { /* * Internally, we use "key" instead of "keys", since the latter is a method on JavasScript objects, * but externally, we allow the user to specify "keys"; "kbd" is also allowed as shorthand for "keyboard". */ if (sCategory == "keys") sCategory = "key"; if (sCategory == "kbd") sCategory = "keyboard"; for (m in MessagesPDP11.CATEGORIES) { if (sCategory == m) { bitsMessage = MessagesPDP11.CATEGORIES[m]; fCriteria = !!(this.bitsMessage & bitsMessage); break; } } if (!bitsMessage) { this.println("unknown message category: " + sCategory); return; } } if (bitsMessage) { if (asArgs[2] == "on") { this.bitsMessage |= bitsMessage; fCriteria = true; } else if (asArgs[2] == "off") { this.bitsMessage &= ~bitsMessage; fCriteria = false; if (bitsMessage == MessagesPDP11.BUFFER) { var i = this.aMessageBuffer.length >= 1000? this.aMessageBuffer.length - 1000 : 0; while (i < this.aMessageBuffer.length) { this.println(this.aMessageBuffer[i++]); } this.aMessageBuffer = []; } } } } /* * Display those message categories that match the current criteria (on or off) */ var n = 0; var sCategories = ""; for (m in MessagesPDP11.CATEGORIES) { if (!sCategory || sCategory == m) { var bitMessage = MessagesPDP11.CATEGORIES[m]; var fEnabled = !!(this.bitsMessage & bitMessage); if (fCriteria !== null && fCriteria != fEnabled) continue; if (sCategories) sCategories += ','; if (!(++n % 10)) sCategories += "\n\t"; // jshint ignore:line /* * Internally, we use "key" instead of "keys", since the latter is a method on JavasScript objects, * but externally, we allow the user to specify "keys". */ if (m == "key") m = "keys"; sCategories += m; } } if (sCategory === undefined) { this.println("message commands:\n\tm [category] [on|off]\tturn categories on/off"); } this.println((fCriteria !== null? (fCriteria? "messages on: " : "messages off: ") : "message categories:\n\t") + (sCategories || "none")); this.historyInit(); // call this just in case MessagesPDP11.INT was turned on } /** * doOptions(asArgs) * * @this {DebuggerPDP11} * @param {Array.} asArgs */ doOptions(asArgs) { switch (asArgs[1]) { case "base": if (asArgs[2]) { var nBase = +asArgs[2]; if (nBase == 8 || nBase == 10 || nBase == 16) { this.nBase = nBase; } else { this.println("invalid base: " + nBase); break; } } this.println("default base: " + this.nBase); break; case "cs": var nCycles; if (asArgs[3] !== undefined) nCycles = +asArgs[3]; // warning: decimal instead of hex conversion switch (asArgs[2]) { case "int": this.cpu.nCyclesChecksumInterval = nCycles; break; case "start": this.cpu.nCyclesChecksumStart = nCycles; break; case "stop": this.cpu.nCyclesChecksumStop = nCycles; break; default: this.println("unknown cs option"); return; } if (nCycles !== undefined) { this.cpu.resetChecksum(); } this.println("checksums " + (this.cpu.flags.checksum? "enabled" : "disabled")); return; case "sp": if (asArgs[2] !== undefined) { if (!this.cpu.setSpeed(+asArgs[2])) { this.println("warning: using 1x multiplier, previous target not reached"); } } this.println("target speed: " + this.cpu.getSpeedTarget() + " (" + this.cpu.getSpeed() + "x)"); return; default: if (asArgs[1]) { this.println("unknown option: " + asArgs[1]); return; } /* falls through */ case "?": this.println("debugger options:"); this.println("\tbase #\t\tset default base to #"); this.println("\tcs int #\tset checksum cycle interval to #"); this.println("\tcs start #\tset checksum cycle start count to #"); this.println("\tcs stop #\tset checksum cycle stop count to #"); this.println("\tsp #\t\tset speed multiplier to #"); break; } } /** * doRegisters(asArgs, fInstruction) * * @this {DebuggerPDP11} * @param {Array.} [asArgs] * @param {boolean} [fInstruction] (true to include the current instruction; default is true) */ doRegisters(asArgs, fInstruction) { if (asArgs && asArgs[1] == '?') { this.println("register commands:"); this.println("\tr\tdump registers"); this.println("\trm\tdump misc registers"); this.println("\trx [#]\tset flag or register x to [#]"); return; } var fMisc = false; var cpu = this.cpu; if (fInstruction == null) fInstruction = true; if (asArgs != null && asArgs.length > 1) { var sReg = asArgs[1]; if (sReg == 'm') { fMisc = true; } else { var sValue = null; var i = sReg.indexOf('='); if (i > 0) { sValue = sReg.substr(i + 1); sReg = sReg.substr(0, i); } else if (asArgs.length > 2) { sValue = asArgs[2]; } else { this.println("missing value for " + asArgs[1]); return; } var w = this.parseExpression(sValue); if (w === undefined) return; var sRegMatch = sReg.toUpperCase(); switch (sRegMatch) { case "SP": case "R6": cpu.setSP(w); break; case "PC": case "R7": cpu.setPC(w); this.dbgAddrNextCode = this.newAddr(cpu.getPC()); break; case "N": if (w) cpu.setNF(); else cpu.clearNF(); break; case "Z": if (w) cpu.setZF(); else cpu.clearZF(); break; case "V": if (w) cpu.setVF(); else cpu.clearVF(); break; case "C": if (w) cpu.setCF(); else cpu.clearCF(); break; case "PS": cpu.setPSW(w); break; case "PI": cpu.setPIR(w); break; case "ER": cpu.regErr = w; fMisc = true; break; case "SL": cpu.setSLR(w); break; case "M0": cpu.setMMR0(w); fMisc = true; break; case "M3": cpu.setMMR3(w); fMisc = true; break; case "AR": if (this.panel) this.panel.setAR(w); fMisc = true; break; case "DR": if (this.panel) this.panel.setDR(w); fMisc = true; break; case "SR": if (this.panel && this.panel.hasSwitches()) { this.panel.setSR(w); fMisc = true; break; } /* falls through */ default: if (sRegMatch.charAt(0) == 'R') { var iReg = +sRegMatch.charAt(1); if (iReg >= 0 && iReg < 6) { cpu.regsGen[iReg] = w & 0xffff; break; } } this.println("unknown register: " + sReg); return; } this.cmp.updateDisplays(); this.println("updated registers:"); } } this.println(this.getRegDump(fMisc)); if (fInstruction) { this.dbgAddrNextCode = this.newAddr(cpu.getPC()); this.doUnassemble(this.toStrAddr(this.dbgAddrNextCode)); } } /** * doRun(sCmd, sAddr, sOptions, fQuiet) * * @this {DebuggerPDP11} * @param {string} sCmd * @param {string|undefined} [sAddr] * @param {string} [sOptions] (the rest of the breakpoint command-line) * @param {boolean} [fQuiet] */ doRun(sCmd, sAddr, sOptions, fQuiet) { if (sCmd == "gt") { this.fIgnoreNextCheckFault = true; } if (sAddr !== undefined) { var dbgAddr = this.parseAddr(sAddr, true); if (!dbgAddr) return; this.parseAddrOptions(dbgAddr, sOptions); this.setTempBreakpoint(dbgAddr); } this.startCPU(true, fQuiet); } /** * doPrint(sCmd) * * NOTE: If the string to print is a quoted string, then we run it through replaceRegs(), so that * you can take advantage of all the special replacement options used for software interrupt logging. * * @this {DebuggerPDP11} * @param {string} sCmd */ doPrint(sCmd) { sCmd = Str.trim(sCmd); var a = sCmd.match(/^(['"])(.*?)\1$/); if (!a) { this.parseExpression(sCmd, true); } else { if (a[2].length > 1) { this.println(this.replaceRegs(a[2])); } else { this.printValue(null, a[2].charCodeAt(0)); } } } /** * doStep(sCmd, sOption) * * @this {DebuggerPDP11} * @param {string} [sCmd] "p" or "pr" * @param {string} [sOption] */ doStep(sCmd, sOption) { if (sOption == '?') { this.println("step commands:"); this.println("\tp\tstep over instruction"); this.println("\tpr\tstep over instruction with register update"); return; } var fCallStep = true; var nRegs = (sCmd == "pr"? 1 : 0); /* * Set up the value for this.nStep (ie, 1 or 2) depending on whether the user wants * a subsequent register dump ("pr") or not ("p"). */ var nStep = 1 + nRegs; if (!this.nStep) { var dbgAddr = this.newAddr(this.cpu.getPC()); var opCode = this.getWord(dbgAddr); if (opCode == PDP11.OPCODE.BPT || opCode == PDP11.OPCODE.IOT || (opCode & PDP11.OPCODE.EMT_MASK) == PDP11.OPCODE.EMT_OP || (opCode & PDP11.OPCODE.SOB_MASK) == PDP11.OPCODE.SOB_OP || (opCode & PDP11.OPCODE.TRAP_MASK) == PDP11.OPCODE.TRAP_OP) { if (fCallStep) { this.nStep = nStep; this.incAddr(dbgAddr, 2); } } else if ((opCode & PDP11.OPCODE.JSR_MASK) == PDP11.OPCODE.JSR_OP) { var s = this.getInstruction(dbgAddr); this.assert(s.indexOf("JSR") >= 0); if (fCallStep) { this.nStep = nStep; } } if (this.nStep) { this.setTempBreakpoint(dbgAddr); if (!this.startCPU()) { if (this.cmp) this.cmp.setFocus(); this.nStep = 0; } /* * A successful run will ultimately call stop(), which will in turn call clearTempBreakpoint(), * which will clear nStep, so there's your assurance that nStep will be reset. Now we may have * stopped for reasons unrelated to the temporary breakpoint, but that's OK. */ } else { this.doTrace(nRegs? "tr" : "t"); } } else { this.println("step in progress"); } } /** * getCall(dbgAddr) * * Given a possible return address (typically from the stack), look for a matching CALL (or INT) that * immediately precedes that address. * * @this {DebuggerPDP11} * @param {DbgAddrPDP11} dbgAddr * @return {string|null} CALL instruction at or near dbgAddr, or null if none */ getCall(dbgAddr) { var sCall = null; var addr = dbgAddr.addr; var addrOrig = addr; for (var n = 1; n <= 6 && !!addr; n++) { if (n > 2) { dbgAddr.addr = addr; var s = this.getInstruction(dbgAddr); if (s.indexOf("JSR") >= 0) { /* * Verify that the length of this call, when added to the address of the call, matches * the original return address. We do this by getting the string index of the opcode bytes, * subtracting that from the string index of the next space, and dividing that difference * by two, to yield the length of the CALL (or INT) instruction, in bytes. */ var i = s.indexOf(' '); var j = s.indexOf(' ', i+1); if (addr + (j - i - 1)/2 == addrOrig) { sCall = s; break; } } } addr -= 2; } dbgAddr.addr = addrOrig; return sCall; } /** * doStackTrace(sCmd, sAddr) * * Use "k" for a normal stack trace and "ks" for a stack trace with symbolic info. * * @this {DebuggerPDP11} * @param {string} [sCmd] * @param {string} [sAddr] (not used yet) */ doStackTrace(sCmd, sAddr) { if (sAddr == '?') { this.println("stack trace commands:"); this.println("\tk\tshow frame addresses"); this.println("\tks\tshow symbol information"); return; } var nFrames = 10, cFrames = 0; var dbgAddrCall = this.newAddr(); var dbgAddrStack = this.newAddr(this.cpu.getSP()); this.println("stack trace for " + this.toStrAddr(dbgAddrStack)); while (cFrames < nFrames) { var sCall = null, sCallPrev = null, cTests = 256; while ((dbgAddrStack.addr >>> 0) < 0x10000) { dbgAddrCall.addr = this.getWord(dbgAddrStack, 2); /* * Because we're using the auto-increment feature of getWord(), and because that will automatically * wrap the offset around the end of the segment, we must also check the addr property to detect the wrap. */ if (dbgAddrStack.addr == null || !cTests--) break; if (dbgAddrCall.addr & 0x1) continue; // an odd address on the PDP-11 is not a valid instruction boundary sCall = this.getCall(dbgAddrCall); if (sCall) break; } /* * The sCallPrev check eliminates duplicate sequential calls, which are usually (but not always) * indicative of a false positive, in which case the previous call is probably bogus as well, but * at least we won't duplicate that mistake. Of course, there are always exceptions, recursion * being one of them, but it's rare that we're debugging recursive code. */ if (!sCall || sCall == sCallPrev) break; var sSymbol = null; if (sCmd == "ks") { var a = sCall.match(/[0-9A-F]+$/); if (a) sSymbol = this.doList(a[0]); } sCall = Str.pad(sCall, 50) + " ;" + (sSymbol || "stack=" + this.toStrAddr(dbgAddrStack)); // + " return=" + this.toStrAddr(dbgAddrCall)); this.println(sCall); sCallPrev = sCall; cFrames++; } if (!cFrames) this.println("no return addresses found"); } /** * doTrace(sCmd, sCount) * * The "t" and "tr" commands interpret the count as a number of instructions, and since * we call the Debugger's stepCPU() for each iteration, a single instruction includes * any/all prefixes; the CPU's stepCPU() treats prefixes as discrete operations. The only * difference between "t" and "tr": the former displays only the next instruction, while * the latter also displays the (updated) registers. * * The "tc" command interprets the count as a number of cycles rather than instructions, * allowing you to quickly execute large chunks of instructions with a single command; it * doesn't display anything until the the chunk has finished. "tc 1" is also a useful * command in that it doesn't inhibit interrupts like "t" or "tr" does. * * However, generally a more useful command is "bn", which allows you to break after some * number of instructions have been executed (as opposed to some number of cycles). * * @this {DebuggerPDP11} * @param {string} [sCmd] ("t", "tc", or "tr") * @param {string} [sCount] # of instructions to step */ doTrace(sCmd, sCount) { if (sCount == '?') { this.println("trace commands:"); this.println("\tt [#]\ttrace # instructions"); this.println("\ttr [#]\ttrace # instructions with register updates"); this.println("\ttc [#]\ttrace # cycles"); this.println("note: bn [#] breaks after # instructions without updates"); return; } var dbg = this; var fRegs = (sCmd != "t"); var nCount = this.parseValue(sCount, null, true) || 1; /* * We used to set nCycles to 1 when a count > 1 was specified, because nCycles set * to 0 used to mean "execute the next instruction without checking for interrupts". * Well, this machine's stepCPU() doesn't do that; it ALWAYS checks for interrupts, * so we should leave nCycles set to 0, so that if an interrupt is dispatched, we will * get to see the first instruction of the interrupt handler. */ var nCycles = 0; // (nCount == 1? 0 : 1); if (sCmd == "tc") { nCycles = nCount; nCount = 1; } this.sCmdTracePrev = sCmd; Web.onCountRepeat( nCount, function onCountStep() { return dbg.setBusy(true) && dbg.stepCPU(nCycles, fRegs, false); }, function onCountStepComplete() { /* * We explicitly called stepCPU() with fUpdateDisplays set to false, because repeatedly * calling updateDisplays() can be very slow, especially if a Control Panel is present with * displayLiveRegs enabled, so once the repeat count has been exhausted, we must perform * a final updateDisplays(). */ if (dbg.panel) dbg.panel.stop(); dbg.cmp.updateDisplays(-1); dbg.setBusy(false); } ); } /** * doUnassemble(sAddr, sAddrEnd, nLines) * * @this {DebuggerPDP11} * @param {string} [sAddr] * @param {string} [sAddrEnd] * @param {number} [nLines] */ doUnassemble(sAddr, sAddrEnd, nLines) { var dbgAddr = this.parseAddr(sAddr, true); if (!dbgAddr) return; if (nLines === undefined) nLines = 1; var nBytes = 0x100; if (sAddrEnd !== undefined) { if (sAddrEnd.charAt(0) == 'l') { var n = this.parseValue(sAddrEnd.substr(1)); if (n != null) nLines = n; } else { var dbgAddrEnd = this.parseAddr(sAddrEnd, true); if (!dbgAddrEnd || dbgAddrEnd.addr < dbgAddr.addr) return; nBytes = dbgAddrEnd.addr - dbgAddr.addr; if (!DEBUG && nBytes > 0x100) { /* * Limiting the amount of disassembled code to 256 bytes in non-DEBUG builds is partly to * prevent the user from wedging the browser by dumping too many lines, but also a recognition * that, in non-DEBUG builds, this.println() keeps print output buffer truncated to 8Kb anyway. */ this.println("range too large"); return; } nLines = -1; } } var nPrinted = 0; var sInstruction; while (nBytes > 0 && nLines--) { var nSequence = (this.isBusy(false) || this.nStep)? this.nCycles : null; var sComment = (nSequence != null? "cycles" : null); var aSymbol = this.findSymbol(dbgAddr); var addr = dbgAddr.addr; // we snap dbgAddr.addr *after* calling findSymbol(), which re-evaluates it if (aSymbol[0] && nLines) { if (!nPrinted && nLines || aSymbol[0].indexOf('+') < 0) { var sLabel = aSymbol[0] + ':'; if (aSymbol[2]) sLabel += ' ' + aSymbol[2]; this.println(sLabel); } } if (aSymbol[3]) { sComment = aSymbol[3]; nSequence = null; } sInstruction = this.getInstruction(dbgAddr, sComment, nSequence); this.println(sInstruction); this.dbgAddrNextCode = dbgAddr; nBytes -= dbgAddr.addr - addr; nPrinted++; } } /** * splitArgs(sCmd) * * @this {DebuggerPDP11} * @param {string} sCmd * @return {Array.} */ splitArgs(sCmd) { var asArgs = sCmd.replace(/ +/g, ' ').split(' '); asArgs[0] = asArgs[0].toLowerCase(); if (asArgs && asArgs.length) { var s0 = asArgs[0]; var ch0 = s0.charAt(0); for (var i = 1; i < s0.length; i++) { var ch = s0.charAt(i); if (ch0 == '?' || ch0 == 'r' || ch < 'a' || ch > 'z') { asArgs[0] = s0.substr(i); asArgs.unshift(s0.substr(0, i)); break; } } } return asArgs; } /** * doCommand(sCmd, fQuiet) * * @this {DebuggerPDP11} * @param {string} sCmd * @param {boolean} [fQuiet] * @return {boolean} true if command processed, false if unrecognized */ doCommand(sCmd, fQuiet) { var result = true; try { if (!sCmd.length || sCmd == "end") { if (this.fAssemble) { this.println("ended assemble at " + this.toStrAddr(this.dbgAddrAssemble)); this.dbgAddrNextCode = this.dbgAddrAssemble; this.fAssemble = false; } sCmd = ""; } else if (!fQuiet) { this.println(DebuggerPDP11.PROMPT + sCmd); } var ch = sCmd.charAt(0); if (ch == '"' || ch == "'") return true; /* * Zap the previous message buffer to ensure the new command's output is not tossed out as a repeat. */ this.sMessagePrev = null; /* * I've relaxed the !isBusy() requirement, to maximize our ability to issue Debugger commands externally. */ if (this.isReady() /* && !this.isBusy(true) */ && sCmd.length > 0) { if (this.fAssemble) { sCmd = "a " + this.toStrAddr(this.dbgAddrAssemble) + ' ' + sCmd; } var fError = false; var asArgs = this.splitArgs(sCmd); switch (asArgs[0].charAt(0)) { case 'a': this.doAssemble(asArgs); break; case 'b': this.doBreak(asArgs[0], asArgs[1], sCmd); break; case 'c': this.doClear(asArgs[0]); break; case 'd': if (!COMPILED && sCmd == "debug") { window.DEBUG = true; this.println("DEBUG checks on"); break; } this.doDump(asArgs); break; case 'e': if (asArgs[0] == "else") break; this.doEdit(asArgs); break; case 'g': this.doRun(asArgs[0], asArgs[1], sCmd, fQuiet); break; case 'h': this.doHalt(fQuiet); break; case 'i': if (asArgs[0] == "if") { if (!this.doIf(sCmd.substr(2), fQuiet)) { result = false; } break; } fError = true; break; case 'k': this.doStackTrace(asArgs[0], asArgs[1]); break; case 'l': if (asArgs[0] == "ln") { this.doList(asArgs[1], true); break; } fError = true; break; case 'm': this.doMessages(asArgs); break; case 'p': if (asArgs[0] == "print") { this.doPrint(sCmd.substr(5)); break; } this.doStep(asArgs[0], asArgs[1]); break; case 'r': if (sCmd == "reset") { if (this.cmp) this.cmp.reset(); break; } this.doRegisters(asArgs); break; case 's': this.doOptions(asArgs); break; case 't': this.doTrace(asArgs[0], asArgs[1]); break; case 'u': this.doUnassemble(asArgs[1], asArgs[2], 8); break; case 'v': if (asArgs[0] == "var") { if (!this.doVar(sCmd.substr(3))) { result = false; } break; } if (asArgs[0] == "ver") { this.println((PDP11.APPNAME || "PDP11") + " version " + (XMLVERSION || PDP11.APPVERSION) + " (" + this.cpu.model + (PDP11.COMPILED? ",RELEASE" : (PDP11.DEBUG? ",DEBUG" : ",NODEBUG")) + (PDP11.TYPEDARRAYS? ",TYPEDARRAYS" : (PDP11.BYTEARRAYS? ",BYTEARRAYS" : ",LONGARRAYS")) + ')'); this.println(Web.getUserAgent()); break; } fError = true; break; case '?': if (asArgs[1]) { this.doPrint(sCmd.substr(1)); break; } this.doHelp(); break; case 'n': if (!COMPILED && sCmd == "nodebug") { window.DEBUG = false; this.println("DEBUG checks off"); break; } if (this.doInfo(asArgs)) break; /* falls through */ default: fError = true; break; } if (fError) { this.println("unknown command: " + sCmd); result = false; } } } catch(e) { this.println("debugger error: " + (e.stack || e.message)); result = false; } return result; } /** * doCommands(sCmds, fSave) * * @this {DebuggerPDP11} * @param {string} sCmds * @param {boolean} [fSave] * @return {boolean} true if all commands processed, false if not */ doCommands(sCmds, fSave) { var a = this.parseCommand(sCmds, fSave); for (var s in a) { if (!this.doCommand(a[+s])) return false; } return true; } /** * DebuggerPDP11.init() * * This function operates on every HTML element of class "debugger", extracting the * JSON-encoded parameters for the Debugger constructor from the element's "data-value" * attribute, invoking the constructor to create a Debugger component, and then binding * any associated HTML controls to the new component. */ static init() { var aeDbg = Component.getElementsByClass(document, PDP11.APPCLASS, "debugger"); for (var iDbg = 0; iDbg < aeDbg.length; iDbg++) { var eDbg = aeDbg[iDbg]; var parmsDbg = Component.getComponentParms(eDbg); var dbg = new DebuggerPDP11(parmsDbg); Component.bindComponentControls(dbg, eDbg, PDP11.APPCLASS); } } } if (DEBUGGER) { /* * NOTE: Every DebuggerPDP11 property from here to the first prototype function definition (initBus()) is * considered a "class constant"; most of them use our "all-caps" convention (and all of them SHOULD, but * that wouldn't help us catch any bugs). * * Technically, all of them should ALSO be preceded by a "@const" annotation, but that's a lot of work and it * really clutters the code. I wish the Closure Compiler had a way to annotate every definition with a given * section with a single annotation.... */ DebuggerPDP11.COMMANDS = { '?': "help/print", 'a [#]': "assemble", // TODO: Implement this command someday 'b [#]': "breakpoint", // multiple variations (use b? to list them) 'c': "clear output", 'd [#]': "dump memory", // additional syntax: d [#] [l#], where l# is a number of bytes to dump 'e [#]': "edit memory", 'g [#]': "go [to #]", 'h': "halt", 'if': "eval expression", 'int [#]': "request interrupt", 'k': "stack trace", "ln": "list nearest symbol(s)", 'm': "messages", 'p': "step over", // other variations: pr (step and dump registers) 'print': "print expression", 'r': "dump/set registers", 'reset': "reset machine", 's': "set options", 't [#]': "trace", // other variations: tr (trace and dump registers) 'u [#]': "unassemble", 'var': "assign variable", 'ver': "print version" }; /* * CPU opcode IDs * * Not listed: BLO (same as BCS) and BHIS (same as BCC). */ DebuggerPDP11.OPS = { NONE: 0, ADC: 1, ADCB: 2, ADD: 3, ASL: 4, ASLB: 5, ASR: 6, ASRB: 7, BCC: 8, BCS: 9, BEQ: 10, BGE: 11, BGT: 12, BHI: 13, BIC: 14, BICB: 15, BIS: 16, BISB: 17, BIT: 18, BITB: 19, BLE: 20, BLOS: 21, BLT: 22, BMI: 23, BNE: 24, BPL: 25, BPT: 26, BR: 27, BVC: 28, BVS: 29, CCC: 30, CLC: 31, CLCN: 32, CLCV: 33, CLCVN: 34, CLCVZ: 35, CLCZ: 36, CLCZN: 37, CLN: 38, CLR: 39, CLRB: 40, CLV: 41, CLVN: 42, CLVZ: 43, CLVZN: 44, CLZ: 45, CLZN: 46, CMP: 47, CMPB: 48, COM: 49, COMB: 50, DEC: 51, DECB: 52, INC: 53, INCB: 54, HALT: 55, JMP: 56, JSR: 57, MARK: 58, MFPD: 59, MFPI: 60, MFPS: 61, MOV: 62, MOVB: 63, MTPD: 64, MTPI: 65, MTPS: 66, NEG: 67, NEGB: 68, NOP: 69, RESET: 70, ROL: 71, ROLB: 72, ROR: 73, RORB: 74, RTI: 75, RTS: 76, SBC: 77, SBCB: 78, SCC: 79, SEC: 80, SECN: 81, SECV: 82, SECVN: 83, SECVZ: 84, SECZ: 85, SECZN: 86, SEN: 87, SEV: 88, SEVN: 89, SEVZ: 90, SEVZN: 91, SEZ: 92, SEZN: 93, SUB: 94, SWAB: 95, SXT: 96, TST: 97, TSTB: 98, WAIT: 99, MUL: 100, DIV: 101, ASH: 102, ASHC: 103, XOR: 104, SOB: 105, EMT: 106, TRAP: 107, SPL: 108, IOT: 109, RTT: 110, MFPT: 111 }; /* * CPU opcode names, indexed by CPU opcode ordinal (above) */ DebuggerPDP11.OPNAMES = [ ".WORD", "ADC", "ADCB", "ADD", "ASL", "ASLB", "ASR", "ASRB", "BCC", "BCS", "BEQ", "BGE", "BGT", "BHI", "BIC", "BICB", "BIS", "BISB", "BIT", "BITB", "BLE", "BLOS", "BLT", "BMI", "BNE", "BPL", "BPT", "BR", "BVC", "BVS", "CCC", "CLC", "CLCN", "CLCV", "CLCVN", "CLCVZ", "CLCZ", "CLCZN", "CLN", "CLR", "CLRB", "CLV", "CLVN", "CLVZ", "CLVZN", "CLZ", "CLZN", "CMP", "CMPB", "COM", "COMB", "DEC", "DECB", "INC", "INCB", "HALT", "JMP", "JSR", "MARK", "MFPD", "MFPI", "MFPS", "MOV", "MOVB", "MTPD", "MTPI", "MTPS", "NEG", "NEGB", "NOP", "RESET", "ROL", "ROLB", "ROR", "RORB", "RTI", "RTS", "SBC", "SBCB", "SCC", "SEC", "SECN", "SECV", "SECVN", "SECVZ", "SECZ", "SECZN", "SEN", "SEV", "SEVN", "SEVZ", "SEVZN", "SEZ", "SEZN", "SUB", "SWAB", "SXT", "TST", "TSTB", "WAIT", "MUL", "DIV", "ASH", "ASHC", "XOR", "SOB", "EMT", "TRAP", "SPL", "IOT", "RTT", "MFPT" ]; /* * Register numbers 0-7 are reserved for cpu.regsGen, 8-15 are reserved for cpu.regsAlt, and 16-19 for cpu.regsStack. */ DebuggerPDP11.REG_PS = 20; DebuggerPDP11.REG_PI = 21; DebuggerPDP11.REG_ER = 22; DebuggerPDP11.REG_SL = 23; DebuggerPDP11.REG_M0 = 24; DebuggerPDP11.REG_M1 = 25; DebuggerPDP11.REG_M2 = 26; DebuggerPDP11.REG_M3 = 27; DebuggerPDP11.REG_AR = 28; // ADDRESS register; see Panel's getAR() and setAR() DebuggerPDP11.REG_DR = 29; // DISPLAY/DATA register; see Panel's getDR() and setDR() DebuggerPDP11.REG_SR = 30; // SWITCH register; see Panel's getSR() and setSR() DebuggerPDP11.REGS = { "SP": 6, "PC": 7, "PS": DebuggerPDP11.REG_PS, "PI": DebuggerPDP11.REG_PI, "ER": DebuggerPDP11.REG_ER, "SL": DebuggerPDP11.REG_SL, "M0": DebuggerPDP11.REG_M0, "M1": DebuggerPDP11.REG_M1, "M2": DebuggerPDP11.REG_M2, "M3": DebuggerPDP11.REG_M3, "AR": DebuggerPDP11.REG_AR, "DR": DebuggerPDP11.REG_DR, "SR": DebuggerPDP11.REG_SR }; DebuggerPDP11.MODES = ["KI","KD","SI","SD","??","??","UI","UD"]; /* * Operand type masks; anything that's not covered by OP_SRC or OP_DST must be a OP_OTHER value. */ DebuggerPDP11.OP_DSTREG = PDP11.OPREG.MASK; DebuggerPDP11.OP_DSTMODE = PDP11.OPMODE.MASK; DebuggerPDP11.OP_DST = (DebuggerPDP11.OP_DSTMODE | DebuggerPDP11.OP_DSTREG); DebuggerPDP11.OP_SRCREG = PDP11.OPREG.MASK << 6; DebuggerPDP11.OP_SRCMODE = PDP11.OPMODE.MASK << 6; DebuggerPDP11.OP_SRC = (DebuggerPDP11.OP_SRCMODE | DebuggerPDP11.OP_SRCREG); DebuggerPDP11.OP_BRANCH = 0x1000; DebuggerPDP11.OP_DSTOFF = 0x2000; DebuggerPDP11.OP_DSTNUM3 = 0x3000; // DST 3-bit number (ie, just the DSTREG field) DebuggerPDP11.OP_DSTNUM6 = 0x6000; // DST 6-bit number (ie, both the DSTREG and DSTMODE fields) DebuggerPDP11.OP_DSTNUM8 = 0x8000; // DST 8-bit number DebuggerPDP11.OP_OTHER = 0xF000; /* * The OPTABLE contains opcode masks, and each mask refers to table of possible values, and each * value refers to an array that contains: * * [0]: {number} of the opcode name (see OP.*) * [1]: {number} containing the first operand type bit(s), if any * [2]: {number} containing the second operand type bit(s), if any * * Note that, by convention, opcodes that require two operands list the SRC operand first and DST operand * second (ie, the OPPOSITE of the Intel convention). * * Also note that, for some of the newer PDP-11 opcodes (eg, MUL, DIV, ASH, ASHC), the location of the * opcode's SRC and DST bits are reversed. This is why, for example, you'll see the MUL instruction defined * below as having OP_DST for the first operand and OP_SRCREG for the second operand. This does NOT mean * that the opcode's destination operand is being listed first, but rather that the bits describing the source * operand are in the opcode's OP_DST field. */ DebuggerPDP11.OPTABLE = { 0xF000: { 0x1000: [DebuggerPDP11.OPS.MOV, DebuggerPDP11.OP_SRC, DebuggerPDP11.OP_DST], // 01SSDD 0x2000: [DebuggerPDP11.OPS.CMP, DebuggerPDP11.OP_SRC, DebuggerPDP11.OP_DST], // 02SSDD 0x3000: [DebuggerPDP11.OPS.BIT, DebuggerPDP11.OP_SRC, DebuggerPDP11.OP_DST], // 03SSDD 0x4000: [DebuggerPDP11.OPS.BIC, DebuggerPDP11.OP_SRC, DebuggerPDP11.OP_DST], // 04SSDD 0x5000: [DebuggerPDP11.OPS.BIS, DebuggerPDP11.OP_SRC, DebuggerPDP11.OP_DST], // 05SSDD 0x6000: [DebuggerPDP11.OPS.ADD, DebuggerPDP11.OP_SRC, DebuggerPDP11.OP_DST], // 06SSDD 0x9000: [DebuggerPDP11.OPS.MOVB, DebuggerPDP11.OP_SRC, DebuggerPDP11.OP_DST], // 11SSDD 0xA000: [DebuggerPDP11.OPS.CMPB, DebuggerPDP11.OP_SRC, DebuggerPDP11.OP_DST], // 12SSDD 0xB000: [DebuggerPDP11.OPS.BITB, DebuggerPDP11.OP_SRC, DebuggerPDP11.OP_DST], // 13SSDD 0xC000: [DebuggerPDP11.OPS.BICB, DebuggerPDP11.OP_SRC, DebuggerPDP11.OP_DST], // 14SSDD 0xD000: [DebuggerPDP11.OPS.BISB, DebuggerPDP11.OP_SRC, DebuggerPDP11.OP_DST], // 15SSDD 0xE000: [DebuggerPDP11.OPS.SUB, DebuggerPDP11.OP_SRC, DebuggerPDP11.OP_DST] // 16SSDD }, 0xFE00: { 0x0800: [DebuggerPDP11.OPS.JSR, DebuggerPDP11.OP_SRCREG, DebuggerPDP11.OP_DST], // 004RDD 0x7000: [DebuggerPDP11.OPS.MUL, DebuggerPDP11.OP_DST, DebuggerPDP11.OP_SRCREG], // 070RSS 0x7200: [DebuggerPDP11.OPS.DIV, DebuggerPDP11.OP_DST, DebuggerPDP11.OP_SRCREG], // 071RSS 0x7400: [DebuggerPDP11.OPS.ASH, DebuggerPDP11.OP_DST, DebuggerPDP11.OP_SRCREG], // 072RSS 0x7600: [DebuggerPDP11.OPS.ASHC, DebuggerPDP11.OP_DST, DebuggerPDP11.OP_SRCREG], // 073RSS 0x7800: [DebuggerPDP11.OPS.XOR, DebuggerPDP11.OP_SRCREG, DebuggerPDP11.OP_DST], // 074RDD 0x7E00: [DebuggerPDP11.OPS.SOB, DebuggerPDP11.OP_SRCREG, DebuggerPDP11.OP_DSTOFF] // 077Rnn }, 0xFF00: { 0x0100: [DebuggerPDP11.OPS.BR, DebuggerPDP11.OP_BRANCH], 0x0200: [DebuggerPDP11.OPS.BNE, DebuggerPDP11.OP_BRANCH], 0x0300: [DebuggerPDP11.OPS.BEQ, DebuggerPDP11.OP_BRANCH], 0x0400: [DebuggerPDP11.OPS.BGE, DebuggerPDP11.OP_BRANCH], 0x0500: [DebuggerPDP11.OPS.BLT, DebuggerPDP11.OP_BRANCH], 0x0600: [DebuggerPDP11.OPS.BGT, DebuggerPDP11.OP_BRANCH], 0x0700: [DebuggerPDP11.OPS.BLE, DebuggerPDP11.OP_BRANCH], 0x8000: [DebuggerPDP11.OPS.BPL, DebuggerPDP11.OP_BRANCH], 0x8100: [DebuggerPDP11.OPS.BMI, DebuggerPDP11.OP_BRANCH], 0x8200: [DebuggerPDP11.OPS.BHI, DebuggerPDP11.OP_BRANCH], 0x8300: [DebuggerPDP11.OPS.BLOS, DebuggerPDP11.OP_BRANCH], 0x8400: [DebuggerPDP11.OPS.BVC, DebuggerPDP11.OP_BRANCH], 0x8500: [DebuggerPDP11.OPS.BVS, DebuggerPDP11.OP_BRANCH], 0x8600: [DebuggerPDP11.OPS.BCC, DebuggerPDP11.OP_BRANCH], 0x8700: [DebuggerPDP11.OPS.BCS, DebuggerPDP11.OP_BRANCH], 0x8800: [DebuggerPDP11.OPS.EMT, DebuggerPDP11.OP_DSTNUM8], // 104000..104377 0x8900: [DebuggerPDP11.OPS.TRAP, DebuggerPDP11.OP_DSTNUM8] // 104400..104777 }, 0xFFC0: { 0x0040: [DebuggerPDP11.OPS.JMP, DebuggerPDP11.OP_DST], // 0001DD 0x00C0: [DebuggerPDP11.OPS.SWAB, DebuggerPDP11.OP_DST], // 0003DD 0x0A00: [DebuggerPDP11.OPS.CLR, DebuggerPDP11.OP_DST], // 0050DD 0x0A40: [DebuggerPDP11.OPS.COM, DebuggerPDP11.OP_DST], // 0051DD 0x0A80: [DebuggerPDP11.OPS.INC, DebuggerPDP11.OP_DST], // 0052DD 0x0AC0: [DebuggerPDP11.OPS.DEC, DebuggerPDP11.OP_DST], // 0053DD 0x0B00: [DebuggerPDP11.OPS.NEG, DebuggerPDP11.OP_DST], // 0054DD 0x0B40: [DebuggerPDP11.OPS.ADC, DebuggerPDP11.OP_DST], // 0055DD 0x0B80: [DebuggerPDP11.OPS.SBC, DebuggerPDP11.OP_DST], // 0056DD 0x0BC0: [DebuggerPDP11.OPS.TST, DebuggerPDP11.OP_DST], // 0057DD 0x0C00: [DebuggerPDP11.OPS.ROR, DebuggerPDP11.OP_DST], // 0060DD 0x0C40: [DebuggerPDP11.OPS.ROL, DebuggerPDP11.OP_DST], // 0061DD 0x0C80: [DebuggerPDP11.OPS.ASR, DebuggerPDP11.OP_DST], // 0062DD 0x0CC0: [DebuggerPDP11.OPS.ASL, DebuggerPDP11.OP_DST], // 0063DD 0x0D00: [DebuggerPDP11.OPS.MARK, DebuggerPDP11.OP_DSTNUM6], // 0064nn 0x0D40: [DebuggerPDP11.OPS.MFPI, DebuggerPDP11.OP_DST], // 0065SS 0x0D80: [DebuggerPDP11.OPS.MTPI, DebuggerPDP11.OP_DST], // 0066DD 0x0DC0: [DebuggerPDP11.OPS.SXT, DebuggerPDP11.OP_DST], // 0067DD 0x8A00: [DebuggerPDP11.OPS.CLRB, DebuggerPDP11.OP_DST], // 1050DD 0x8A40: [DebuggerPDP11.OPS.COMB, DebuggerPDP11.OP_DST], // 1051DD 0x8A80: [DebuggerPDP11.OPS.INCB, DebuggerPDP11.OP_DST], // 1052DD 0x8AC0: [DebuggerPDP11.OPS.DECB, DebuggerPDP11.OP_DST], // 1053DD 0x8B00: [DebuggerPDP11.OPS.NEGB, DebuggerPDP11.OP_DST], // 1054DD 0x8B40: [DebuggerPDP11.OPS.ADCB, DebuggerPDP11.OP_DST], // 1055DD 0x8B80: [DebuggerPDP11.OPS.SBCB, DebuggerPDP11.OP_DST], // 1056DD 0x8BC0: [DebuggerPDP11.OPS.TSTB, DebuggerPDP11.OP_DST], // 1057DD 0x8C00: [DebuggerPDP11.OPS.RORB, DebuggerPDP11.OP_DST], // 1060DD 0x8C40: [DebuggerPDP11.OPS.ROLB, DebuggerPDP11.OP_DST], // 1061DD 0x8C80: [DebuggerPDP11.OPS.ASRB, DebuggerPDP11.OP_DST], // 1062DD 0x8CC0: [DebuggerPDP11.OPS.ASLB, DebuggerPDP11.OP_DST], // 1063DD 0x8D00: [DebuggerPDP11.OPS.MTPS, DebuggerPDP11.OP_DST], // 1064SS (only on LSI-11) 0x8D40: [DebuggerPDP11.OPS.MFPD, DebuggerPDP11.OP_DST], // 1065DD (same as MFPI if no separate instruction/data spaces) 0x8D80: [DebuggerPDP11.OPS.MTPD, DebuggerPDP11.OP_DST], // 1066DD (same as MTPI if no separate instruction/data spaces) 0x8DC0: [DebuggerPDP11.OPS.MFPS, DebuggerPDP11.OP_DST] // 1067SS (only on LSI-11) }, 0xFFF8: { 0x0080: [DebuggerPDP11.OPS.RTS, DebuggerPDP11.OP_DSTREG], // 00020R 0x0098: [DebuggerPDP11.OPS.SPL, DebuggerPDP11.OP_DSTNUM3] // 00023N }, 0xFFFF: { 0x0000: [DebuggerPDP11.OPS.HALT], // 000000 0x0001: [DebuggerPDP11.OPS.WAIT], // 000001 0x0002: [DebuggerPDP11.OPS.RTI], // 000002 0x0003: [DebuggerPDP11.OPS.BPT], // 000003 0x0004: [DebuggerPDP11.OPS.IOT], // 000004 0x0005: [DebuggerPDP11.OPS.RESET], // 000005 0x0006: [DebuggerPDP11.OPS.RTT], // 000006 0x0007: [DebuggerPDP11.OPS.MFPT], // 000007 (only on PDP-11/44 & KB11-EM?) 0x00A0: [DebuggerPDP11.OPS.NOP], 0x00A1: [DebuggerPDP11.OPS.CLC], 0x00A2: [DebuggerPDP11.OPS.CLV], 0x00A3: [DebuggerPDP11.OPS.CLCV], 0x00A4: [DebuggerPDP11.OPS.CLZ], 0x00A5: [DebuggerPDP11.OPS.CLCZ], 0x00A6: [DebuggerPDP11.OPS.CLVZ], 0x00A7: [DebuggerPDP11.OPS.CLCVZ], 0x00A8: [DebuggerPDP11.OPS.CLN], 0x00A9: [DebuggerPDP11.OPS.CLCN], 0x00AA: [DebuggerPDP11.OPS.CLVN], 0x00AB: [DebuggerPDP11.OPS.CLCVN], 0x00AC: [DebuggerPDP11.OPS.CLZN], 0x00AD: [DebuggerPDP11.OPS.CLCZN], 0x00AE: [DebuggerPDP11.OPS.CLVZN], 0x00AF: [DebuggerPDP11.OPS.CCC], // aka CLCVZN 0x00B0: [DebuggerPDP11.OPS.NOP], 0x00B1: [DebuggerPDP11.OPS.SEC], 0x00B2: [DebuggerPDP11.OPS.SEV], 0x00B3: [DebuggerPDP11.OPS.SECV], 0x00B4: [DebuggerPDP11.OPS.SEZ], 0x00B5: [DebuggerPDP11.OPS.SECZ], 0x00B6: [DebuggerPDP11.OPS.SEVZ], 0x00B7: [DebuggerPDP11.OPS.SECVZ], 0x00B8: [DebuggerPDP11.OPS.SEN], 0x00B9: [DebuggerPDP11.OPS.SECN], 0x00BA: [DebuggerPDP11.OPS.SEVN], 0x00BB: [DebuggerPDP11.OPS.SECVN], 0x00BC: [DebuggerPDP11.OPS.SEZN], 0x00BD: [DebuggerPDP11.OPS.SECZN], 0x00BE: [DebuggerPDP11.OPS.SEVZN], 0x00BF: [DebuggerPDP11.OPS.SCC] // aka SECVZN } }; DebuggerPDP11.OPNONE = [DebuggerPDP11.OPS.NONE]; /* * Table of opcodes added to the 11/40 and newer */ DebuggerPDP11.OP1140 = [ DebuggerPDP11.OPS.MARK, DebuggerPDP11.OPS.MFPI, DebuggerPDP11.OPS.MTPI, DebuggerPDP11.OPS.SXT, DebuggerPDP11.OPS.RTT, DebuggerPDP11.OPS.MUL, DebuggerPDP11.OPS.DIV, DebuggerPDP11.OPS.ASH, DebuggerPDP11.OPS.ASHC, DebuggerPDP11.OPS.XOR, DebuggerPDP11.OPS.SOB ]; /* * Table of opcodes added to the 11/45 and newer */ DebuggerPDP11.OP1145 = [ DebuggerPDP11.OPS.SPL, DebuggerPDP11.OPS.MFPD, DebuggerPDP11.OPS.MTPD ]; DebuggerPDP11.HISTORY_LIMIT = DEBUG? 100000 : 1000; DebuggerPDP11.PROMPT = ">> "; /* * Initialize every Debugger module on the page (as IF there's ever going to be more than one ;-)) */ Web.onInit(DebuggerPDP11.init); } // endif DEBUGGER if (NODE) module.exports = DebuggerPDP11;