Also, each PCjs debugger can now be independently accessed from an external debugger (eg, "pcx86('ver')" or "pc8080('ver')"); the global '$' symbol is no longer used for debugger access.
TODO: There are still many places where GLOBALVAR should be replaced with PCX86.GLOBALVAR or PC8080.GLOBALVAR as appropriate (which I've finally started doing in each debugger.js file).
4838 lines
190 KiB
JavaScript
4838 lines
190 KiB
JavaScript
/**
|
|
* @fileoverview Implements the PC8080 Debugger component.
|
|
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
|
* @version 1.0
|
|
* Created 2016-Apr-18
|
|
*
|
|
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
|
|
*
|
|
* This file is part of PCjs, a computer emulation software project at <http://pcjs.org/>.
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/gpl.html>.
|
|
*
|
|
* You are required to include the above copyright notice in every source code file of every
|
|
* copy or modified version of this work, and to display that copyright notice on every screen
|
|
* that loads or runs any version of this software (see COPYRIGHT in /modules/shared/lib/defines.js).
|
|
*
|
|
* 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 (DEBUGGER) {
|
|
if (NODE) {
|
|
var str = require("../../shared/lib/strlib");
|
|
var usr = require("../../shared/lib/usrlib");
|
|
var web = require("../../shared/lib/weblib");
|
|
var Component = require("../../shared/lib/component");
|
|
var State = require("../../shared/lib/state");
|
|
var PC8080 = require("./defines");
|
|
var CPUDef8080 = require("./cpudef");
|
|
var CPU8080 = require("./cpu");
|
|
var Keyboard8080= require("./keyboard");
|
|
var Messages8080= require("./messages");
|
|
var Memory8080 = require("./memory");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Debugger8080 Address Object
|
|
*
|
|
* addr address
|
|
* fTemporary true if this is a temporary breakpoint address
|
|
* sCmd set for breakpoint addresses if there's an associated command string
|
|
* aCmds preprocessed commands (from sCmd)
|
|
*
|
|
* @typedef {{
|
|
* addr:(number|undefined),
|
|
* fTemporary:(boolean|undefined),
|
|
* sCmd:(string|undefined),
|
|
* aCmds:(Array.<string>|undefined)
|
|
* }}
|
|
*/
|
|
var DbgAddr8080;
|
|
|
|
/**
|
|
* Debugger8080(parmsDbg)
|
|
*
|
|
* @constructor
|
|
* @extends Component
|
|
* @param {Object} parmsDbg
|
|
*
|
|
* The Debugger8080 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 Debugger8080 component is an optional component that implements a variety of user
|
|
* commands for controlling the CPU, dumping and editing memory, etc.
|
|
*/
|
|
function Debugger8080(parmsDbg)
|
|
{
|
|
if (DEBUGGER) {
|
|
|
|
Component.call(this, "Debugger", parmsDbg, Debugger8080);
|
|
|
|
this.style = Debugger8080.STYLE_8080;
|
|
|
|
/*
|
|
* These keep track of instruction activity, but only when tracing or when Debugger8080 checks
|
|
* have been enabled (eg, one or more breakpoints have been set).
|
|
*
|
|
* They are zeroed by the reset() notification handler. cInstructions is advanced by
|
|
* stepCPU() and checkInstruction() calls. nCycles is updated by every stepCPU() or stop()
|
|
* call and simply represents the number of cycles performed by the last run of instructions.
|
|
*/
|
|
this.nCycles = 0;
|
|
this.cOpcodes = this.cOpcodesStart = 0;
|
|
|
|
/*
|
|
* 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 maintains command history. New commands are inserted at index 0 of the array.
|
|
* When Enter is pressed on an empty input buffer, we default to the command at aPrevCmds[0].
|
|
*/
|
|
this.iPrevCmd = -1;
|
|
this.aPrevCmds = [];
|
|
|
|
/*
|
|
* fAssemble is true when "assemble mode" is active, false when not.
|
|
*/
|
|
this.fAssemble = false;
|
|
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 = [];
|
|
|
|
/*
|
|
* aVariables is an object with properties that grows as setVariable() assigns more variables;
|
|
* each property corresponds to one variable, where the property name is the variable name (ie,
|
|
* a string beginning with a letter or underscore, followed by zero or more additional letters,
|
|
* digits, or underscores) and the property value is the variable's numeric value. See doVar()
|
|
* and setVariable() for details.
|
|
*
|
|
* Note that parseValue(), through its reliance on str.parseInt(), assumes a default base of 16
|
|
* if no base is explicitly indicated (eg, a trailing decimal period), and if you define variable
|
|
* names containing exclusively hex alpha characters (a-f), those variables will take precedence
|
|
* over the corresponding hex values. In other words, if you define variables "a" and "b", you
|
|
* will no longer be able to simply type "a" or "b" to specify the decimal values 10 or 11.
|
|
*/
|
|
this.aVariables = {};
|
|
|
|
/*
|
|
* 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.nBreakIns = 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.historyInit();
|
|
|
|
/*
|
|
* Initialize Debugger8080 message support
|
|
*/
|
|
this.afnDumpers = [];
|
|
this.messageInit(parmsDbg['messages']);
|
|
|
|
this.sInitCommands = parmsDbg['commands'];
|
|
|
|
/*
|
|
* Make it easier to access Debugger8080 commands from an external REPL (eg, the WebStorm
|
|
* "live" console window); eg:
|
|
*
|
|
* pc8080('r')
|
|
* pc8080('dw 0:0')
|
|
* pc8080('h')
|
|
* ...
|
|
*/
|
|
var dbg = this;
|
|
if (window) {
|
|
if (window[PC8080.APPCLASS] === undefined) {
|
|
window[PC8080.APPCLASS] = function(s) { return dbg.doCommands(s); };
|
|
}
|
|
} else {
|
|
if (global[PC8080.APPCLASS] === undefined) {
|
|
global[PC8080.APPCLASS] = function(s) { return dbg.doCommands(s); };
|
|
}
|
|
}
|
|
|
|
} // endif DEBUGGER
|
|
}
|
|
|
|
if (DEBUGGER) {
|
|
|
|
Component.subclass(Debugger8080);
|
|
|
|
/*
|
|
* NOTE: Every Debugger8080 property from here to the first prototype function definition (initBus()) is a
|
|
* 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....
|
|
*
|
|
* Bugs can slip through the cracks without those annotations; for example, I unthinkingly redefined TYPE_SI
|
|
* at one point, and if all the definitions had been preceded by an "@const", that mistake would have been
|
|
* caught at compile-time.
|
|
*/
|
|
|
|
Debugger8080.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",
|
|
'f': "frequencies",
|
|
'g [#]': "go [to #]",
|
|
'h': "halt",
|
|
'i [#]': "input port #",
|
|
'if': "eval expression",
|
|
'int [#]': "request interrupt",
|
|
'k': "stack trace",
|
|
"ln": "list nearest symbol(s)",
|
|
'm': "messages",
|
|
'o [#]': "output port #",
|
|
'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",
|
|
'v': "print version",
|
|
'var': "assign variable"
|
|
};
|
|
|
|
Debugger8080.STYLE_8080 = 8080;
|
|
Debugger8080.STYLE_8086 = 8086;
|
|
|
|
/*
|
|
* CPU instruction ordinals
|
|
*/
|
|
Debugger8080.INS = {
|
|
NONE: 0, ACI: 1, ADC: 2, ADD: 3, ADI: 4, ANA: 5, ANI: 6, CALL: 7,
|
|
CC: 8, CM: 9, CNC: 10, CNZ: 11, CP: 12, CPE: 13, CPO: 14, CZ: 15,
|
|
CMA: 16, CMC: 17, CMP: 18, CPI: 19, DAA: 20, DAD: 21, DCR: 22, DCX: 23,
|
|
DI: 24, EI: 25, HLT: 26, IN: 27, INR: 28, INX: 29, JMP: 30, JC: 31,
|
|
JM: 32, JNC: 33, JNZ: 34, JP: 35, JPE: 36, JPO: 37, JZ: 38, LDA: 39,
|
|
LDAX: 40, LHLD: 41, LXI: 42, MOV: 43, MVI: 44, NOP: 45, ORA: 46, ORI: 47,
|
|
OUT: 48, PCHL: 49, POP: 50, PUSH: 51, RAL: 52, RAR: 53, RET: 54, RC: 55,
|
|
RM: 56, RNC: 57, RNZ: 58, RP: 59, RPE: 60, RPO: 61, RZ: 62, RLC: 63,
|
|
RRC: 64, RST: 65, SBB: 66, SBI: 67, SHLD: 68, SPHL: 69, STA: 70, STAX: 71,
|
|
STC: 72, SUB: 73, SUI: 74, XCHG: 75, XRA: 76, XRI: 77, XTHL: 78
|
|
};
|
|
|
|
/*
|
|
* CPU instruction names (mnemonics), indexed by CPU instruction ordinal (above)
|
|
*
|
|
* If you change the default style, using the "s" command (eg, "s 8086"), then the 8086 table
|
|
* will be used instead. TODO: Add a "s z80" command for Z80-style mnemonics.
|
|
*/
|
|
Debugger8080.INS_NAMES = [
|
|
"NONE", "ACI", "ADC", "ADD", "ADI", "ANA", "ANI", "CALL",
|
|
"CC", "CM", "CNC", "CNZ", "CP", "CPE", "CPO", "CZ",
|
|
"CMA", "CMC", "CMP", "CPI", "DAA", "DAD", "DCR", "DCX",
|
|
"DI", "EI", "HLT", "IN", "INR", "INX", "JMP", "JC",
|
|
"JM", "JNC", "JNZ", "JP", "JPE", "JPO", "JZ", "LDA",
|
|
"LDAX", "LHLD", "LXI", "MOV", "MVI", "NOP", "ORA", "ORI",
|
|
"OUT", "PCHL", "POP", "PUSH", "RAL", "RAR", "RET", "RC",
|
|
"RM", "RNC", "RNZ", "RP", "RPE", "RPO", "RZ", "RLC",
|
|
"RRC", "RST", "SBB", "SBI", "SHLD", "SPHL", "STA", "STAX",
|
|
"STC", "SUB", "SUI", "XCHG", "XRA", "XRI", "XTHL"
|
|
];
|
|
|
|
Debugger8080.INS_NAMES_8086 = [
|
|
"NONE", "ADC", "ADC", "ADD", "ADD", "AND", "AND", "CALL",
|
|
"CALLC", "CALLS", "CALLNC", "CALLNZ", "CALLNS", "CALLP", "CALLNP", "CALLZ",
|
|
"NOT", "CMC", "CMP", "CMP", "DAA", "ADD", "DEC", "DEC",
|
|
"CLI", "STI", "HLT", "IN", "INC", "INC", "JMP", "JC",
|
|
"JS", "JNC", "JNZ", "JNS", "JP", "JNP", "JZ", "MOV",
|
|
"MOV", "MOV", "MOV", "MOV", "MOV", "NOP", "OR", "OR",
|
|
"OUT", "JMP", "POP", "PUSH", "RCL", "RCR", "RET", "RETC",
|
|
"RETS", "RETNC", "RETNZ", "RETNS", "RETP", "RETNP", "RETZ", "ROL",
|
|
"ROR", "RST", "SBB", "SBB", "MOV", "MOV", "MOV", "MOV",
|
|
"STC", "SUB", "SUB", "XCHG", "XOR", "XOR", "XCHG"
|
|
];
|
|
|
|
Debugger8080.REG_B = 0x00;
|
|
Debugger8080.REG_C = 0x01;
|
|
Debugger8080.REG_D = 0x02;
|
|
Debugger8080.REG_E = 0x03;
|
|
Debugger8080.REG_H = 0x04;
|
|
Debugger8080.REG_L = 0x05;
|
|
Debugger8080.REG_M = 0x06;
|
|
Debugger8080.REG_A = 0x07;
|
|
Debugger8080.REG_BC = 0x08;
|
|
Debugger8080.REG_DE = 0x09;
|
|
Debugger8080.REG_HL = 0x0A;
|
|
Debugger8080.REG_SP = 0x0B;
|
|
Debugger8080.REG_PC = 0x0C;
|
|
Debugger8080.REG_PS = 0x0D;
|
|
Debugger8080.REG_PSW = 0x0E; // aka AF if Z80-style mnemonics
|
|
|
|
/*
|
|
* NOTE: "PS" is the complete processor status, which includes bits like the Interrupt flag (IF),
|
|
* which is NOT the same as "PSW", which is the low 8 bits of "PS" combined with "A" in the high byte.
|
|
*/
|
|
Debugger8080.REGS = [
|
|
"B", "C", "D", "E", "H", "L", "M", "A", "BC", "DE", "HL", "SP", "PC", "PS", "PSW"
|
|
];
|
|
|
|
/*
|
|
* Operand type descriptor masks and definitions
|
|
*/
|
|
Debugger8080.TYPE_SIZE = 0x000F; // size field
|
|
Debugger8080.TYPE_MODE = 0x00F0; // mode field
|
|
Debugger8080.TYPE_IREG = 0x0F00; // implied register field
|
|
Debugger8080.TYPE_OTHER = 0xF000; // "other" field
|
|
|
|
/*
|
|
* TYPE_SIZE values
|
|
*/
|
|
Debugger8080.TYPE_NONE = 0x0000; // (all other TYPE fields ignored)
|
|
Debugger8080.TYPE_BYTE = 0x0001; // byte, regardless of operand size
|
|
Debugger8080.TYPE_SBYTE = 0x0002; // byte sign-extended to word
|
|
Debugger8080.TYPE_WORD = 0x0003; // word (16-bit value)
|
|
|
|
/*
|
|
* TYPE_MODE values
|
|
*/
|
|
Debugger8080.TYPE_REG = 0x0010; // register
|
|
Debugger8080.TYPE_IMM = 0x0020; // immediate data
|
|
Debugger8080.TYPE_ADDR = 0x0033; // immediate (word) address
|
|
Debugger8080.TYPE_MEM = 0x0040; // memory reference
|
|
Debugger8080.TYPE_INT = 0x0080; // interrupt level encoded in instruction (bits 3-5)
|
|
|
|
/*
|
|
* TYPE_IREG values, based on the REG_* constants.
|
|
*
|
|
* NOte that TYPE_M isn't really a register, just an alternative form of TYPE_HL | TYPE_MEM.
|
|
*/
|
|
Debugger8080.TYPE_A = (Debugger8080.REG_A << 8 | Debugger8080.TYPE_REG | Debugger8080.TYPE_BYTE);
|
|
Debugger8080.TYPE_B = (Debugger8080.REG_B << 8 | Debugger8080.TYPE_REG | Debugger8080.TYPE_BYTE);
|
|
Debugger8080.TYPE_C = (Debugger8080.REG_C << 8 | Debugger8080.TYPE_REG | Debugger8080.TYPE_BYTE);
|
|
Debugger8080.TYPE_D = (Debugger8080.REG_D << 8 | Debugger8080.TYPE_REG | Debugger8080.TYPE_BYTE);
|
|
Debugger8080.TYPE_E = (Debugger8080.REG_E << 8 | Debugger8080.TYPE_REG | Debugger8080.TYPE_BYTE);
|
|
Debugger8080.TYPE_H = (Debugger8080.REG_H << 8 | Debugger8080.TYPE_REG | Debugger8080.TYPE_BYTE);
|
|
Debugger8080.TYPE_L = (Debugger8080.REG_L << 8 | Debugger8080.TYPE_REG | Debugger8080.TYPE_BYTE);
|
|
Debugger8080.TYPE_M = (Debugger8080.REG_M << 8 | Debugger8080.TYPE_REG | Debugger8080.TYPE_BYTE | Debugger8080.TYPE_MEM);
|
|
Debugger8080.TYPE_BC = (Debugger8080.REG_BC << 8 | Debugger8080.TYPE_REG | Debugger8080.TYPE_WORD);
|
|
Debugger8080.TYPE_DE = (Debugger8080.REG_DE << 8 | Debugger8080.TYPE_REG | Debugger8080.TYPE_WORD);
|
|
Debugger8080.TYPE_HL = (Debugger8080.REG_HL << 8 | Debugger8080.TYPE_REG | Debugger8080.TYPE_WORD);
|
|
Debugger8080.TYPE_SP = (Debugger8080.REG_SP << 8 | Debugger8080.TYPE_REG | Debugger8080.TYPE_WORD);
|
|
Debugger8080.TYPE_PC = (Debugger8080.REG_PC << 8 | Debugger8080.TYPE_REG | Debugger8080.TYPE_WORD);
|
|
Debugger8080.TYPE_PSW = (Debugger8080.REG_PSW<< 8 | Debugger8080.TYPE_REG | Debugger8080.TYPE_WORD);
|
|
|
|
/*
|
|
* TYPE_OTHER bit definitions
|
|
*/
|
|
Debugger8080.TYPE_IN = 0x1000; // operand is input
|
|
Debugger8080.TYPE_OUT = 0x2000; // operand is output
|
|
Debugger8080.TYPE_BOTH = (Debugger8080.TYPE_IN | Debugger8080.TYPE_OUT);
|
|
Debugger8080.TYPE_OPT = 0x4000; // optional operand (ie, normally omitted in 8080 assembly language)
|
|
Debugger8080.TYPE_UNDOC = 0x8000; // opcode is an undocumented alternative encoding
|
|
|
|
/*
|
|
* The aaOpDescs array is indexed by opcode, and each element is a sub-array (aOpDesc) that describes
|
|
* the corresponding opcode. The sub-elements are as follows:
|
|
*
|
|
* [0]: {number} of the opcode name (see INS.*)
|
|
* [1]: {number} containing the destination operand descriptor bit(s), if any
|
|
* [2]: {number} containing the source operand descriptor bit(s), if any
|
|
* [3]: {number} containing the occasional third operand descriptor bit(s), if any
|
|
*
|
|
* These sub-elements are all optional. If [0] is not present, the opcode is undefined; if [1] is not
|
|
* present (or contains zero), the opcode has no (or only implied) operands; if [2] is not present, the
|
|
* opcode has only a single operand. And so on.
|
|
*
|
|
* Additional default rules:
|
|
*
|
|
* 1) If no TYPE_OTHER bits are specified for the first (destination) operand, TYPE_OUT is assumed;
|
|
* 2) If no TYPE_OTHER bits are specified for the second (source) operand, TYPE_IN is assumed;
|
|
* 3) If no size is specified for the second operand, the size is assumed to match the first operand.
|
|
*/
|
|
Debugger8080.aaOpDescs = [
|
|
/* 0x00 */ [Debugger8080.INS.NOP],
|
|
/* 0x01 */ [Debugger8080.INS.LXI, Debugger8080.TYPE_BC, Debugger8080.TYPE_IMM],
|
|
/* 0x02 */ [Debugger8080.INS.STAX, Debugger8080.TYPE_BC | Debugger8080.TYPE_MEM, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT],
|
|
/* 0x03 */ [Debugger8080.INS.INX, Debugger8080.TYPE_BC],
|
|
/* 0x04 */ [Debugger8080.INS.INR, Debugger8080.TYPE_B],
|
|
/* 0x05 */ [Debugger8080.INS.DCR, Debugger8080.TYPE_B],
|
|
/* 0x06 */ [Debugger8080.INS.MVI, Debugger8080.TYPE_B, Debugger8080.TYPE_IMM],
|
|
/* 0x07 */ [Debugger8080.INS.RLC],
|
|
/* 0x08 */ [Debugger8080.INS.NOP, Debugger8080.TYPE_UNDOC],
|
|
/* 0x09 */ [Debugger8080.INS.DAD, Debugger8080.TYPE_HL | Debugger8080.TYPE_OPT, Debugger8080.TYPE_BC],
|
|
/* 0x0A */ [Debugger8080.INS.LDAX, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_BC | Debugger8080.TYPE_MEM],
|
|
/* 0x0B */ [Debugger8080.INS.DCX, Debugger8080.TYPE_BC],
|
|
/* 0x0C */ [Debugger8080.INS.INR, Debugger8080.TYPE_C],
|
|
/* 0x0D */ [Debugger8080.INS.DCR, Debugger8080.TYPE_C],
|
|
/* 0x0E */ [Debugger8080.INS.MVI, Debugger8080.TYPE_C, Debugger8080.TYPE_IMM],
|
|
/* 0x0F */ [Debugger8080.INS.RRC],
|
|
/* 0x10 */ [Debugger8080.INS.NOP, Debugger8080.TYPE_UNDOC],
|
|
/* 0x11 */ [Debugger8080.INS.LXI, Debugger8080.TYPE_DE, Debugger8080.TYPE_IMM],
|
|
/* 0x12 */ [Debugger8080.INS.STAX, Debugger8080.TYPE_DE | Debugger8080.TYPE_MEM, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT],
|
|
/* 0x13 */ [Debugger8080.INS.INX, Debugger8080.TYPE_DE],
|
|
/* 0x14 */ [Debugger8080.INS.INR, Debugger8080.TYPE_D],
|
|
/* 0x15 */ [Debugger8080.INS.DCR, Debugger8080.TYPE_D],
|
|
/* 0x16 */ [Debugger8080.INS.MVI, Debugger8080.TYPE_D, Debugger8080.TYPE_IMM],
|
|
/* 0x17 */ [Debugger8080.INS.RAL],
|
|
/* 0x18 */ [Debugger8080.INS.NOP, Debugger8080.TYPE_UNDOC],
|
|
/* 0x19 */ [Debugger8080.INS.DAD, Debugger8080.TYPE_HL | Debugger8080.TYPE_OPT, Debugger8080.TYPE_DE],
|
|
/* 0x1A */ [Debugger8080.INS.LDAX, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_DE | Debugger8080.TYPE_MEM],
|
|
/* 0x1B */ [Debugger8080.INS.DCX, Debugger8080.TYPE_DE],
|
|
/* 0x1C */ [Debugger8080.INS.INR, Debugger8080.TYPE_E],
|
|
/* 0x1D */ [Debugger8080.INS.DCR, Debugger8080.TYPE_E],
|
|
/* 0x1E */ [Debugger8080.INS.MVI, Debugger8080.TYPE_E, Debugger8080.TYPE_IMM],
|
|
/* 0x1F */ [Debugger8080.INS.RAR],
|
|
/* 0x20 */ [Debugger8080.INS.NOP, Debugger8080.TYPE_UNDOC],
|
|
/* 0x21 */ [Debugger8080.INS.LXI, Debugger8080.TYPE_HL, Debugger8080.TYPE_IMM],
|
|
/* 0x22 */ [Debugger8080.INS.SHLD, Debugger8080.TYPE_ADDR | Debugger8080.TYPE_MEM, Debugger8080.TYPE_HL | Debugger8080.TYPE_OPT],
|
|
/* 0x23 */ [Debugger8080.INS.INX, Debugger8080.TYPE_HL],
|
|
/* 0x24 */ [Debugger8080.INS.INR, Debugger8080.TYPE_H],
|
|
/* 0x25 */ [Debugger8080.INS.DCR, Debugger8080.TYPE_H],
|
|
/* 0x26 */ [Debugger8080.INS.MVI, Debugger8080.TYPE_H, Debugger8080.TYPE_IMM],
|
|
/* 0x27 */ [Debugger8080.INS.DAA],
|
|
/* 0x28 */ [Debugger8080.INS.NOP, Debugger8080.TYPE_UNDOC],
|
|
/* 0x29 */ [Debugger8080.INS.DAD, Debugger8080.TYPE_HL | Debugger8080.TYPE_OPT, Debugger8080.TYPE_HL],
|
|
/* 0x2A */ [Debugger8080.INS.LHLD, Debugger8080.TYPE_HL | Debugger8080.TYPE_OPT, Debugger8080.TYPE_ADDR | Debugger8080.TYPE_MEM],
|
|
/* 0x2B */ [Debugger8080.INS.DCX, Debugger8080.TYPE_HL],
|
|
/* 0x2C */ [Debugger8080.INS.INR, Debugger8080.TYPE_L],
|
|
/* 0x2D */ [Debugger8080.INS.DCR, Debugger8080.TYPE_L],
|
|
/* 0x2E */ [Debugger8080.INS.MVI, Debugger8080.TYPE_L, Debugger8080.TYPE_IMM],
|
|
/* 0x2F */ [Debugger8080.INS.CMA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT],
|
|
/* 0x30 */ [Debugger8080.INS.NOP, Debugger8080.TYPE_UNDOC],
|
|
/* 0x31 */ [Debugger8080.INS.LXI, Debugger8080.TYPE_SP, Debugger8080.TYPE_IMM],
|
|
/* 0x32 */ [Debugger8080.INS.STA, Debugger8080.TYPE_ADDR | Debugger8080.TYPE_MEM, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT],
|
|
/* 0x33 */ [Debugger8080.INS.INX, Debugger8080.TYPE_SP],
|
|
/* 0x34 */ [Debugger8080.INS.INR, Debugger8080.TYPE_M],
|
|
/* 0x35 */ [Debugger8080.INS.DCR, Debugger8080.TYPE_M],
|
|
/* 0x36 */ [Debugger8080.INS.MVI, Debugger8080.TYPE_M, Debugger8080.TYPE_IMM],
|
|
/* 0x37 */ [Debugger8080.INS.STC],
|
|
/* 0x38 */ [Debugger8080.INS.NOP, Debugger8080.TYPE_UNDOC],
|
|
/* 0x39 */ [Debugger8080.INS.DAD, Debugger8080.TYPE_HL | Debugger8080.TYPE_OPT, Debugger8080.TYPE_SP],
|
|
/* 0x3A */ [Debugger8080.INS.LDA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_ADDR | Debugger8080.TYPE_MEM],
|
|
/* 0x3B */ [Debugger8080.INS.DCX, Debugger8080.TYPE_SP],
|
|
/* 0x3C */ [Debugger8080.INS.INR, Debugger8080.TYPE_A],
|
|
/* 0x3D */ [Debugger8080.INS.DCR, Debugger8080.TYPE_A],
|
|
/* 0x3E */ [Debugger8080.INS.MVI, Debugger8080.TYPE_A, Debugger8080.TYPE_IMM],
|
|
/* 0x3F */ [Debugger8080.INS.CMC],
|
|
/* 0x40 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_B, Debugger8080.TYPE_B],
|
|
/* 0x41 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_B, Debugger8080.TYPE_C],
|
|
/* 0x42 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_B, Debugger8080.TYPE_D],
|
|
/* 0x43 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_B, Debugger8080.TYPE_E],
|
|
/* 0x44 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_B, Debugger8080.TYPE_H],
|
|
/* 0x45 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_B, Debugger8080.TYPE_L],
|
|
/* 0x46 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_B, Debugger8080.TYPE_M],
|
|
/* 0x47 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_B, Debugger8080.TYPE_A],
|
|
/* 0x48 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_C, Debugger8080.TYPE_B],
|
|
/* 0x49 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_C, Debugger8080.TYPE_C],
|
|
/* 0x4A */ [Debugger8080.INS.MOV, Debugger8080.TYPE_C, Debugger8080.TYPE_D],
|
|
/* 0x4B */ [Debugger8080.INS.MOV, Debugger8080.TYPE_C, Debugger8080.TYPE_E],
|
|
/* 0x4C */ [Debugger8080.INS.MOV, Debugger8080.TYPE_C, Debugger8080.TYPE_H],
|
|
/* 0x4D */ [Debugger8080.INS.MOV, Debugger8080.TYPE_C, Debugger8080.TYPE_L],
|
|
/* 0x4E */ [Debugger8080.INS.MOV, Debugger8080.TYPE_C, Debugger8080.TYPE_M],
|
|
/* 0x4F */ [Debugger8080.INS.MOV, Debugger8080.TYPE_C, Debugger8080.TYPE_A],
|
|
/* 0x50 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_D, Debugger8080.TYPE_B],
|
|
/* 0x51 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_D, Debugger8080.TYPE_C],
|
|
/* 0x52 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_D, Debugger8080.TYPE_D],
|
|
/* 0x53 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_D, Debugger8080.TYPE_E],
|
|
/* 0x54 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_D, Debugger8080.TYPE_H],
|
|
/* 0x55 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_D, Debugger8080.TYPE_L],
|
|
/* 0x56 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_D, Debugger8080.TYPE_M],
|
|
/* 0x57 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_D, Debugger8080.TYPE_A],
|
|
/* 0x58 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_E, Debugger8080.TYPE_B],
|
|
/* 0x59 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_E, Debugger8080.TYPE_C],
|
|
/* 0x5A */ [Debugger8080.INS.MOV, Debugger8080.TYPE_E, Debugger8080.TYPE_D],
|
|
/* 0x5B */ [Debugger8080.INS.MOV, Debugger8080.TYPE_E, Debugger8080.TYPE_E],
|
|
/* 0x5C */ [Debugger8080.INS.MOV, Debugger8080.TYPE_E, Debugger8080.TYPE_H],
|
|
/* 0x5D */ [Debugger8080.INS.MOV, Debugger8080.TYPE_E, Debugger8080.TYPE_L],
|
|
/* 0x5E */ [Debugger8080.INS.MOV, Debugger8080.TYPE_E, Debugger8080.TYPE_M],
|
|
/* 0x5F */ [Debugger8080.INS.MOV, Debugger8080.TYPE_E, Debugger8080.TYPE_A],
|
|
/* 0x60 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_H, Debugger8080.TYPE_B],
|
|
/* 0x61 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_H, Debugger8080.TYPE_C],
|
|
/* 0x62 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_H, Debugger8080.TYPE_D],
|
|
/* 0x63 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_H, Debugger8080.TYPE_E],
|
|
/* 0x64 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_H, Debugger8080.TYPE_H],
|
|
/* 0x65 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_H, Debugger8080.TYPE_L],
|
|
/* 0x66 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_H, Debugger8080.TYPE_M],
|
|
/* 0x67 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_H, Debugger8080.TYPE_A],
|
|
/* 0x68 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_L, Debugger8080.TYPE_B],
|
|
/* 0x69 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_L, Debugger8080.TYPE_C],
|
|
/* 0x6A */ [Debugger8080.INS.MOV, Debugger8080.TYPE_L, Debugger8080.TYPE_D],
|
|
/* 0x6B */ [Debugger8080.INS.MOV, Debugger8080.TYPE_L, Debugger8080.TYPE_E],
|
|
/* 0x6C */ [Debugger8080.INS.MOV, Debugger8080.TYPE_L, Debugger8080.TYPE_H],
|
|
/* 0x6D */ [Debugger8080.INS.MOV, Debugger8080.TYPE_L, Debugger8080.TYPE_L],
|
|
/* 0x6E */ [Debugger8080.INS.MOV, Debugger8080.TYPE_L, Debugger8080.TYPE_M],
|
|
/* 0x6F */ [Debugger8080.INS.MOV, Debugger8080.TYPE_L, Debugger8080.TYPE_A],
|
|
/* 0x70 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_M, Debugger8080.TYPE_B],
|
|
/* 0x71 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_M, Debugger8080.TYPE_C],
|
|
/* 0x72 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_M, Debugger8080.TYPE_D],
|
|
/* 0x73 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_M, Debugger8080.TYPE_E],
|
|
/* 0x74 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_M, Debugger8080.TYPE_H],
|
|
/* 0x75 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_M, Debugger8080.TYPE_L],
|
|
/* 0x76 */ [Debugger8080.INS.HLT],
|
|
/* 0x77 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_M, Debugger8080.TYPE_A],
|
|
/* 0x78 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_A, Debugger8080.TYPE_B],
|
|
/* 0x79 */ [Debugger8080.INS.MOV, Debugger8080.TYPE_A, Debugger8080.TYPE_C],
|
|
/* 0x7A */ [Debugger8080.INS.MOV, Debugger8080.TYPE_A, Debugger8080.TYPE_D],
|
|
/* 0x7B */ [Debugger8080.INS.MOV, Debugger8080.TYPE_A, Debugger8080.TYPE_E],
|
|
/* 0x7C */ [Debugger8080.INS.MOV, Debugger8080.TYPE_A, Debugger8080.TYPE_H],
|
|
/* 0x7D */ [Debugger8080.INS.MOV, Debugger8080.TYPE_A, Debugger8080.TYPE_L],
|
|
/* 0x7E */ [Debugger8080.INS.MOV, Debugger8080.TYPE_A, Debugger8080.TYPE_M],
|
|
/* 0x7F */ [Debugger8080.INS.MOV, Debugger8080.TYPE_A, Debugger8080.TYPE_A],
|
|
/* 0x80 */ [Debugger8080.INS.ADD, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_B],
|
|
/* 0x81 */ [Debugger8080.INS.ADD, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_C],
|
|
/* 0x82 */ [Debugger8080.INS.ADD, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_D],
|
|
/* 0x83 */ [Debugger8080.INS.ADD, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_E],
|
|
/* 0x84 */ [Debugger8080.INS.ADD, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_H],
|
|
/* 0x85 */ [Debugger8080.INS.ADD, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_L],
|
|
/* 0x86 */ [Debugger8080.INS.ADD, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_M],
|
|
/* 0x87 */ [Debugger8080.INS.ADD, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_A],
|
|
/* 0x88 */ [Debugger8080.INS.ADC, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_B],
|
|
/* 0x89 */ [Debugger8080.INS.ADC, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_C],
|
|
/* 0x8A */ [Debugger8080.INS.ADC, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_D],
|
|
/* 0x8B */ [Debugger8080.INS.ADC, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_E],
|
|
/* 0x8C */ [Debugger8080.INS.ADC, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_H],
|
|
/* 0x8D */ [Debugger8080.INS.ADC, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_L],
|
|
/* 0x8E */ [Debugger8080.INS.ADC, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_M],
|
|
/* 0x8F */ [Debugger8080.INS.ADC, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_A],
|
|
/* 0x90 */ [Debugger8080.INS.SUB, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_B],
|
|
/* 0x91 */ [Debugger8080.INS.SUB, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_C],
|
|
/* 0x92 */ [Debugger8080.INS.SUB, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_D],
|
|
/* 0x93 */ [Debugger8080.INS.SUB, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_E],
|
|
/* 0x94 */ [Debugger8080.INS.SUB, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_H],
|
|
/* 0x95 */ [Debugger8080.INS.SUB, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_L],
|
|
/* 0x96 */ [Debugger8080.INS.SUB, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_M],
|
|
/* 0x97 */ [Debugger8080.INS.SUB, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_A],
|
|
/* 0x98 */ [Debugger8080.INS.SBB, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_B],
|
|
/* 0x99 */ [Debugger8080.INS.SBB, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_C],
|
|
/* 0x9A */ [Debugger8080.INS.SBB, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_D],
|
|
/* 0x9B */ [Debugger8080.INS.SBB, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_E],
|
|
/* 0x9C */ [Debugger8080.INS.SBB, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_H],
|
|
/* 0x9D */ [Debugger8080.INS.SBB, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_L],
|
|
/* 0x9E */ [Debugger8080.INS.SBB, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_M],
|
|
/* 0x9F */ [Debugger8080.INS.SBB, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_A],
|
|
/* 0xA0 */ [Debugger8080.INS.ANA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_B],
|
|
/* 0xA1 */ [Debugger8080.INS.ANA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_C],
|
|
/* 0xA2 */ [Debugger8080.INS.ANA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_D],
|
|
/* 0xA3 */ [Debugger8080.INS.ANA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_E],
|
|
/* 0xA4 */ [Debugger8080.INS.ANA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_H],
|
|
/* 0xA5 */ [Debugger8080.INS.ANA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_L],
|
|
/* 0xA6 */ [Debugger8080.INS.ANA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_M],
|
|
/* 0xA7 */ [Debugger8080.INS.ANA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_A],
|
|
/* 0xA8 */ [Debugger8080.INS.XRA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_B],
|
|
/* 0xA9 */ [Debugger8080.INS.XRA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_C],
|
|
/* 0xAA */ [Debugger8080.INS.XRA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_D],
|
|
/* 0xAB */ [Debugger8080.INS.XRA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_E],
|
|
/* 0xAC */ [Debugger8080.INS.XRA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_H],
|
|
/* 0xAD */ [Debugger8080.INS.XRA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_L],
|
|
/* 0xAE */ [Debugger8080.INS.XRA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_M],
|
|
/* 0xAF */ [Debugger8080.INS.XRA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_A],
|
|
/* 0xB0 */ [Debugger8080.INS.ORA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_B],
|
|
/* 0xB1 */ [Debugger8080.INS.ORA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_C],
|
|
/* 0xB2 */ [Debugger8080.INS.ORA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_D],
|
|
/* 0xB3 */ [Debugger8080.INS.ORA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_E],
|
|
/* 0xB4 */ [Debugger8080.INS.ORA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_H],
|
|
/* 0xB5 */ [Debugger8080.INS.ORA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_L],
|
|
/* 0xB6 */ [Debugger8080.INS.ORA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_M],
|
|
/* 0xB7 */ [Debugger8080.INS.ORA, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_A],
|
|
/* 0xB8 */ [Debugger8080.INS.CMP, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_B],
|
|
/* 0xB9 */ [Debugger8080.INS.CMP, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_C],
|
|
/* 0xBA */ [Debugger8080.INS.CMP, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_D],
|
|
/* 0xBB */ [Debugger8080.INS.CMP, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_E],
|
|
/* 0xBC */ [Debugger8080.INS.CMP, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_H],
|
|
/* 0xBD */ [Debugger8080.INS.CMP, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_L],
|
|
/* 0xBE */ [Debugger8080.INS.CMP, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_M],
|
|
/* 0xBF */ [Debugger8080.INS.CMP, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_A],
|
|
/* 0xC0 */ [Debugger8080.INS.RNZ],
|
|
/* 0xC1 */ [Debugger8080.INS.POP, Debugger8080.TYPE_BC],
|
|
/* 0xC2 */ [Debugger8080.INS.JNZ, Debugger8080.TYPE_ADDR],
|
|
/* 0xC3 */ [Debugger8080.INS.JMP, Debugger8080.TYPE_ADDR],
|
|
/* 0xC4 */ [Debugger8080.INS.CNZ, Debugger8080.TYPE_ADDR],
|
|
/* 0xC5 */ [Debugger8080.INS.PUSH, Debugger8080.TYPE_BC],
|
|
/* 0xC6 */ [Debugger8080.INS.ADI, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_IMM | Debugger8080.TYPE_BYTE],
|
|
/* 0xC7 */ [Debugger8080.INS.RST, Debugger8080.TYPE_INT],
|
|
/* 0xC8 */ [Debugger8080.INS.RZ],
|
|
/* 0xC9 */ [Debugger8080.INS.RET],
|
|
/* 0xCA */ [Debugger8080.INS.JZ, Debugger8080.TYPE_ADDR],
|
|
/* 0xCB */ [Debugger8080.INS.JMP, Debugger8080.TYPE_ADDR | Debugger8080.TYPE_UNDOC],
|
|
/* 0xCC */ [Debugger8080.INS.CZ, Debugger8080.TYPE_ADDR],
|
|
/* 0xCD */ [Debugger8080.INS.CALL, Debugger8080.TYPE_ADDR],
|
|
/* 0xCE */ [Debugger8080.INS.ACI, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_IMM | Debugger8080.TYPE_BYTE],
|
|
/* 0xCF */ [Debugger8080.INS.RST, Debugger8080.TYPE_INT],
|
|
/* 0xD0 */ [Debugger8080.INS.RNC],
|
|
/* 0xD1 */ [Debugger8080.INS.POP, Debugger8080.TYPE_DE],
|
|
/* 0xD2 */ [Debugger8080.INS.JNC, Debugger8080.TYPE_ADDR],
|
|
/* 0xD3 */ [Debugger8080.INS.OUT, Debugger8080.TYPE_IMM | Debugger8080.TYPE_BYTE,Debugger8080.TYPE_A | Debugger8080.TYPE_OPT],
|
|
/* 0xD4 */ [Debugger8080.INS.CNC, Debugger8080.TYPE_ADDR],
|
|
/* 0xD5 */ [Debugger8080.INS.PUSH, Debugger8080.TYPE_DE],
|
|
/* 0xD6 */ [Debugger8080.INS.SUI, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_IMM | Debugger8080.TYPE_BYTE],
|
|
/* 0xD7 */ [Debugger8080.INS.RST, Debugger8080.TYPE_INT],
|
|
/* 0xD8 */ [Debugger8080.INS.RC],
|
|
/* 0xD9 */ [Debugger8080.INS.RET, Debugger8080.TYPE_UNDOC],
|
|
/* 0xDA */ [Debugger8080.INS.JC, Debugger8080.TYPE_ADDR],
|
|
/* 0xDB */ [Debugger8080.INS.IN, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_IMM | Debugger8080.TYPE_BYTE],
|
|
/* 0xDC */ [Debugger8080.INS.CC, Debugger8080.TYPE_ADDR],
|
|
/* 0xDD */ [Debugger8080.INS.CALL, Debugger8080.TYPE_ADDR | Debugger8080.TYPE_UNDOC],
|
|
/* 0xDE */ [Debugger8080.INS.SBI, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_IMM | Debugger8080.TYPE_BYTE],
|
|
/* 0xDF */ [Debugger8080.INS.RST, Debugger8080.TYPE_INT],
|
|
/* 0xE0 */ [Debugger8080.INS.RPO],
|
|
/* 0xE1 */ [Debugger8080.INS.POP, Debugger8080.TYPE_HL],
|
|
/* 0xE2 */ [Debugger8080.INS.JPO, Debugger8080.TYPE_ADDR],
|
|
/* 0xE3 */ [Debugger8080.INS.XTHL, Debugger8080.TYPE_SP | Debugger8080.TYPE_MEM| Debugger8080.TYPE_OPT, Debugger8080.TYPE_HL | Debugger8080.TYPE_OPT],
|
|
/* 0xE4 */ [Debugger8080.INS.CPO, Debugger8080.TYPE_ADDR],
|
|
/* 0xE5 */ [Debugger8080.INS.PUSH, Debugger8080.TYPE_HL],
|
|
/* 0xE6 */ [Debugger8080.INS.ANI, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_IMM | Debugger8080.TYPE_BYTE],
|
|
/* 0xE7 */ [Debugger8080.INS.RST, Debugger8080.TYPE_INT],
|
|
/* 0xE8 */ [Debugger8080.INS.RPE],
|
|
/* 0xE9 */ [Debugger8080.INS.PCHL, Debugger8080.TYPE_HL],
|
|
/* 0xEA */ [Debugger8080.INS.JPE, Debugger8080.TYPE_ADDR],
|
|
/* 0xEB */ [Debugger8080.INS.XCHG, Debugger8080.TYPE_HL | Debugger8080.TYPE_OPT, Debugger8080.TYPE_DE | Debugger8080.TYPE_OPT],
|
|
/* 0xEC */ [Debugger8080.INS.CPE, Debugger8080.TYPE_ADDR],
|
|
/* 0xED */ [Debugger8080.INS.CALL, Debugger8080.TYPE_ADDR | Debugger8080.TYPE_UNDOC],
|
|
/* 0xEE */ [Debugger8080.INS.XRI, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_IMM | Debugger8080.TYPE_BYTE],
|
|
/* 0xEF */ [Debugger8080.INS.RST, Debugger8080.TYPE_INT],
|
|
/* 0xF0 */ [Debugger8080.INS.RP],
|
|
/* 0xF1 */ [Debugger8080.INS.POP, Debugger8080.TYPE_PSW],
|
|
/* 0xF2 */ [Debugger8080.INS.JP, Debugger8080.TYPE_ADDR],
|
|
/* 0xF3 */ [Debugger8080.INS.DI],
|
|
/* 0xF4 */ [Debugger8080.INS.CP, Debugger8080.TYPE_ADDR],
|
|
/* 0xF5 */ [Debugger8080.INS.PUSH, Debugger8080.TYPE_PSW],
|
|
/* 0xF6 */ [Debugger8080.INS.ORI, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_IMM | Debugger8080.TYPE_BYTE],
|
|
/* 0xF7 */ [Debugger8080.INS.RST, Debugger8080.TYPE_INT],
|
|
/* 0xF8 */ [Debugger8080.INS.RM],
|
|
/* 0xF9 */ [Debugger8080.INS.SPHL, Debugger8080.TYPE_SP | Debugger8080.TYPE_OPT, Debugger8080.TYPE_HL | Debugger8080.TYPE_OPT],
|
|
/* 0xFA */ [Debugger8080.INS.JM, Debugger8080.TYPE_ADDR],
|
|
/* 0xFB */ [Debugger8080.INS.EI],
|
|
/* 0xFC */ [Debugger8080.INS.CM, Debugger8080.TYPE_ADDR],
|
|
/* 0xFD */ [Debugger8080.INS.CALL, Debugger8080.TYPE_ADDR | Debugger8080.TYPE_UNDOC],
|
|
/* 0xFE */ [Debugger8080.INS.CPI, Debugger8080.TYPE_A | Debugger8080.TYPE_OPT, Debugger8080.TYPE_IMM | Debugger8080.TYPE_BYTE],
|
|
/* 0xFF */ [Debugger8080.INS.RST, Debugger8080.TYPE_INT]
|
|
];
|
|
|
|
/*
|
|
* Message categories supported by the messageEnabled() function and other assorted message
|
|
* functions. Each category has a corresponding bit value that can be combined (ie, OR'ed) as
|
|
* needed. The Debugger's message command ("m") is used to turn message categories on and off,
|
|
* like so:
|
|
*
|
|
* m port on
|
|
* m port off
|
|
* ...
|
|
*
|
|
* NOTE: The order of these categories can be rearranged, alphabetized, etc, as desired; just be
|
|
* aware that changing the bit values could break saved Debugger states (not a huge concern, just
|
|
* something to be aware of).
|
|
*/
|
|
Debugger8080.MESSAGES = {
|
|
"cpu": Messages8080.CPU,
|
|
"bus": Messages8080.BUS,
|
|
"mem": Messages8080.MEM,
|
|
"port": Messages8080.PORT,
|
|
"chipset": Messages8080.CHIPSET,
|
|
"keyboard": Messages8080.KEYBOARD, // "kbd" is also allowed as shorthand for "keyboard"; see doMessages()
|
|
"key": Messages8080.KEYS, // using "key" instead of "keys", since the latter is a method on JavasScript objects
|
|
"video": Messages8080.VIDEO,
|
|
"fdc": Messages8080.FDC,
|
|
"disk": Messages8080.DISK,
|
|
"serial": Messages8080.SERIAL,
|
|
"speaker": Messages8080.SPEAKER,
|
|
"computer": Messages8080.COMPUTER,
|
|
"log": Messages8080.LOG,
|
|
"warn": Messages8080.WARN,
|
|
/*
|
|
* Now we turn to message actions rather than message types; for example, setting "halt"
|
|
* on or off doesn't enable "halt" messages, but rather halts the CPU on any message above.
|
|
*
|
|
* Similarly, "m buffer on" turns on message buffering, defering the display of all messages
|
|
* until "m buffer off" is issued.
|
|
*/
|
|
"buffer": Messages8080.BUFFER,
|
|
"halt": Messages8080.HALT
|
|
};
|
|
|
|
Debugger8080.HISTORY_LIMIT = DEBUG? 100000 : 1000;
|
|
|
|
/**
|
|
* initBus(bus, cpu, dbg)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {Computer8080} cmp
|
|
* @param {Bus8080} bus
|
|
* @param {CPUState8080} cpu
|
|
* @param {Debugger8080} dbg
|
|
*/
|
|
Debugger8080.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|
{
|
|
this.bus = bus;
|
|
this.cpu = cpu;
|
|
this.cmp = cmp;
|
|
|
|
/*
|
|
* Re-initialize Debugger message support if necessary
|
|
*/
|
|
var sMessages = cmp.getMachineParm('messages');
|
|
if (sMessages) this.messageInit(sMessages);
|
|
|
|
this.aaOpDescs = Debugger8080.aaOpDescs;
|
|
|
|
this.messageDump(Messages8080.BUS, function onDumpBus(asArgs) { dbg.dumpBus(asArgs); });
|
|
|
|
this.setReady();
|
|
};
|
|
|
|
/**
|
|
* setBinding(sHTMLType, sBinding, control, sValue)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas")
|
|
* @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
|
|
*/
|
|
Debugger8080.prototype.setBinding = function(sHTMLType, 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 sCmds;
|
|
if (event.keyCode == Keyboard8080.KEYCODE.CR) {
|
|
sCmds = control.value;
|
|
control.value = "";
|
|
dbg.doCommands(sCmds, true);
|
|
}
|
|
else if (event.keyCode == Keyboard8080.KEYCODE.ESC) {
|
|
control.value = sCmds = "";
|
|
}
|
|
else {
|
|
if (event.keyCode == Keyboard8080.KEYCODE.UP) {
|
|
if (dbg.iPrevCmd < dbg.aPrevCmds.length - 1) {
|
|
sCmds = dbg.aPrevCmds[++dbg.iPrevCmd];
|
|
}
|
|
}
|
|
else if (event.keyCode == Keyboard8080.KEYCODE.DOWN) {
|
|
if (dbg.iPrevCmd > 0) {
|
|
sCmds = dbg.aPrevCmds[--dbg.iPrevCmd];
|
|
} else {
|
|
sCmds = "";
|
|
dbg.iPrevCmd = -1;
|
|
}
|
|
}
|
|
if (sCmds != null) {
|
|
var cch = sCmds.length;
|
|
control.value = sCmds;
|
|
control.setSelectionRange(cch, cch);
|
|
}
|
|
}
|
|
if (sCmds != 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 sCmds = dbg.controlDebug.value;
|
|
dbg.controlDebug.value = "";
|
|
dbg.doCommands(sCmds, 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);
|
|
dbg.setBusy(false);
|
|
}
|
|
return fCompleted;
|
|
}
|
|
);
|
|
return true;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* updateFocus()
|
|
*
|
|
* @this {Debugger8080}
|
|
*/
|
|
Debugger8080.prototype.updateFocus = function()
|
|
{
|
|
if (this.controlDebug) this.controlDebug.focus();
|
|
};
|
|
|
|
/**
|
|
* getAddr(dbgAddr, fWrite, nb)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {DbgAddr8080|null|undefined} 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 CPUDef8080.ADDR_INVALID
|
|
*/
|
|
Debugger8080.prototype.getAddr = function(dbgAddr, fWrite, nb)
|
|
{
|
|
var addr = dbgAddr && dbgAddr.addr;
|
|
if (addr == null) {
|
|
addr = CPUDef8080.ADDR_INVALID;
|
|
}
|
|
return addr;
|
|
};
|
|
|
|
/**
|
|
* getByte(dbgAddr, inc)
|
|
*
|
|
* We must route all our memory requests through the CPU now, in case paging is enabled.
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {DbgAddr8080} dbgAddr
|
|
* @param {number} [inc]
|
|
* @return {number}
|
|
*/
|
|
Debugger8080.prototype.getByte = function(dbgAddr, inc)
|
|
{
|
|
var b = 0xff;
|
|
var addr = this.getAddr(dbgAddr, false, 1);
|
|
if (addr !== CPUDef8080.ADDR_INVALID) {
|
|
b = this.bus.getByteDirect(addr);
|
|
if (inc) this.incAddr(dbgAddr, inc);
|
|
}
|
|
return b;
|
|
};
|
|
|
|
/**
|
|
* getWord(dbgAddr, fAdvance)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {DbgAddr8080} dbgAddr
|
|
* @param {boolean} [fAdvance]
|
|
* @return {number}
|
|
*/
|
|
Debugger8080.prototype.getWord = function(dbgAddr, fAdvance)
|
|
{
|
|
return this.getShort(dbgAddr, fAdvance? 2 : 0);
|
|
};
|
|
|
|
/**
|
|
* getShort(dbgAddr, inc)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {DbgAddr8080} dbgAddr
|
|
* @param {number} [inc]
|
|
* @return {number}
|
|
*/
|
|
Debugger8080.prototype.getShort = function(dbgAddr, inc)
|
|
{
|
|
var w = 0xffff;
|
|
var addr = this.getAddr(dbgAddr, false, 2);
|
|
if (addr !== CPUDef8080.ADDR_INVALID) {
|
|
w = this.bus.getShortDirect(addr);
|
|
if (inc) this.incAddr(dbgAddr, inc);
|
|
}
|
|
return w;
|
|
};
|
|
|
|
/**
|
|
* setByte(dbgAddr, b, inc)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {DbgAddr8080} dbgAddr
|
|
* @param {number} b
|
|
* @param {number} [inc]
|
|
*/
|
|
Debugger8080.prototype.setByte = function(dbgAddr, b, inc)
|
|
{
|
|
var addr = this.getAddr(dbgAddr, true, 1);
|
|
if (addr !== CPUDef8080.ADDR_INVALID) {
|
|
this.bus.setByteDirect(addr, b);
|
|
if (inc) this.incAddr(dbgAddr, inc);
|
|
this.cpu.updateCPU(true); // we set fForce to true in case video memory was the target
|
|
}
|
|
};
|
|
|
|
/**
|
|
* setShort(dbgAddr, w, inc)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {DbgAddr8080} dbgAddr
|
|
* @param {number} w
|
|
* @param {number} [inc]
|
|
*/
|
|
Debugger8080.prototype.setShort = function(dbgAddr, w, inc)
|
|
{
|
|
var addr = this.getAddr(dbgAddr, true, 2);
|
|
if (addr !== CPUDef8080.ADDR_INVALID) {
|
|
this.bus.setShortDirect(addr, w);
|
|
if (inc) this.incAddr(dbgAddr, inc);
|
|
this.cpu.updateCPU(true); // we set fForce to true in case video memory was the target
|
|
}
|
|
};
|
|
|
|
/**
|
|
* newAddr(addr)
|
|
*
|
|
* Returns a NEW DbgAddr8080 object, initialized with specified values and/or defaults.
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {number} [addr]
|
|
* @return {DbgAddr8080}
|
|
*/
|
|
Debugger8080.prototype.newAddr = function(addr)
|
|
{
|
|
return {addr: addr, fTemporary: false};
|
|
};
|
|
|
|
/**
|
|
* setAddr(dbgAddr, addr)
|
|
*
|
|
* Updates an EXISTING DbgAddr8080 object, initialized with specified values and/or defaults.
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {DbgAddr8080} dbgAddr
|
|
* @param {number} addr
|
|
* @return {DbgAddr8080}
|
|
*/
|
|
Debugger8080.prototype.setAddr = function(dbgAddr, addr)
|
|
{
|
|
dbgAddr.addr = addr;
|
|
dbgAddr.fTemporary = false;
|
|
return dbgAddr;
|
|
};
|
|
|
|
/**
|
|
* packAddr(dbgAddr)
|
|
*
|
|
* Packs a DbgAddr8080 object into an Array suitable for saving in a machine state object.
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {DbgAddr8080} dbgAddr
|
|
* @return {Array}
|
|
*/
|
|
Debugger8080.prototype.packAddr = function(dbgAddr)
|
|
{
|
|
return [dbgAddr.addr, dbgAddr.fTemporary];
|
|
};
|
|
|
|
/**
|
|
* unpackAddr(aAddr)
|
|
*
|
|
* Unpacks a DbgAddr8080 object from an Array created by packAddr() and restored from a saved machine state.
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {Array} aAddr
|
|
* @return {DbgAddr8080}
|
|
*/
|
|
Debugger8080.prototype.unpackAddr = function(aAddr)
|
|
{
|
|
return {addr: aAddr[0], fTemporary: aAddr[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 CPUDef8080.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
|
|
* Bus8080.nBusLimit; in the case of CPUDef8080.ADDR_INVALID, that will generally refer to the top of the physical
|
|
* address space.
|
|
*
|
|
* @this {Debugger8080}
|
|
* @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 {DbgAddr8080|null|undefined}
|
|
*/
|
|
Debugger8080.prototype.parseAddr = function(sAddr, fCode, fNoChecks, fPrint)
|
|
{
|
|
var dbgAddr;
|
|
var dbgAddrNext = (fCode? this.dbgAddrNextCode : this.dbgAddrNextData);
|
|
var addr = dbgAddrNext.addr;
|
|
if (sAddr !== undefined) {
|
|
sAddr = this.parseReference(sAddr);
|
|
dbgAddr = this.findSymbolAddr(sAddr);
|
|
if (dbgAddr) return dbgAddr;
|
|
addr = this.parseExpression(sAddr, fPrint);
|
|
}
|
|
if (addr != null) {
|
|
dbgAddr = this.newAddr(addr);
|
|
}
|
|
return dbgAddr;
|
|
};
|
|
|
|
/**
|
|
* parseAddrOptions(dbdAddr, sOptions)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {DbgAddr8080} dbgAddr
|
|
* @param {string} [sOptions]
|
|
*/
|
|
Debugger8080.prototype.parseAddrOptions = function(dbgAddr, sOptions)
|
|
{
|
|
if (sOptions) {
|
|
var a = sOptions.match(/(['"])(.*?)\1/);
|
|
if (a) {
|
|
dbgAddr.aCmds = this.parseCommand(dbgAddr.sCmd = a[2]);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* incAddr(dbgAddr, inc)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {DbgAddr8080} dbgAddr
|
|
* @param {number} [inc] contains value to increment dbgAddr by (default is 1)
|
|
*/
|
|
Debugger8080.prototype.incAddr = function(dbgAddr, inc)
|
|
{
|
|
if (dbgAddr.addr != null) {
|
|
dbgAddr.addr += (inc || 1);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* toHexOffset(off)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {number|null|undefined} [off]
|
|
* @return {string} the hex representation of off
|
|
*/
|
|
Debugger8080.prototype.toHexOffset = function(off)
|
|
{
|
|
return str.toHex(off, 4);
|
|
};
|
|
|
|
/**
|
|
* toHexAddr(dbgAddr)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {DbgAddr8080} dbgAddr
|
|
* @return {string} the hex representation of the address
|
|
*/
|
|
Debugger8080.prototype.toHexAddr = function(dbgAddr)
|
|
{
|
|
return this.toHexOffset(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 {Debugger8080}
|
|
* @param {DbgAddr8080} dbgAddr
|
|
* @param {number} [cchMax] (default is 256)
|
|
* @return {string} (and dbgAddr advanced past the terminating zero)
|
|
*/
|
|
Debugger8080.prototype.getSZ = function(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 {Debugger8080}
|
|
* @param {Array} aBlocks
|
|
* @param {string} [sAddr] (optional block address)
|
|
*/
|
|
Debugger8080.prototype.dumpBlocks = function(aBlocks, sAddr)
|
|
{
|
|
var addr = 0, i = 0, n = aBlocks.length;
|
|
|
|
if (sAddr) {
|
|
addr = this.getAddr(this.parseAddr(sAddr));
|
|
if (addr === CPUDef8080.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 = Memory8080.TYPE.NAMES[typePrev];
|
|
if (block) {
|
|
this.println(str.toHex(block.id) + " %" + str.toHex(i << this.bus.nBlockShift) + " %%" + str.toHex(block.addr) + " " + str.toHexWord(block.used) + " " + str.toHexWord(block.size) + " " + sType);
|
|
}
|
|
if (typePrev != Memory8080.TYPE.NONE) typePrev = -1;
|
|
cPrev = 0;
|
|
}
|
|
addr += this.bus.nBlockSize;
|
|
i++;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* dumpBus(asArgs)
|
|
*
|
|
* Dumps Bus allocations.
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {Array.<string>} asArgs (asArgs[0] is an optional block address)
|
|
*/
|
|
Debugger8080.prototype.dumpBus = function(asArgs)
|
|
{
|
|
this.dumpBlocks(this.bus.aMemBlocks, 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 {Debugger8080}
|
|
* @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)
|
|
*/
|
|
Debugger8080.prototype.dumpHistory = function(sPrev, sLines)
|
|
{
|
|
var sMore = "";
|
|
var cHistory = 0;
|
|
var iHistory = this.iOpcodeHistory;
|
|
var aHistory = this.aOpcodeHistory;
|
|
|
|
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.iOpcodeHistory) {
|
|
|
|
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--;
|
|
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 {Debugger8080}
|
|
* @param {string|undefined} sEnable contains zero or more message categories to enable, separated by '|'
|
|
*/
|
|
Debugger8080.prototype.messageInit = function(sEnable)
|
|
{
|
|
this.dbg = this;
|
|
this.bitsMessage = this.bitsWarning = Messages8080.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 Debugger8080.MESSAGES) {
|
|
if (usr.indexOf(aEnable, m) >= 0) {
|
|
this.bitsMessage |= Debugger8080.MESSAGES[m];
|
|
this.println(m + " messages enabled");
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* messageDump(bitMessage, fnDumper)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {number} bitMessage is one Messages category flag
|
|
* @param {function(Array.<string>)} fnDumper is a function the Debugger can use to dump data for that category
|
|
* @return {boolean} true if successfully registered, false if not
|
|
*/
|
|
Debugger8080.prototype.messageDump = function(bitMessage, fnDumper)
|
|
{
|
|
for (var m in Debugger8080.MESSAGES) {
|
|
if (bitMessage == Debugger8080.MESSAGES[m]) {
|
|
this.afnDumpers[m] = fnDumper;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* getRegIndex(sReg, off)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string} sReg
|
|
* @param {number} [off] optional offset into sReg
|
|
* @return {number} register index, or -1 if not found
|
|
*/
|
|
Debugger8080.prototype.getRegIndex = function(sReg, off)
|
|
{
|
|
var i;
|
|
sReg = sReg.toUpperCase();
|
|
if (off == null) {
|
|
i = usr.indexOf(Debugger8080.REGS, sReg);
|
|
} else {
|
|
i = usr.indexOf(Debugger8080.REGS, sReg.substr(off, 2));
|
|
if (i < 0) i = usr.indexOf(Debugger8080.REGS, sReg.substr(off, 1));
|
|
}
|
|
return i;
|
|
};
|
|
|
|
/**
|
|
* getRegString(iReg)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {number} iReg
|
|
* @return {string}
|
|
*/
|
|
Debugger8080.prototype.getRegString = function(iReg)
|
|
{
|
|
var cch = 0;
|
|
var n = this.getRegValue(iReg);
|
|
if (n !== undefined) {
|
|
switch(iReg) {
|
|
case Debugger8080.REG_A:
|
|
case Debugger8080.REG_B:
|
|
case Debugger8080.REG_C:
|
|
case Debugger8080.REG_D:
|
|
case Debugger8080.REG_E:
|
|
case Debugger8080.REG_H:
|
|
case Debugger8080.REG_L:
|
|
case Debugger8080.REG_M:
|
|
cch = 2;
|
|
break;
|
|
case Debugger8080.REG_BC:
|
|
case Debugger8080.REG_DE:
|
|
case Debugger8080.REG_HL:
|
|
case Debugger8080.REG_SP:
|
|
case Debugger8080.REG_PC:
|
|
case Debugger8080.REG_PS:
|
|
case Debugger8080.REG_PSW:
|
|
cch = 4;
|
|
break;
|
|
}
|
|
}
|
|
return cch? str.toHex(n, cch) : "??";
|
|
};
|
|
|
|
/**
|
|
* getRegValue(iReg)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {number} iReg
|
|
* @return {number|undefined}
|
|
*/
|
|
Debugger8080.prototype.getRegValue = function(iReg)
|
|
{
|
|
var n;
|
|
if (iReg >= 0) {
|
|
var cpu = this.cpu;
|
|
switch(iReg) {
|
|
case Debugger8080.REG_A:
|
|
n = cpu.regA;
|
|
break;
|
|
case Debugger8080.REG_B:
|
|
n = cpu.regB;
|
|
break;
|
|
case Debugger8080.REG_C:
|
|
n = cpu.regC;
|
|
break;
|
|
case Debugger8080.REG_BC:
|
|
n = cpu.getBC();
|
|
break;
|
|
case Debugger8080.REG_D:
|
|
n = cpu.regD;
|
|
break;
|
|
case Debugger8080.REG_E:
|
|
n = cpu.regE;
|
|
break;
|
|
case Debugger8080.REG_DE:
|
|
n = cpu.getDE();
|
|
break;
|
|
case Debugger8080.REG_H:
|
|
n = cpu.regH;
|
|
break;
|
|
case Debugger8080.REG_L:
|
|
n = cpu.regL;
|
|
break;
|
|
case Debugger8080.REG_HL:
|
|
n = cpu.getHL();
|
|
break;
|
|
case Debugger8080.REG_M:
|
|
n = cpu.getByte(cpu.getHL());
|
|
break;
|
|
case Debugger8080.REG_SP:
|
|
n = cpu.getSP();
|
|
break;
|
|
case Debugger8080.REG_PC:
|
|
n = cpu.getPC();
|
|
break;
|
|
case Debugger8080.REG_PS:
|
|
n = cpu.getPS();
|
|
break;
|
|
case Debugger8080.REG_PSW:
|
|
n = cpu.getPSW();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return n;
|
|
};
|
|
|
|
/**
|
|
* replaceRegs(s)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string} s
|
|
* @return {string}
|
|
*/
|
|
Debugger8080.prototype.replaceRegs = function(s)
|
|
{
|
|
/*
|
|
* Replace any references first; this means that register references inside the reference
|
|
* do NOT need to be prefixed with '@'.
|
|
*/
|
|
s = this.parseReference(s);
|
|
|
|
/*
|
|
* Replace every @XX (or @XXX), where XX (or XXX) is a register, with the register's value.
|
|
*/
|
|
var i = 0;
|
|
var b, sChar, sAddr, dbgAddr, sReplace;
|
|
while ((i = s.indexOf('@', i)) >= 0) {
|
|
var iReg = this.getRegIndex(s, i + 1);
|
|
if (iReg >= 0) {
|
|
s = s.substr(0, i) + this.getRegString(iReg) + s.substr(i + 1 + Debugger8080.REGS[iReg].length);
|
|
}
|
|
i++;
|
|
}
|
|
/*
|
|
* Replace every #XX, where XX is a hex byte value, with the corresponding ASCII character (if printable).
|
|
*/
|
|
i = 0;
|
|
while ((i = s.indexOf('#', i)) >= 0) {
|
|
sChar = s.substr(i+1, 2);
|
|
b = str.parseInt(sChar, 16);
|
|
if (b != null && b >= 32 && b < 128) {
|
|
sReplace = sChar + " '" + String.fromCharCode(b) + "'";
|
|
s = s.replace('#' + sChar, sReplace);
|
|
i += sReplace.length;
|
|
continue;
|
|
}
|
|
i++;
|
|
}
|
|
/*
|
|
* Replace every $XXXX:XXXX, where XXXX:XXXX is a segmented address, with the zero-terminated string at that address.
|
|
*/
|
|
i = 0;
|
|
while ((i = s.indexOf('$', i)) >= 0) {
|
|
sAddr = s.substr(i+1, 9);
|
|
dbgAddr = this.parseAddr(sAddr);
|
|
if (dbgAddr) {
|
|
sReplace = sAddr + ' "' + this.getSZ(dbgAddr) + '"';
|
|
s = s.replace('$' + sAddr, sReplace);
|
|
i += sReplace.length;
|
|
continue;
|
|
}
|
|
i++;
|
|
}
|
|
/*
|
|
* Replace every ^XXXX:XXXX, where XXXX:XXXX is a segmented address, with the FCB filename stored at that address.
|
|
*/
|
|
i = 0;
|
|
while ((i = s.indexOf('^', i)) >= 0) {
|
|
sAddr = s.substr(i+1, 9);
|
|
dbgAddr = this.parseAddr(sAddr);
|
|
if (dbgAddr) {
|
|
this.incAddr(dbgAddr);
|
|
sReplace = sAddr + ' "' + this.getSZ(dbgAddr, 11) + '"';
|
|
s = s.replace('^' + sAddr, sReplace);
|
|
i += sReplace.length;
|
|
continue;
|
|
}
|
|
i++;
|
|
}
|
|
return s;
|
|
};
|
|
|
|
/**
|
|
* message(sMessage, fAddress)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string} sMessage is any caller-defined message string
|
|
* @param {boolean} [fAddress] is true to display the current CS:IP
|
|
*/
|
|
Debugger8080.prototype.message = function(sMessage, fAddress)
|
|
{
|
|
if (fAddress) {
|
|
sMessage += " at " + this.toHexAddr(this.newAddr(this.cpu.getPC()));
|
|
}
|
|
|
|
if (this.bitsMessage & Messages8080.BUFFER) {
|
|
this.aMessageBuffer.push(sMessage);
|
|
return;
|
|
}
|
|
|
|
if (this.sMessagePrev && sMessage == this.sMessagePrev) return;
|
|
this.sMessagePrev = sMessage;
|
|
|
|
if (this.bitsMessage & Messages8080.HALT) {
|
|
this.stopCPU();
|
|
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 CPU8080.calcStartTime() for a discussion of why we might want to call yieldCPU() *before*
|
|
* we display the message.
|
|
*/
|
|
if (this.cpu) this.cpu.yieldCPU();
|
|
};
|
|
|
|
/**
|
|
* messageIO(component, port, bOut, addrFrom, name, bIn, bitsMessage)
|
|
*
|
|
* Most (if not all) port handlers should provide a name for their respective ports, so if no name is provided,
|
|
* we assume this is an unknown port, and display a message by default.
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {Component} component
|
|
* @param {number} port
|
|
* @param {number|null} bOut if an output operation
|
|
* @param {number|null} [addrFrom]
|
|
* @param {string|null} [name] of the port, if any
|
|
* @param {number|null} [bIn] is the input value, if known, on an input operation
|
|
* @param {number} [bitsMessage] is one or more Messages category flag(s)
|
|
*/
|
|
Debugger8080.prototype.messageIO = function(component, port, bOut, addrFrom, name, bIn, bitsMessage)
|
|
{
|
|
bitsMessage |= Messages8080.PORT;
|
|
if (name == null || (this.bitsMessage & bitsMessage) == bitsMessage) {
|
|
this.message(component.idComponent + '.' + (bOut != null? "outPort" : "inPort") + '(' + str.toHexWord(port) + ',' + (name? name : "unknown") + (bOut != null? ',' + str.toHexByte(bOut) : "") + ')' + (bIn != null? (": " + str.toHexByte(bIn)) : "") + (addrFrom != null? (" at " + this.toHexOffset(addrFrom)) : ""));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* init()
|
|
*
|
|
* @this {Debugger8080}
|
|
*/
|
|
Debugger8080.prototype.init = function()
|
|
{
|
|
this.println("Type ? for help with PC8080 Debugger commands");
|
|
this.updateStatus();
|
|
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 {Debugger8080}
|
|
* @param {boolean} [fQuiet]
|
|
*/
|
|
Debugger8080.prototype.historyInit = function(fQuiet)
|
|
{
|
|
var i;
|
|
if (!this.checksEnabled()) {
|
|
if (this.aOpcodeHistory && this.aOpcodeHistory.length && !fQuiet) {
|
|
this.println("instruction history buffer freed");
|
|
}
|
|
this.iOpcodeHistory = 0;
|
|
this.aOpcodeHistory = [];
|
|
this.aaOpcodeCounts = [];
|
|
return;
|
|
}
|
|
if (!this.aOpcodeHistory || !this.aOpcodeHistory.length) {
|
|
this.aOpcodeHistory = new Array(Debugger8080.HISTORY_LIMIT);
|
|
for (i = 0; i < this.aOpcodeHistory.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.aOpcodeHistory[i] = this.newAddr();
|
|
}
|
|
this.iOpcodeHistory = 0;
|
|
if (!fQuiet) {
|
|
this.println("instruction history buffer allocated");
|
|
}
|
|
}
|
|
if (!this.aaOpcodeCounts || !this.aaOpcodeCounts.length) {
|
|
this.aaOpcodeCounts = new Array(256);
|
|
for (i = 0; i < this.aaOpcodeCounts.length; i++) {
|
|
this.aaOpcodeCounts[i] = [i, 0];
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* runCPU(fUpdateFocus)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {boolean} [fUpdateFocus] is true to update focus
|
|
* @return {boolean} true if run request successful, false if not
|
|
*/
|
|
Debugger8080.prototype.runCPU = function(fUpdateFocus)
|
|
{
|
|
if (!this.isCPUAvail()) return false;
|
|
this.cpu.runCPU(fUpdateFocus);
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* stepCPU(nCycles, fRegs, fUpdateCPU)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {number} nCycles (0 for one instruction without checking breakpoints)
|
|
* @param {boolean} [fRegs] is true to display registers after step (default is false)
|
|
* @param {boolean} [fUpdateCPU] is false to disable calls to updateCPU() (default is true)
|
|
* @return {boolean}
|
|
*/
|
|
Debugger8080.prototype.stepCPU = function(nCycles, fRegs, fUpdateCPU)
|
|
{
|
|
if (!this.isCPUAvail()) return false;
|
|
|
|
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);
|
|
}
|
|
try {
|
|
var nCyclesStep = this.cpu.stepCPU(nCycles);
|
|
if (nCyclesStep > 0) {
|
|
this.nCycles += nCyclesStep;
|
|
this.cpu.addCycles(nCyclesStep, true);
|
|
this.cpu.updateChecksum(nCyclesStep);
|
|
this.cOpcodes++;
|
|
}
|
|
}
|
|
catch(exception) {
|
|
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.runCPU(), we must nudge the cpu's update code,
|
|
* and then update our own state. Normally, the only time fUpdateCPU will be false is when doTrace()
|
|
* is calling us in a loop, in which case it will perform its own updateCPU() when it's done.
|
|
*/
|
|
if (fUpdateCPU !== false) this.cpu.updateCPU();
|
|
|
|
this.updateStatus(fRegs || false);
|
|
return (this.nCycles > 0);
|
|
};
|
|
|
|
/**
|
|
* stopCPU()
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {boolean} [fComplete]
|
|
*/
|
|
Debugger8080.prototype.stopCPU = function(fComplete)
|
|
{
|
|
if (this.cpu) this.cpu.stopCPU(fComplete);
|
|
};
|
|
|
|
/**
|
|
* updateStatus(fRegs)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {boolean} [fRegs] (default is true)
|
|
*/
|
|
Debugger8080.prototype.updateStatus = function(fRegs)
|
|
{
|
|
if (fRegs === undefined) fRegs = true;
|
|
|
|
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();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* isCPUAvail()
|
|
*
|
|
* Make sure the CPU is ready (finished initializing), not busy (already running), and not in an error state.
|
|
*
|
|
* @this {Debugger8080}
|
|
* @return {boolean}
|
|
*/
|
|
Debugger8080.prototype.isCPUAvail = function()
|
|
{
|
|
if (!this.cpu)
|
|
return false;
|
|
if (!this.cpu.isReady())
|
|
return false;
|
|
if (!this.cpu.isPowered())
|
|
return false;
|
|
if (this.cpu.isBusy())
|
|
return false;
|
|
return !this.cpu.isError();
|
|
};
|
|
|
|
/**
|
|
* powerUp(data, fRepower)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {Object|null} data
|
|
* @param {boolean} [fRepower]
|
|
* @return {boolean} true if successful, false if failure
|
|
*/
|
|
Debugger8080.prototype.powerUp = function(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 && this.restore) {
|
|
if (!this.restore(data)) return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* powerDown(fSave, fShutdown)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {boolean} [fSave]
|
|
* @param {boolean} [fShutdown]
|
|
* @return {Object|boolean}
|
|
*/
|
|
Debugger8080.prototype.powerDown = function(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 {Debugger8080}
|
|
* @param {boolean} fQuiet (true only when called from our own powerUp handler)
|
|
*/
|
|
Debugger8080.prototype.reset = function(fQuiet)
|
|
{
|
|
this.historyInit();
|
|
this.cOpcodes = this.cOpcodesStart = 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.fRunning = false;
|
|
this.clearTempBreakpoint();
|
|
if (!fQuiet) this.updateStatus();
|
|
};
|
|
|
|
/**
|
|
* save()
|
|
*
|
|
* This implements (very rudimentary) save support for the Debugger component.
|
|
*
|
|
* @this {Debugger8080}
|
|
* @return {Object}
|
|
*/
|
|
Debugger8080.prototype.save = function()
|
|
{
|
|
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 {Debugger8080}
|
|
* @param {Object} data
|
|
* @return {boolean} true if successful, false if failure
|
|
*/
|
|
Debugger8080.prototype.restore = function(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 {Debugger8080}
|
|
* @param {number} ms
|
|
* @param {number} nCycles
|
|
*/
|
|
Debugger8080.prototype.start = function(ms, nCycles)
|
|
{
|
|
if (!this.nStep) this.println("running");
|
|
this.flags.fRunning = 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 {Debugger8080}
|
|
* @param {number} ms
|
|
* @param {number} nCycles
|
|
*/
|
|
Debugger8080.prototype.stop = function(ms, nCycles)
|
|
{
|
|
if (this.flags.fRunning) {
|
|
this.flags.fRunning = 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.cOpcodes + " opcodes, ";
|
|
/*
|
|
* $ops displays progress by calculating cOpcodes - cOpcodesStart, so before
|
|
* zeroing cOpcodes, we should subtract cOpcodes from cOpcodesStart (since we're
|
|
* effectively subtracting cOpcodes from cOpcodes as well).
|
|
*/
|
|
this.cOpcodesStart -= this.cOpcodes;
|
|
this.cOpcodes = 0;
|
|
}
|
|
sStopped += this.nCycles + " cycles, " + msTotal + " ms, " + nCyclesPerSecond + " hz)";
|
|
} else {
|
|
if (this.messageEnabled(Messages8080.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.updateFocus();
|
|
this.clearTempBreakpoint(this.cpu.getPC());
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 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 {Debugger8080}
|
|
* @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
|
|
*/
|
|
Debugger8080.prototype.checksEnabled = function(fRelease)
|
|
{
|
|
return ((DEBUG && !fRelease)? true : (this.aBreakExec.length > 1 || !!this.nBreakIns));
|
|
};
|
|
|
|
/**
|
|
* 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 frequencies and instruction history.
|
|
*
|
|
* @this {Debugger8080}
|
|
* @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
|
|
*/
|
|
Debugger8080.prototype.checkInstruction = function(addr, nState)
|
|
{
|
|
var cpu = this.cpu;
|
|
|
|
if (nState > 0) {
|
|
if (this.nBreakIns && !--this.nBreakIns) {
|
|
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 Messages8080.INT messages.
|
|
*/
|
|
if (nState >= 0 && this.aaOpcodeCounts.length) {
|
|
this.cOpcodes++;
|
|
var bOpcode = this.bus.getByteDirect(addr);
|
|
if (bOpcode != null) {
|
|
this.aaOpcodeCounts[bOpcode][1]++;
|
|
var dbgAddr = this.aOpcodeHistory[this.iOpcodeHistory];
|
|
this.setAddr(dbgAddr, cpu.getPC());
|
|
if (DEBUG) dbgAddr.cycleCount = cpu.getCycles();
|
|
if (++this.iOpcodeHistory == this.aOpcodeHistory.length) this.iOpcodeHistory = 0;
|
|
}
|
|
}
|
|
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 {Debugger8080}
|
|
* @param {number} addr
|
|
* @param {number} [nb] (# of bytes; default is 1)
|
|
* @return {boolean} true if breakpoint hit, false if not
|
|
*/
|
|
Debugger8080.prototype.checkMemoryRead = function(addr, nb)
|
|
{
|
|
if (this.checkBreakpoint(addr, nb || 1, this.aBreakRead)) {
|
|
this.stopCPU(true);
|
|
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 {Debugger8080}
|
|
* @param {number} addr
|
|
* @param {number} [nb] (# of bytes; default is 1)
|
|
* @return {boolean} true if breakpoint hit, false if not
|
|
*/
|
|
Debugger8080.prototype.checkMemoryWrite = function(addr, nb)
|
|
{
|
|
if (this.checkBreakpoint(addr, nb || 1, this.aBreakWrite)) {
|
|
this.stopCPU(true);
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* checkPortInput(port, size, data)
|
|
*
|
|
* This "check" function is called by the Bus component to inform us that port input occurred.
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {number} port
|
|
* @param {number} size
|
|
* @param {number} data
|
|
* @return {boolean} true if breakpoint hit, false if not
|
|
*/
|
|
Debugger8080.prototype.checkPortInput = function(port, size, data)
|
|
{
|
|
/*
|
|
* We trust that the Bus component won't call us unless we told it to, so we halt unconditionally
|
|
*/
|
|
this.println("break on input from port " + str.toHexWord(port) + ": " + str.toHex(data));
|
|
this.stopCPU(true);
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* checkPortOutput(port, size, data)
|
|
*
|
|
* This "check" function is called by the Bus component to inform us that port output occurred.
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {number} port
|
|
* @param {number} size
|
|
* @param {number} data
|
|
* @return {boolean} true if breakpoint hit, false if not
|
|
*/
|
|
Debugger8080.prototype.checkPortOutput = function(port, size, data)
|
|
{
|
|
/*
|
|
* We trust that the Bus component won't call us unless we told it to, so we halt unconditionally
|
|
*/
|
|
this.println("break on output to port " + str.toHexWord(port) + ": " + str.toHex(data));
|
|
this.stopCPU(true);
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* clearBreakpoints()
|
|
*
|
|
* @this {Debugger8080}
|
|
*/
|
|
Debugger8080.prototype.clearBreakpoints = function()
|
|
{
|
|
var i, dbgAddr;
|
|
this.aBreakExec = ["bp"];
|
|
if (this.aBreakRead !== undefined) {
|
|
for (i = 1; i < this.aBreakRead.length; i++) {
|
|
dbgAddr = this.aBreakRead[i];
|
|
this.bus.removeMemBreak(this.getAddr(dbgAddr), false);
|
|
}
|
|
}
|
|
this.aBreakRead = ["br"];
|
|
if (this.aBreakWrite !== undefined) {
|
|
for (i = 1; i < this.aBreakWrite.length; i++) {
|
|
dbgAddr = this.aBreakWrite[i];
|
|
this.bus.removeMemBreak(this.getAddr(dbgAddr), true);
|
|
}
|
|
}
|
|
this.aBreakWrite = ["bw"];
|
|
/*
|
|
* nSuppressBreaks ensures we can't get into an infinite loop where a breakpoint lookup requires
|
|
* reading a segment descriptor via getSegment(), and that triggers more memory reads, which triggers
|
|
* more breakpoint checks.
|
|
*/
|
|
this.nSuppressBreaks = 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 {Debugger8080}
|
|
* @param {Array} aBreak
|
|
* @param {DbgAddr8080} dbgAddr
|
|
* @param {boolean} [fTemporary]
|
|
* @return {boolean} true if breakpoint added, false if already exists
|
|
*/
|
|
Debugger8080.prototype.addBreakpoint = function(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 === CPUDef8080.ADDR_INVALID) {
|
|
this.println("invalid address: " + this.toHexAddr(dbgAddr));
|
|
fSuccess = false;
|
|
} else {
|
|
this.bus.addMemBreak(addr, aBreak == this.aBreakWrite);
|
|
}
|
|
}
|
|
|
|
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 {Debugger8080}
|
|
* @param {Array} aBreak
|
|
* @param {DbgAddr8080} dbgAddr
|
|
* @param {boolean} [fRemove]
|
|
* @param {boolean} [fTemporary]
|
|
* @param {boolean} [fQuiet]
|
|
* @return {boolean} true if found, false if not
|
|
*/
|
|
Debugger8080.prototype.findBreakpoint = function(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) {
|
|
this.bus.removeMemBreak(addr, aBreak == this.aBreakWrite);
|
|
}
|
|
/*
|
|
* 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 {Debugger8080}
|
|
* @param {Array} aBreak
|
|
* @return {number} of breakpoints listed, 0 if none
|
|
*/
|
|
Debugger8080.prototype.listBreakpoints = function(aBreak)
|
|
{
|
|
for (var i = 1; i < aBreak.length; i++) {
|
|
this.printBreakpoint(aBreak, i);
|
|
}
|
|
return aBreak.length - 1;
|
|
};
|
|
|
|
/**
|
|
* printBreakpoint(aBreak, i, sAction)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {Array} aBreak
|
|
* @param {number} i
|
|
* @param {string} [sAction]
|
|
*/
|
|
Debugger8080.prototype.printBreakpoint = function(aBreak, i, sAction)
|
|
{
|
|
var dbgAddr = aBreak[i];
|
|
this.println(aBreak[0] + ' ' + this.toHexAddr(dbgAddr) + (sAction? (' ' + sAction) : (dbgAddr.sCmd? (' "' + dbgAddr.sCmd + '"') : '')));
|
|
};
|
|
|
|
/**
|
|
* setTempBreakpoint(dbgAddr)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {DbgAddr8080} dbgAddr of new temp breakpoint
|
|
*/
|
|
Debugger8080.prototype.setTempBreakpoint = function(dbgAddr)
|
|
{
|
|
this.addBreakpoint(this.aBreakExec, dbgAddr, true);
|
|
};
|
|
|
|
/**
|
|
* clearTempBreakpoint(addr)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {number|undefined} [addr] clear all temp breakpoints if no address specified
|
|
*/
|
|
Debugger8080.prototype.clearTempBreakpoint = function(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 {Debugger8080}
|
|
* @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
|
|
*/
|
|
Debugger8080.prototype.checkBreakpoint = function(addr, nb, aBreak, fTemporary)
|
|
{
|
|
/*
|
|
* Time to check for execution breakpoints; note that this should be done BEFORE updating frequency
|
|
* or 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;
|
|
|
|
/*
|
|
* We used to calculate the linear address of the breakpoint at the time the
|
|
* breakpoint was added, so that a breakpoint set in one mode (eg, in real-mode)
|
|
* would still work as intended if the mode changed later (eg, to protected-mode).
|
|
*
|
|
* However, that created difficulties setting protected-mode breakpoints in segments
|
|
* that might not be defined yet, or that could move in physical memory.
|
|
*
|
|
* If you want to create a real-mode breakpoint that will break regardless of mode,
|
|
* use the physical address of the real-mode memory location instead.
|
|
*/
|
|
var addrBreak = this.getAddr(dbgAddrBreak);
|
|
for (var n = 0; n < nb; n++) {
|
|
if (addr + n == addrBreak) {
|
|
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)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {DbgAddr8080} dbgAddr
|
|
* @param {string} [sComment] is an associated comment
|
|
* @param {number} [nSequence] is an associated sequence number, undefined if none
|
|
* @return {string} (and dbgAddr is updated to the next instruction)
|
|
*/
|
|
Debugger8080.prototype.getInstruction = function(dbgAddr, sComment, nSequence)
|
|
{
|
|
var dbgAddrIns = this.newAddr(dbgAddr.addr);
|
|
|
|
var bOpcode = this.getByte(dbgAddr, 1);
|
|
|
|
var asOpcodes = this.style != Debugger8080.STYLE_8086? Debugger8080.INS_NAMES : Debugger8080.INS_NAMES_8086;
|
|
var aOpDesc = this.aaOpDescs[bOpcode];
|
|
var iIns = aOpDesc[0];
|
|
|
|
var sOperands = "";
|
|
var sOpcode = asOpcodes[iIns];
|
|
var cOperands = aOpDesc.length - 1;
|
|
var typeSizeDefault = Debugger8080.TYPE_NONE, type;
|
|
|
|
for (var iOperand = 1; iOperand <= cOperands; iOperand++) {
|
|
|
|
var disp, off, cch;
|
|
var sOperand = "";
|
|
|
|
type = aOpDesc[iOperand];
|
|
if (type === undefined) continue;
|
|
if ((type & Debugger8080.TYPE_OPT) && this.style == Debugger8080.STYLE_8080) continue;
|
|
|
|
var typeMode = type & Debugger8080.TYPE_MODE;
|
|
if (!typeMode) continue;
|
|
|
|
var typeSize = type & Debugger8080.TYPE_SIZE;
|
|
if (!typeSize) {
|
|
type |= typeSizeDefault;
|
|
} else {
|
|
typeSizeDefault = typeSize;
|
|
}
|
|
|
|
var typeOther = type & Debugger8080.TYPE_OTHER;
|
|
if (!typeOther) {
|
|
type |= (iOperand == 1? Debugger8080.TYPE_OUT : Debugger8080.TYPE_IN);
|
|
}
|
|
|
|
if (typeMode & Debugger8080.TYPE_IMM) {
|
|
sOperand = this.getImmOperand(type, dbgAddr);
|
|
}
|
|
else if (typeMode & Debugger8080.TYPE_REG) {
|
|
sOperand = this.getRegOperand((type & Debugger8080.TYPE_IREG) >> 8, type, dbgAddr);
|
|
}
|
|
else if (typeMode & Debugger8080.TYPE_INT) {
|
|
sOperand = ((bOpcode >> 3) & 0x7).toString();
|
|
}
|
|
|
|
if (!sOperand || !sOperand.length) {
|
|
sOperands = "INVALID";
|
|
break;
|
|
}
|
|
if (sOperands.length > 0) sOperands += ',';
|
|
sOperands += (sOperand || "???");
|
|
}
|
|
|
|
var sBytes = "";
|
|
var sLine = this.toHexAddr(dbgAddrIns) + ' ';
|
|
if (dbgAddrIns.addr !== CPUDef8080.ADDR_INVALID && dbgAddr.addr !== CPUDef8080.ADDR_INVALID) {
|
|
do {
|
|
sBytes += str.toHex(this.getByte(dbgAddrIns, 1), 2);
|
|
if (dbgAddrIns.addr == null) break;
|
|
} while (dbgAddrIns.addr != dbgAddr.addr);
|
|
}
|
|
|
|
sLine += str.pad(sBytes, 10);
|
|
sLine += (type & Debugger8080.TYPE_UNDOC)? '*' : ' ';
|
|
sLine += str.pad(sOpcode, 7);
|
|
if (sOperands) sLine += ' ' + sOperands;
|
|
|
|
if (sComment) {
|
|
sLine = str.pad(sLine, 40) + ';' + sComment;
|
|
if (!this.cpu.flags.fChecksum) {
|
|
sLine += (nSequence != null? '=' + nSequence.toString() : "");
|
|
} else {
|
|
var nCycles = this.cpu.getCycles();
|
|
sLine += "cycles=" + nCycles.toString() + " cs=" + str.toHex(this.cpu.aCounts.nChecksum);
|
|
}
|
|
}
|
|
return sLine;
|
|
};
|
|
|
|
/**
|
|
* getImmOperand(type, dbgAddr)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {number} type
|
|
* @param {DbgAddr8080} dbgAddr
|
|
* @return {string} operand
|
|
*/
|
|
Debugger8080.prototype.getImmOperand = function(type, dbgAddr)
|
|
{
|
|
var sOperand = ' ';
|
|
var typeSize = type & Debugger8080.TYPE_SIZE;
|
|
|
|
switch (typeSize) {
|
|
case Debugger8080.TYPE_BYTE:
|
|
sOperand = str.toHex(this.getByte(dbgAddr, 1), 2);
|
|
break;
|
|
case Debugger8080.TYPE_SBYTE:
|
|
sOperand = str.toHex((this.getByte(dbgAddr, 1) << 24) >> 24, 4);
|
|
break;
|
|
case Debugger8080.TYPE_WORD:
|
|
sOperand = str.toHex(this.getShort(dbgAddr, 2), 4);
|
|
break;
|
|
default:
|
|
return "imm(" + str.toHexWord(type) + ')';
|
|
}
|
|
if (this.style == Debugger8080.STYLE_8086 && (type & Debugger8080.TYPE_MEM)) {
|
|
sOperand = '[' + sOperand + ']';
|
|
} else if (!(type & Debugger8080.TYPE_REG)) {
|
|
sOperand = (this.style == Debugger8080.STYLE_8080? '$' : "0x") + sOperand;
|
|
}
|
|
return sOperand;
|
|
};
|
|
|
|
/**
|
|
* getRegOperand(iReg, type, dbgAddr)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {number} iReg
|
|
* @param {number} type
|
|
* @param {DbgAddr8080} dbgAddr
|
|
* @return {string} operand
|
|
*/
|
|
Debugger8080.prototype.getRegOperand = function(iReg, type, dbgAddr)
|
|
{
|
|
/*
|
|
* Although this breaks with 8080 assembler conventions, I'm going to experiment with some different
|
|
* mnemonics; specifically, "[HL]" instead of "M". This is also more in keeping with how getImmOperand()
|
|
* displays memory references (ie, by enclosing them in brackets).
|
|
*/
|
|
var sOperand = Debugger8080.REGS[iReg];
|
|
if (this.style == Debugger8080.STYLE_8086 && (type & Debugger8080.TYPE_MEM)) {
|
|
if (iReg == Debugger8080.REG_M) {
|
|
sOperand = "HL";
|
|
}
|
|
sOperand = '[' + sOperand + ']';
|
|
}
|
|
return sOperand;
|
|
};
|
|
|
|
/**
|
|
* parseInstruction(sOp, sOperand, addr)
|
|
*
|
|
* TODO: Unimplemented. See parseInstruction() in modules/c1pjs/lib/debugger.js for a working implementation.
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string} sOp
|
|
* @param {string|undefined} sOperand
|
|
* @param {DbgAddr8080} dbgAddr of memory where this instruction is being assembled
|
|
* @return {Array.<number>} of opcode bytes; if the instruction can't be parsed, the array will be empty
|
|
*/
|
|
Debugger8080.prototype.parseInstruction = function(sOp, sOperand, dbgAddr)
|
|
{
|
|
var aOpBytes = [];
|
|
this.println("not supported yet");
|
|
return aOpBytes;
|
|
};
|
|
|
|
/**
|
|
* getFlagOutput(sFlag)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string} sFlag
|
|
* @return {string} value of flag
|
|
*/
|
|
Debugger8080.prototype.getFlagOutput = function(sFlag)
|
|
{
|
|
var b;
|
|
switch (sFlag) {
|
|
case "IF":
|
|
b = this.cpu.getIF();
|
|
break;
|
|
case "SF":
|
|
b = this.cpu.getSF();
|
|
break;
|
|
case "ZF":
|
|
b = this.cpu.getZF();
|
|
break;
|
|
case "AF":
|
|
b = this.cpu.getAF();
|
|
break;
|
|
case "PF":
|
|
b = this.cpu.getPF();
|
|
break;
|
|
case "CF":
|
|
b = this.cpu.getCF();
|
|
break;
|
|
default:
|
|
b = 0;
|
|
break;
|
|
}
|
|
return sFlag.charAt(0) + (b? '1' : '0') + ' ';
|
|
};
|
|
|
|
/**
|
|
* getRegOutput(iReg)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {number} iReg
|
|
* @return {string}
|
|
*/
|
|
Debugger8080.prototype.getRegOutput = function(iReg)
|
|
{
|
|
var sReg = Debugger8080.REGS[iReg];
|
|
return sReg + '=' + this.getRegString(iReg) + ' ';
|
|
};
|
|
|
|
/**
|
|
* getRegDump()
|
|
*
|
|
* Sample 8080 register dump:
|
|
*
|
|
* A=00 BC=0000 DE=0000 HL=0000 SP=0000 I0 S0 Z0 A0 P0 C0
|
|
* 0000 00 NOP
|
|
*
|
|
* @this {Debugger8080}
|
|
* @return {string}
|
|
*/
|
|
Debugger8080.prototype.getRegDump = function()
|
|
{
|
|
var s;
|
|
s = this.getRegOutput(Debugger8080.REG_A) +
|
|
this.getRegOutput(Debugger8080.REG_BC) +
|
|
this.getRegOutput(Debugger8080.REG_DE) +
|
|
this.getRegOutput(Debugger8080.REG_HL) +
|
|
this.getRegOutput(Debugger8080.REG_SP) +
|
|
this.getFlagOutput("IF") + this.getFlagOutput("SF") + this.getFlagOutput("ZF") +
|
|
this.getFlagOutput("AF") + this.getFlagOutput("PF") + this.getFlagOutput("CF");
|
|
return s;
|
|
};
|
|
|
|
Debugger8080.aBinOpPrecedence = {
|
|
'||': 0, // logical OR
|
|
'&&': 1, // logical AND
|
|
'|': 2, // bitwise OR
|
|
'^': 3, // bitwise XOR
|
|
'&': 4, // bitwise AND
|
|
'!=': 5, // inequality
|
|
'==': 5, // equality
|
|
'>=': 6, // greater than or equal to
|
|
'>': 6, // greater than
|
|
'<=': 6, // less than or equal to
|
|
'<': 6, // less than
|
|
'>>>': 7, // unsigned bitwise right shift
|
|
'>>': 7, // bitwise right shift
|
|
'<<': 7, // bitwise left shift
|
|
'-': 8, // subtraction
|
|
'+': 8, // addition
|
|
'%': 9, // remainder
|
|
'/': 9, // division
|
|
'*': 9 // multiplication
|
|
};
|
|
|
|
/**
|
|
* evalExpression(aVals, aOps, cOps)
|
|
*
|
|
* In Node, if you set a variable to 0x80000001; ie:
|
|
*
|
|
* foo=0x80000001|0
|
|
*
|
|
* and then calculate foo*foo using "(foo*foo).toString(2)", the result is:
|
|
*
|
|
* '11111111111111111111111111111100000000000000000000000000000000'
|
|
*
|
|
* which is slightly incorrect because it has overflowed JavaScript's floating-point precision.
|
|
*
|
|
* 0x80000001 in decimal is -2147483647, so the product is 4611686014132420609, which is 0x3FFFFFFF00000001.
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {Array.<number>} aVals
|
|
* @param {Array.<string>} aOps
|
|
* @param {number} [cOps] (default is all)
|
|
* @return {boolean} true if successful, false if error
|
|
*/
|
|
Debugger8080.prototype.evalExpression = function(aVals, aOps, cOps)
|
|
{
|
|
cOps = cOps || -1;
|
|
while (cOps-- && aOps.length) {
|
|
var chOp = aOps.pop();
|
|
if (aVals.length < 2) return false;
|
|
var valNew;
|
|
var val2 = aVals.pop();
|
|
var val1 = aVals.pop();
|
|
switch(chOp) {
|
|
case '*':
|
|
valNew = val1 * val2;
|
|
break;
|
|
case '/':
|
|
if (!val2) return false;
|
|
valNew = val1 / val2;
|
|
break;
|
|
case '%':
|
|
if (!val2) return false;
|
|
valNew = val1 % val2;
|
|
break;
|
|
case '+':
|
|
valNew = val1 + val2;
|
|
break;
|
|
case '-':
|
|
valNew = val1 - val2;
|
|
break;
|
|
case '<<':
|
|
valNew = val1 << val2;
|
|
break;
|
|
case '>>':
|
|
valNew = val1 >> val2;
|
|
break;
|
|
case '>>>':
|
|
valNew = val1 >>> val2;
|
|
break;
|
|
case '<':
|
|
valNew = (val1 < val2? 1 : 0);
|
|
break;
|
|
case '<=':
|
|
valNew = (val1 <= val2? 1 : 0);
|
|
break;
|
|
case '>':
|
|
valNew = (val1 > val2? 1 : 0);
|
|
break;
|
|
case '>=':
|
|
valNew = (val1 >= val2? 1 : 0);
|
|
break;
|
|
case '==':
|
|
valNew = (val1 == val2? 1 : 0);
|
|
break;
|
|
case '!=':
|
|
valNew = (val1 != val2? 1 : 0);
|
|
break;
|
|
case '&':
|
|
valNew = val1 & val2;
|
|
break;
|
|
case '^':
|
|
valNew = val1 ^ val2;
|
|
break;
|
|
case '|':
|
|
valNew = val1 | val2;
|
|
break;
|
|
case '&&':
|
|
valNew = (val1 && val2? 1 : 0);
|
|
break;
|
|
case '||':
|
|
valNew = (val1 || val2? 1 : 0);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
aVals.push(valNew|0);
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* parseExpression(sExp, fPrint)
|
|
*
|
|
* A quick-and-dirty expression parser. It takes an expression like:
|
|
*
|
|
* EDX+EDX*4+12345678
|
|
*
|
|
* and builds a value stack in aVals and a "binop" (binary operator) stack in aOps:
|
|
*
|
|
* aVals aOps
|
|
* ----- ----
|
|
* EDX +
|
|
* EDX *
|
|
* 4 +
|
|
* ...
|
|
*
|
|
* We pop 1 "binop" from aOps and 2 values from aVals whenever a "binop" of lower priority than its
|
|
* predecessor is encountered, evaluate, and push the result back onto aVals.
|
|
*
|
|
* Unary operators like '~' and ternary operators like '?:' are not supported; neither are parentheses.
|
|
*
|
|
* However, parseReference() now makes it possible to write parenthetical-style sub-expressions by using
|
|
* {...} (braces), as well as address references by using [...] (brackets).
|
|
*
|
|
* Why am I using braces instead of parentheses for sub-expressions? Because parseReference() serves
|
|
* multiple purposes, the other being reference replacement in message strings passing through replaceRegs(),
|
|
* and I didn't want parentheses taking on a new meaning in message strings.
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string|undefined} sExp
|
|
* @param {boolean} [fPrint] is true to print all resolved values, false for quiet parsing
|
|
* @return {number|undefined} numeric value, or undefined if sExp contains any undefined or invalid values
|
|
*/
|
|
Debugger8080.prototype.parseExpression = function(sExp, fPrint)
|
|
{
|
|
var value;
|
|
|
|
if (sExp) {
|
|
/*
|
|
* First process (and eliminate) any references, aka sub-expressions.
|
|
*/
|
|
sExp = this.parseReference(sExp);
|
|
|
|
var i = 0;
|
|
var fError = false;
|
|
var sExpOrig = sExp;
|
|
var aVals = [], aOps = [];
|
|
/*
|
|
* All browsers (including, I believe, IE9 and up) support the following idiosyncrasy of a regexp split():
|
|
* when the regexp uses a capturing pattern, the resulting array will include entries for all the pattern
|
|
* matches along with the non-matches. This effectively means that, in the set of expressions that we
|
|
* support, all even entries in asValues will contain "values" and all odd entries will contain "operators".
|
|
*
|
|
* And although I tried to list the supported operators in "precedential" order, bitwise operators must
|
|
* be out-of-order so that we don't mistakenly match either '>' or '<' when they're part of '>>' or '<<'.
|
|
*/
|
|
var regExp = /(\|\||&&|\||^|&|!=|==|>=|>>>|>>|>|<=|<<|<|-|\+|%|\/|\*)/;
|
|
var asValues = sExp.split(regExp);
|
|
while (i < asValues.length) {
|
|
var sValue = asValues[i++];
|
|
var cchValue = sValue.length;
|
|
var s = str.trim(sValue);
|
|
if (!s) {
|
|
fError = true;
|
|
break;
|
|
}
|
|
var v = this.parseValue(s, null, fPrint === false);
|
|
if (v === undefined) {
|
|
fError = true;
|
|
fPrint = false;
|
|
break;
|
|
}
|
|
aVals.push(v);
|
|
if (i == asValues.length) break;
|
|
var sOp = asValues[i++], cchOp = sOp.length;
|
|
this.assert(Debugger8080.aBinOpPrecedence[sOp] != null);
|
|
if (aOps.length && Debugger8080.aBinOpPrecedence[sOp] < Debugger8080.aBinOpPrecedence[aOps[aOps.length-1]]) {
|
|
this.evalExpression(aVals, aOps, 1);
|
|
}
|
|
aOps.push(sOp);
|
|
sExp = sExp.substr(cchValue + cchOp);
|
|
}
|
|
if (!this.evalExpression(aVals, aOps) || aVals.length != 1) {
|
|
fError = true;
|
|
}
|
|
if (!fError) {
|
|
value = aVals.pop();
|
|
if (fPrint) this.printValue(null, value);
|
|
} else {
|
|
if (fPrint) this.println("error parsing '" + sExpOrig + "' at character " + (sExpOrig.length - sExp.length));
|
|
}
|
|
}
|
|
return value;
|
|
};
|
|
|
|
/**
|
|
* parseReference(s)
|
|
*
|
|
* Returns the given string with any "{expression}" sequences replaced with the value of the expression,
|
|
* and any "[address]" references replaced with the contents of the address. Expressions are parsed BEFORE
|
|
* addresses. Owing to this function's simplistic parsing, nested braces/brackets are not supported
|
|
* (define intermediate variables if needed).
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string} s
|
|
* @return {string}
|
|
*/
|
|
Debugger8080.prototype.parseReference = function(s)
|
|
{
|
|
var a;
|
|
while (a = s.match(/\{(.*?)}/)) {
|
|
if (a[1].indexOf('{') >= 0) break; // unsupported nested brace(s)
|
|
var value = this.parseExpression(a[1]);
|
|
s = s.replace('{' + a[1] + '}', value != null? str.toHex(value) : "undefined");
|
|
}
|
|
while (a = s.match(/\[(.*?)]/)) {
|
|
if (a[1].indexOf('[') >= 0) break; // unsupported nested bracket(s)
|
|
var dbgAddr = this.parseAddr(a[1]);
|
|
s = s.replace('[' + a[1] + ']', dbgAddr? str.toHex(this.getWord(dbgAddr), 4) : "undefined");
|
|
}
|
|
return this.parseSysVars(s);
|
|
};
|
|
|
|
/**
|
|
* parseSysVars(s)
|
|
*
|
|
* Returns the given string with any recognized "$var" replaced with its value; eg:
|
|
*
|
|
* $ops: the number of opcodes executed since the last time it was displayed (or reset)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string} s
|
|
* @return {string}
|
|
*/
|
|
Debugger8080.prototype.parseSysVars = function(s)
|
|
{
|
|
var a;
|
|
while (a = s.match(/\$([a-z]+)/i)) {
|
|
var v = null;
|
|
switch(a[1].toLowerCase()) {
|
|
case "ops":
|
|
v = this.cOpcodes - this.cOpcodesStart;
|
|
break;
|
|
}
|
|
if (v == null) break;
|
|
s = s.replace(a[0], v.toString());
|
|
}
|
|
return s;
|
|
};
|
|
|
|
/**
|
|
* parseValue(sValue, sName, fQuiet)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string|undefined} sValue
|
|
* @param {string|null} [sName] is the name of the value, if any
|
|
* @param {boolean} [fQuiet]
|
|
* @return {number|undefined} numeric value, or undefined if sValue is either undefined or invalid
|
|
*/
|
|
Debugger8080.prototype.parseValue = function(sValue, sName, fQuiet)
|
|
{
|
|
var value;
|
|
if (sValue !== undefined) {
|
|
var iReg = this.getRegIndex(sValue);
|
|
if (iReg >= 0) {
|
|
value = this.getRegValue(iReg);
|
|
} else {
|
|
value = this.getVariable(sValue);
|
|
if (value === undefined) value = str.parseInt(sValue);
|
|
}
|
|
if (value === undefined && !fQuiet) this.println("invalid " + (sName? sName : "value") + ": " + sValue);
|
|
} else {
|
|
if (!fQuiet) this.println("missing " + (sName || "value"));
|
|
}
|
|
return value;
|
|
};
|
|
|
|
/**
|
|
* printValue(sVar, value)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string|null} sVar
|
|
* @param {number|undefined} value
|
|
* @return {boolean} true if value defined, false if not
|
|
*/
|
|
Debugger8080.prototype.printValue = function(sVar, value)
|
|
{
|
|
var sValue;
|
|
var fDefined = false;
|
|
if (value !== undefined) {
|
|
fDefined = true;
|
|
sValue = str.toHexLong(value) + " " + value + ". (" + str.toBinBytes(value) + ")";
|
|
}
|
|
sVar = (sVar != null? (sVar + ": ") : "");
|
|
this.println(sVar + sValue);
|
|
return fDefined;
|
|
};
|
|
|
|
/**
|
|
* printVariable(sVar)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string} [sVar]
|
|
* @return {boolean} true if all value(s) defined, false if not
|
|
*/
|
|
Debugger8080.prototype.printVariable = function(sVar)
|
|
{
|
|
if (sVar) {
|
|
return this.printValue(sVar, this.aVariables[sVar]);
|
|
}
|
|
var cVariables = 0;
|
|
for (sVar in this.aVariables) {
|
|
this.printValue(sVar, this.aVariables[sVar]);
|
|
cVariables++;
|
|
}
|
|
return cVariables > 0;
|
|
};
|
|
|
|
/**
|
|
* delVariable(sVar)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string} sVar
|
|
*/
|
|
Debugger8080.prototype.delVariable = function(sVar)
|
|
{
|
|
delete this.aVariables[sVar];
|
|
};
|
|
|
|
/**
|
|
* getVariable(sVar)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string} sVar
|
|
* @return {number|undefined}
|
|
*/
|
|
Debugger8080.prototype.getVariable = function(sVar)
|
|
{
|
|
return this.aVariables[sVar];
|
|
};
|
|
|
|
/**
|
|
* setVariable(sVar, value)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string} sVar
|
|
* @param {number} value
|
|
*/
|
|
Debugger8080.prototype.setVariable = function(sVar, value)
|
|
{
|
|
this.aVariables[sVar] = value;
|
|
};
|
|
|
|
/**
|
|
* comparePairs(p1, p2)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {number|string|Array|Object} p1
|
|
* @param {number|string|Array|Object} p2
|
|
* @return {number}
|
|
*/
|
|
Debugger8080.prototype.comparePairs = function(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 {Debugger8080}
|
|
* @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)
|
|
*/
|
|
Debugger8080.prototype.addSymbols = function(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 {Debugger8080}
|
|
*/
|
|
Debugger8080.prototype.dumpSymbols = function()
|
|
{
|
|
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.toHexOffset(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 {Debugger8080}
|
|
* @param {DbgAddr8080} dbgAddr
|
|
* @param {boolean} [fNearest]
|
|
* @return {Array} where [0] == symbol name, [1] == symbol value, [2] == any annotation, and [3] == any associated comment
|
|
*/
|
|
Debugger8080.prototype.findSymbol = function(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 aSymbolTable for sSymbol, and if found, return a dbgAddr (same as parseAddr())
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string} sSymbol
|
|
* @return {DbgAddr8080|undefined}
|
|
*/
|
|
Debugger8080.prototype.findSymbolAddr = function(sSymbol)
|
|
{
|
|
var dbgAddr;
|
|
if (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 !== undefined) {
|
|
var offSymbol = symbol['o'];
|
|
if (offSymbol !== undefined) {
|
|
/*
|
|
* 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.
|
|
*/
|
|
dbgAddr = this.newAddr(offSymbol);
|
|
}
|
|
/*
|
|
* The symbol matched, but it wasn't for an address (no 'o' offset), and there's no point
|
|
* looking any farther, since each symbol appears only once, so we indicate it's an unknown symbol.
|
|
*/
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
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
|
|
*/
|
|
Debugger8080.prototype.returnSymbol = function(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 {Debugger8080}
|
|
*/
|
|
Debugger8080.prototype.doHelp = function()
|
|
{
|
|
var s = "commands:";
|
|
for (var sCommand in Debugger8080.COMMANDS) {
|
|
s += '\n' + str.pad(sCommand, 9) + Debugger8080.COMMANDS[sCommand];
|
|
}
|
|
if (!this.checksEnabled()) s += "\nnote: frequency/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 {Debugger8080}
|
|
* @param {Array.<string>} asArgs is the complete argument array, beginning with the "a" command in asArgs[0]
|
|
*/
|
|
Debugger8080.prototype.doAssemble = function(asArgs)
|
|
{
|
|
var dbgAddr = this.parseAddr(asArgs[1], true);
|
|
if (!dbgAddr) return;
|
|
|
|
this.dbgAddrAssemble = dbgAddr;
|
|
if (asArgs[2] === undefined) {
|
|
this.println("begin assemble at " + this.toHexAddr(dbgAddr));
|
|
this.fAssemble = true;
|
|
this.cpu.updateCPU();
|
|
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 [a] set exec breakpoint on linear addr [a]
|
|
* br [a] set read breakpoint on linear addr [a]
|
|
* bw [a] set write breakpoint on linear addr [a]
|
|
* bc [a] clear breakpoint on linear addr [a] (use "*" for all breakpoints)
|
|
* bl list breakpoints
|
|
*
|
|
* to which we have recently added the following I/O breakpoint commands:
|
|
*
|
|
* bi [p] toggle input breakpoint on port [p] (use "*" for all input ports)
|
|
* bo [p] toggle output breakpoint on port [p] (use "*" for all output ports)
|
|
*
|
|
* These two new commands operate as toggles so that if "*" is used to trap all input (or output),
|
|
* you can also use these commands to NOT trap specific ports.
|
|
*
|
|
* bn [n] break after [n] instructions
|
|
*
|
|
* TODO: Update the "bl" command to include any/all I/O breakpoints, and the "bc" command to
|
|
* clear them. Because "bi" and "bo" commands are piggy-backing on Bus functions, those breakpoints
|
|
* are currently outside the realm of what the "bl" and "bc" commands are aware of.
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string} sCmd
|
|
* @param {string|undefined} [sAddr]
|
|
* @param {string} [sOptions] (the rest of the breakpoint command-line)
|
|
*/
|
|
Debugger8080.prototype.doBreak = function(sCmd, sAddr, sOptions)
|
|
{
|
|
if (sAddr == '?') {
|
|
this.println("breakpoint commands:");
|
|
this.println("\tbi [p]\ttoggle break on input port [p]");
|
|
this.println("\tbo [p]\ttoggle break on output port [p]");
|
|
this.println("\tbp [a]\tset exec breakpoint at addr [a]");
|
|
this.println("\tbr [a]\tset read breakpoint at addr [a]");
|
|
this.println("\tbw [a]\tset write breakpoint at addr [a]");
|
|
this.println("\tbc [a]\tclear breakpoint at addr [a]");
|
|
this.println("\tbl\tlist all breakpoints");
|
|
this.println("\tbn [n]\tbreak after [n] 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') {
|
|
this.nBreakIns = this.parseValue(sAddr);
|
|
this.println("break after " + this.nBreakIns + " 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;
|
|
}
|
|
|
|
sAddr = str.toHexWord(dbgAddr.addr);
|
|
|
|
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.toHexAddr(dbgAddr));
|
|
return;
|
|
}
|
|
|
|
if (sParm == 'i') {
|
|
this.println("breakpoint " + (this.bus.addPortInputBreak(dbgAddr.addr)? "enabled" : "cleared") + ": port " + sAddr + " (input)");
|
|
return;
|
|
}
|
|
|
|
if (sParm == 'o') {
|
|
this.println("breakpoint " + (this.bus.addPortOutputBreak(dbgAddr.addr)? "enabled" : "cleared") + ": port " + sAddr + " (output)");
|
|
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 {Debugger8080}
|
|
* @param {string} [sCmd] (eg, "cls" or "clear")
|
|
*/
|
|
Debugger8080.prototype.doClear = function(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, in hex, which we convert to the appropriate number
|
|
* of lines, because we always display whole lines. If the length is omitted/undefined, it defaults to 0x80 (128.)
|
|
* bytes, which normally translates to 8 lines.
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {Array.<string>} asArgs (formerly sCmd, [sAddr], [sLen] and [sBytes])
|
|
*/
|
|
Debugger8080.prototype.doDump = function(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 Debugger8080.MESSAGES) {
|
|
if (this.afnDumpers[m]) {
|
|
if (sDumpers) sDumpers += ',';
|
|
sDumpers = sDumpers + m;
|
|
}
|
|
}
|
|
sDumpers += ",state,symbols";
|
|
this.println("dump memory commands:");
|
|
this.println("\tdb [a] [#] dump # bytes at address a");
|
|
this.println("\tdw [a] [#] dump # words at address a");
|
|
this.println("\tdd [a] [#] dump # dwords at address a");
|
|
this.println("\tdh [#] [#] dump # instructions from history");
|
|
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();
|
|
this.println(sState);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (sAddr == "symbols") {
|
|
this.dumpSymbols();
|
|
return;
|
|
}
|
|
|
|
if (sCmd == "d") {
|
|
for (m in Debugger8080.MESSAGES) {
|
|
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 || "db";
|
|
} else {
|
|
this.sCmdDumpPrev = sCmd;
|
|
}
|
|
|
|
if (sCmd == "dh") {
|
|
this.dumpHistory(sAddr, sLen);
|
|
return;
|
|
}
|
|
|
|
var dbgAddr = this.parseAddr(sAddr);
|
|
if (!dbgAddr) return;
|
|
|
|
var len = 0; // 0 is not a default; it triggers the appropriate default below
|
|
if (sLen) {
|
|
if (sLen.charAt(0) == 'l') {
|
|
sLen = sLen.substr(1) || sBytes;
|
|
}
|
|
len = this.parseValue(sLen) >>> 0; // negative lengths not allowed
|
|
if (len > 0x10000) len = 0x10000; // prevent bad user (or variable) input from producing excessive output
|
|
}
|
|
|
|
var sDump = "";
|
|
var size = (sCmd == "dd"? 4 : (sCmd == "dw"? 2 : 1));
|
|
var cb = (size * len) || 128;
|
|
var cLines = ((cb + 15) >> 4) || 1;
|
|
|
|
while (cLines-- && cb > 0) {
|
|
var data = 0, iByte = 0, i;
|
|
var sData = "", sChars = "";
|
|
sAddr = this.toHexAddr(dbgAddr);
|
|
for (i = 16; i > 0 && cb > 0; i--) {
|
|
var b = this.getByte(dbgAddr, 1);
|
|
data |= (b << (iByte++ << 3));
|
|
if (iByte == size) {
|
|
sData += str.toHex(data, size * 2);
|
|
sData += (size == 1? (i == 9? '-' : ' ') : " ");
|
|
data = iByte = 0;
|
|
}
|
|
sChars += (b >= 32 && b < 128? String.fromCharCode(b) : '.');
|
|
cb--;
|
|
}
|
|
if (sDump) sDump += '\n';
|
|
sDump += sAddr + " " + sData + ((i == 0)? (' ' + sChars) : "");
|
|
}
|
|
|
|
if (sDump) this.println(sDump);
|
|
this.dbgAddrNextData = dbgAddr;
|
|
};
|
|
|
|
/**
|
|
* doEdit(asArgs)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {Array.<string>} asArgs
|
|
*/
|
|
Debugger8080.prototype.doEdit = function(asArgs)
|
|
{
|
|
var size = 1;
|
|
var mask = 0xff;
|
|
var fnGet = this.getByte;
|
|
var fnSet = this.setByte;
|
|
if (asArgs[0] == "ew") {
|
|
size = 2;
|
|
mask = 0xffff;
|
|
fnGet = this.getShort;
|
|
fnSet = this.setShort;
|
|
}
|
|
var cch = size << 1;
|
|
|
|
var sAddr = asArgs[1];
|
|
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");
|
|
}
|
|
var vOld = fnGet.call(this, dbgAddr);
|
|
this.println("changing " + this.toHexAddr(dbgAddr) + " from 0x" + str.toHex(vOld, cch) + " to 0x" + str.toHex(vNew, cch));
|
|
fnSet.call(this, dbgAddr, vNew, size);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* doFreqs(sParm)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string|undefined} sParm
|
|
*/
|
|
Debugger8080.prototype.doFreqs = function(sParm)
|
|
{
|
|
if (sParm == '?') {
|
|
this.println("frequency commands:");
|
|
this.println("\tclear\tclear all frequency counts");
|
|
return;
|
|
}
|
|
var i;
|
|
var cData = 0;
|
|
if (this.aaOpcodeCounts) {
|
|
if (sParm == "clear") {
|
|
for (i = 0; i < this.aaOpcodeCounts.length; i++)
|
|
this.aaOpcodeCounts[i] = [i, 0];
|
|
this.println("frequency data cleared");
|
|
cData++;
|
|
}
|
|
else if (sParm !== undefined) {
|
|
this.println("unknown frequency command: " + sParm);
|
|
cData++;
|
|
}
|
|
else {
|
|
var aaSortedOpcodeCounts = this.aaOpcodeCounts.slice();
|
|
aaSortedOpcodeCounts.sort(function(p, q) {
|
|
return q[1] - p[1];
|
|
});
|
|
var asOpcodes = this.style != Debugger8080.STYLE_8086? Debugger8080.INS_NAMES : Debugger8080.INS_NAMES_8086;
|
|
for (i = 0; i < aaSortedOpcodeCounts.length; i++) {
|
|
var bOpcode = aaSortedOpcodeCounts[i][0];
|
|
var cFreq = aaSortedOpcodeCounts[i][1];
|
|
if (cFreq) {
|
|
this.println((asOpcodes[this.aaOpDescs[bOpcode][0]] + " ").substr(0, 5) + " (" + str.toHexByte(bOpcode) + "): " + cFreq + " times");
|
|
cData++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!cData) {
|
|
this.println("no frequency data available");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* doHalt(fQuiet)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {boolean} [fQuiet]
|
|
*/
|
|
Debugger8080.prototype.doHalt = function(fQuiet)
|
|
{
|
|
var sMsg;
|
|
if (this.flags.fRunning) {
|
|
sMsg = "halting";
|
|
this.stopCPU();
|
|
} else {
|
|
if (this.isBusy(true)) return;
|
|
sMsg = "already halted";
|
|
}
|
|
if (!fQuiet) this.println(sMsg);
|
|
};
|
|
|
|
/**
|
|
* 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 {Debugger8080}
|
|
* @param {string} sCmd
|
|
* @param {boolean} [fQuiet]
|
|
* @return {boolean} true if expression is non-zero, false if zero (or undefined due to a parse error)
|
|
*/
|
|
Debugger8080.prototype.doIf = function(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 {Debugger8080}
|
|
* @param {Array.<string>} asArgs
|
|
* @return {boolean} true only if the instruction info command ("n") is supported
|
|
*/
|
|
Debugger8080.prototype.doInfo = function(asArgs)
|
|
{
|
|
if (DEBUG) {
|
|
this.println("msPerYield: " + this.cpu.aCounts.msPerYield);
|
|
this.println("nCyclesPerBurst: " + this.cpu.aCounts.nCyclesPerBurst);
|
|
this.println("nCyclesPerYield: " + this.cpu.aCounts.nCyclesPerYield);
|
|
this.println("nCyclesPerVideoUpdate: " + this.cpu.aCounts.nCyclesPerVideoUpdate);
|
|
this.println("nCyclesPerStatusUpdate: " + this.cpu.aCounts.nCyclesPerStatusUpdate);
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* doInput(sPort)
|
|
*
|
|
* Simulate a 1-byte port input operation.
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string|undefined} sPort
|
|
*/
|
|
Debugger8080.prototype.doInput = function(sPort)
|
|
{
|
|
if (!sPort || sPort == '?') {
|
|
this.println("input commands:");
|
|
this.println("\ti [p]\tread port [p]");
|
|
/*
|
|
* TODO: Regarding this warning, consider adding an "unchecked" version of
|
|
* bus.checkPortInputNotify(), since all Debugger memory accesses are unchecked, too.
|
|
*
|
|
* All port I/O handlers ARE aware when the Debugger is calling (addrFrom is undefined),
|
|
* but changing them all to be non-destructive would take time, and situations where you
|
|
* actually want to affect the hardware state are just as likely as not....
|
|
*/
|
|
this.println("warning: port accesses can affect hardware state");
|
|
return;
|
|
}
|
|
var port = this.parseValue(sPort);
|
|
if (port !== undefined) {
|
|
var bIn = this.bus.checkPortInputNotify(port, 1);
|
|
this.println(str.toHexWord(port) + ": " + str.toHexByte(bIn));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* doInt(sLevel)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string} sLevel
|
|
* @return {boolean} true if success, false if error
|
|
*/
|
|
Debugger8080.prototype.doInt = function(sLevel)
|
|
{
|
|
if (!this.cpu.getIF()) {
|
|
this.println("interrupts disabled (use rif=1 to enable)");
|
|
return false;
|
|
}
|
|
var nLevel = this.parseExpression(sLevel);
|
|
if (nLevel == null) return false;
|
|
this.println("requesting interrupt level " + nLevel);
|
|
this.cpu.requestINTR(nLevel);
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* 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 {Debugger8080}
|
|
* @param {string} sCmd
|
|
* @return {boolean} true if valid "var" assignment, false if not
|
|
*/
|
|
Debugger8080.prototype.doVar = function(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 {Debugger8080}
|
|
* @param {string} sAddr
|
|
* @param {boolean} [fPrint]
|
|
* @return {string|null}
|
|
*/
|
|
Debugger8080.prototype.doList = function(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.toHexOffset(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.toHexOffset(aSymbol[5]) + ')' + sDelta;
|
|
if (fPrint) this.println(s);
|
|
if (!sSymbol) sSymbol = s;
|
|
}
|
|
} else {
|
|
if (fPrint) this.println("no symbols");
|
|
}
|
|
}
|
|
return sSymbol;
|
|
};
|
|
|
|
/**
|
|
* doMessages(asArgs)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {Array.<string>} asArgs
|
|
*/
|
|
Debugger8080.prototype.doMessages = function(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) & ~(Messages8080.HALT | Messages8080.KEYS | Messages8080.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 Debugger8080.MESSAGES) {
|
|
if (sCategory == m) {
|
|
bitsMessage = Debugger8080.MESSAGES[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 == Messages8080.BUFFER) {
|
|
for (var i = 0; i < this.aMessageBuffer.length; i++) {
|
|
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 Debugger8080.MESSAGES) {
|
|
if (!sCategory || sCategory == m) {
|
|
var bitMessage = Debugger8080.MESSAGES[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 Messages8080.INT was turned on
|
|
};
|
|
|
|
/**
|
|
* doOptions(asArgs)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {Array.<string>} asArgs
|
|
*/
|
|
Debugger8080.prototype.doOptions = function(asArgs)
|
|
{
|
|
switch (asArgs[1]) {
|
|
case "8080":
|
|
this.style = Debugger8080.STYLE_8080;
|
|
break;
|
|
|
|
case "8086":
|
|
this.style = Debugger8080.STYLE_8086;
|
|
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.aCounts.nCyclesChecksumInterval = nCycles;
|
|
break;
|
|
case "start":
|
|
this.cpu.aCounts.nCyclesChecksumStart = nCycles;
|
|
break;
|
|
case "stop":
|
|
this.cpu.aCounts.nCyclesChecksumStop = nCycles;
|
|
break;
|
|
default:
|
|
this.println("unknown cs option");
|
|
return;
|
|
}
|
|
if (nCycles !== undefined) {
|
|
this.cpu.resetChecksum();
|
|
}
|
|
this.println("checksums " + (this.cpu.flags.fChecksum? "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;
|
|
|
|
case "?":
|
|
this.println("debugger options:");
|
|
this.println("\t8080\t\tselect 8080-style mnemonics");
|
|
this.println("\t8086\t\tselect 8086-style mnemonics");
|
|
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;
|
|
|
|
default:
|
|
if (asArgs[1]) {
|
|
this.println("unknown option: " + asArgs[1]);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
this.println(this.style + "-style mnemonics enabled");
|
|
};
|
|
|
|
/**
|
|
* doOutput(sPort, sByte)
|
|
*
|
|
* Simulate a 1-byte port output operation.
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string|undefined} sPort
|
|
* @param {string|undefined} sByte (string representation of 1 byte)
|
|
*/
|
|
Debugger8080.prototype.doOutput = function(sPort, sByte)
|
|
{
|
|
if (!sPort || sPort == '?') {
|
|
this.println("output commands:");
|
|
this.println("\to [p] [b]\twrite byte [b] to port [p]");
|
|
/*
|
|
* TODO: Regarding this warning, consider adding an "unchecked" version of
|
|
* bus.checkPortOutputNotify(), since all Debugger memory accesses are unchecked, too.
|
|
*
|
|
* All port I/O handlers ARE aware when the Debugger is calling (addrFrom is undefined),
|
|
* but changing them all to be non-destructive would take time, and situations where you
|
|
* actually want to affect the hardware state are just as likely as not....
|
|
*/
|
|
this.println("warning: port accesses can affect hardware state");
|
|
return;
|
|
}
|
|
var port = this.parseValue(sPort, "port #");
|
|
var bOut = this.parseValue(sByte);
|
|
if (port !== undefined && bOut !== undefined) {
|
|
this.bus.checkPortOutputNotify(port, 1, bOut);
|
|
this.println(str.toHexWord(port) + ": " + str.toHexByte(bOut));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* doRegisters(asArgs, fInstruction)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {Array.<string>} [asArgs]
|
|
* @param {boolean} [fInstruction] (true to include the current instruction; default is true)
|
|
*/
|
|
Debugger8080.prototype.doRegisters = function(asArgs, fInstruction)
|
|
{
|
|
if (asArgs && asArgs[1] == '?') {
|
|
this.println("register commands:");
|
|
this.println("\tr\tdump registers");
|
|
this.println("\trx [#]\tset flag or register x to [#]");
|
|
return;
|
|
}
|
|
|
|
var cpu = this.cpu;
|
|
if (fInstruction == null) fInstruction = true;
|
|
|
|
if (asArgs != null && asArgs.length > 1) {
|
|
var sReg = asArgs[1];
|
|
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 fValid = false;
|
|
var w = this.parseExpression(sValue);
|
|
|
|
if (w !== undefined) {
|
|
fValid = true;
|
|
var sRegMatch = sReg.toUpperCase();
|
|
switch (sRegMatch) {
|
|
case "A":
|
|
cpu.regA = w & 0xff;
|
|
break;
|
|
case "B":
|
|
cpu.regB = w & 0xff;
|
|
break;
|
|
case "BC":
|
|
cpu.regB = ((w >> 8) & 0xff);
|
|
/* falls through */
|
|
case "C":
|
|
cpu.regC = w & 0xff;
|
|
break;
|
|
case "D":
|
|
cpu.regD = w & 0xff;
|
|
break;
|
|
case "DE":
|
|
cpu.regD = ((w >> 8) & 0xff);
|
|
/* falls through */
|
|
case "E":
|
|
cpu.regE = w & 0xff;
|
|
break;
|
|
case "H":
|
|
cpu.regH = w & 0xff;
|
|
break;
|
|
case "HL":
|
|
cpu.regH = ((w >> 8) & 0xff);
|
|
/* falls through */
|
|
case "L":
|
|
cpu.regL = w & 0xff;
|
|
break;
|
|
case "SP":
|
|
cpu.setSP(w);
|
|
break;
|
|
case "PC":
|
|
cpu.setPC(w);
|
|
this.dbgAddrNextCode = this.newAddr(cpu.getPC());
|
|
break;
|
|
case "PS":
|
|
cpu.setPS(w);
|
|
break;
|
|
case "PSW":
|
|
cpu.setPSW(w);
|
|
break;
|
|
case "CF":
|
|
if (w) cpu.setCF(); else cpu.clearCF();
|
|
break;
|
|
case "PF":
|
|
if (w) cpu.setPF(); else cpu.clearPF();
|
|
break;
|
|
case "AF":
|
|
if (w) cpu.setAF(); else cpu.clearAF();
|
|
break;
|
|
case "ZF":
|
|
if (w) cpu.setZF(); else cpu.clearZF();
|
|
break;
|
|
case "SF":
|
|
if (w) cpu.setSF(); else cpu.clearSF();
|
|
break;
|
|
case "IF":
|
|
if (w) cpu.setIF(); else cpu.clearIF();
|
|
break;
|
|
default:
|
|
this.println("unknown register: " + sReg);
|
|
return;
|
|
}
|
|
}
|
|
if (!fValid) {
|
|
this.println("invalid value: " + sValue);
|
|
return;
|
|
}
|
|
cpu.updateCPU();
|
|
this.println("updated registers:");
|
|
}
|
|
|
|
this.println(this.getRegDump());
|
|
|
|
if (fInstruction) {
|
|
this.dbgAddrNextCode = this.newAddr(cpu.getPC());
|
|
this.doUnassemble(this.toHexAddr(this.dbgAddrNextCode));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* doRun(sCmd, sAddr, sOptions, fQuiet)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string} sCmd
|
|
* @param {string|undefined} [sAddr]
|
|
* @param {string} [sOptions] (the rest of the breakpoint command-line)
|
|
* @param {boolean} [fQuiet]
|
|
*/
|
|
Debugger8080.prototype.doRun = function(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);
|
|
}
|
|
if (!this.runCPU(true)) {
|
|
if (!fQuiet) this.println("cpu busy or unavailable, run command ignored");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 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 {Debugger8080}
|
|
* @param {string} sCmd
|
|
*/
|
|
Debugger8080.prototype.doPrint = function(sCmd)
|
|
{
|
|
sCmd = str.trim(sCmd);
|
|
var a = sCmd.match(/^(['"])(.*?)\1$/);
|
|
if (!a) {
|
|
this.parseExpression(sCmd, true);
|
|
} else {
|
|
this.println(this.replaceRegs(a[2]));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* doStep(sCmd)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string} [sCmd] "p" or "pr"
|
|
*/
|
|
Debugger8080.prototype.doStep = function(sCmd)
|
|
{
|
|
var fCallStep = true;
|
|
var fRegs = (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 + fRegs;
|
|
if (!this.nStep) {
|
|
var dbgAddr = this.newAddr(this.cpu.getPC());
|
|
var bOpcode = this.getByte(dbgAddr);
|
|
|
|
switch (bOpcode) {
|
|
case CPUDef8080.OPCODE.CALL:
|
|
if (fCallStep) {
|
|
this.nStep = nStep;
|
|
this.incAddr(dbgAddr, 3);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (this.nStep) {
|
|
this.setTempBreakpoint(dbgAddr);
|
|
if (!this.runCPU()) {
|
|
if (this.cmp) this.cmp.updateFocus();
|
|
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(fRegs? "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 {Debugger8080}
|
|
* @param {DbgAddr8080} dbgAddr
|
|
* @return {string|null} CALL instruction at or near dbgAddr, or null if none
|
|
*/
|
|
Debugger8080.prototype.getCall = function(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("CALL") >= 0) {
|
|
/*
|
|
* Verify that the length of this CALL (or INT), when added to the address of the CALL (or INT),
|
|
* 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--;
|
|
}
|
|
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 {Debugger8080}
|
|
* @param {string} [sCmd]
|
|
* @param {string} [sAddr] (not used yet)
|
|
*/
|
|
Debugger8080.prototype.doStackTrace = function(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.toHexAddr(dbgAddrStack));
|
|
|
|
while (cFrames < nFrames) {
|
|
var sCall = null, sCallPrev = null, cTests = 256;
|
|
while ((dbgAddrStack.addr >>> 0) < 0x10000) {
|
|
dbgAddrCall.addr = this.getWord(dbgAddrStack, true);
|
|
/*
|
|
* 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;
|
|
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.toHexAddr(dbgAddrStack)); // + " return=" + this.toHexAddr(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.
|
|
*
|
|
* 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 {Debugger8080}
|
|
* @param {string} [sCmd] ("t", "tc", or "tr")
|
|
* @param {string} [sCount] # of instructions to step
|
|
*/
|
|
Debugger8080.prototype.doTrace = function(sCmd, sCount)
|
|
{
|
|
var dbg = this;
|
|
var fRegs = (sCmd != "t");
|
|
var nCount = this.parseValue(sCount, null, true) || 1;
|
|
var nCycles = (nCount == 1? 0 : 1);
|
|
if (sCmd == "tc") {
|
|
nCycles = nCount;
|
|
nCount = 1;
|
|
}
|
|
web.onCountRepeat(
|
|
nCount,
|
|
function onCountStep() {
|
|
return dbg.setBusy(true) && dbg.stepCPU(nCycles, fRegs, false);
|
|
},
|
|
function onCountStepComplete() {
|
|
/*
|
|
* We explicitly called stepCPU() with fUpdateCPU === false, because repeatedly
|
|
* calling updateCPU() can be very slow, especially when fDisplayLiveRegs is true,
|
|
* so once the repeat count has been exhausted, we must perform a final updateCPU().
|
|
*/
|
|
dbg.cpu.updateCPU();
|
|
dbg.setBusy(false);
|
|
}
|
|
);
|
|
};
|
|
|
|
/**
|
|
* doUnassemble(sAddr, sAddrEnd, n)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string} [sAddr]
|
|
* @param {string} [sAddrEnd]
|
|
* @param {number} [n]
|
|
*/
|
|
Debugger8080.prototype.doUnassemble = function(sAddr, sAddrEnd, n)
|
|
{
|
|
var dbgAddr = this.parseAddr(sAddr, true);
|
|
if (!dbgAddr) return;
|
|
|
|
if (n === undefined) n = 1;
|
|
|
|
var cb = 0x100;
|
|
if (sAddrEnd !== undefined) {
|
|
|
|
var dbgAddrEnd = this.parseAddr(sAddrEnd, true);
|
|
if (!dbgAddrEnd || dbgAddrEnd.addr < dbgAddr.addr) return;
|
|
|
|
cb = dbgAddrEnd.addr - dbgAddr.addr;
|
|
if (!DEBUG && cb > 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;
|
|
}
|
|
n = -1;
|
|
}
|
|
|
|
var cLines = 0;
|
|
var sInstruction;
|
|
|
|
while (cb > 0 && n--) {
|
|
|
|
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] && n) {
|
|
if (!cLines && n || 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;
|
|
cb -= dbgAddr.addr - addr;
|
|
cLines++;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* parseCommand(sCmd, fSave, chSep)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string|undefined} sCmd
|
|
* @param {boolean} [fSave] is true to save the command, false if not
|
|
* @param {string} [chSep] is the command separator character (default is ';')
|
|
* @return {Array.<string>}
|
|
*/
|
|
Debugger8080.prototype.parseCommand = function(sCmd, fSave, chSep)
|
|
{
|
|
if (fSave) {
|
|
if (!sCmd) {
|
|
if (this.fAssemble) {
|
|
sCmd = "end";
|
|
} else {
|
|
sCmd = this.aPrevCmds[this.iPrevCmd+1];
|
|
}
|
|
} else {
|
|
if (this.iPrevCmd < 0 && this.aPrevCmds.length) {
|
|
this.iPrevCmd = 0;
|
|
}
|
|
if (this.iPrevCmd < 0 || sCmd != this.aPrevCmds[this.iPrevCmd]) {
|
|
this.aPrevCmds.splice(0, 0, sCmd);
|
|
this.iPrevCmd = 0;
|
|
}
|
|
this.iPrevCmd--;
|
|
}
|
|
}
|
|
var a = [];
|
|
if (sCmd) {
|
|
/*
|
|
* With the introduction of breakpoint commands (ie, quoted command sequences
|
|
* associated with a breakpoint), we can no longer perform simplistic splitting.
|
|
*
|
|
* a = sCmd.split(chSep || ';');
|
|
* for (var i = 0; i < a.length; i++) a[i] = str.trim(a[i]);
|
|
*
|
|
* We may now split on semi-colons ONLY if they are outside a quoted sequence.
|
|
*
|
|
* Also, to allow quoted strings *inside* breakpoint commands, we first replace all
|
|
* DOUBLE double-quotes with single quotes.
|
|
*/
|
|
sCmd = sCmd.toLowerCase().replace(/""/g, "'");
|
|
|
|
var iPrev = 0;
|
|
var chQuote = null;
|
|
chSep = chSep || ';';
|
|
/*
|
|
* NOTE: Processing charAt() up to and INCLUDING length is not a typo; we're taking
|
|
* advantage of the fact that charAt() with an invalid index returns an empty string,
|
|
* allowing us to use the same substring() call to capture the final portion of sCmd.
|
|
*
|
|
* In a sense, it allows us to pretend that the string ends with a zero terminator.
|
|
*/
|
|
for (var i = 0; i <= sCmd.length; i++) {
|
|
var ch = sCmd.charAt(i);
|
|
if (ch == '"' || ch == "'") {
|
|
if (!chQuote) {
|
|
chQuote = ch;
|
|
} else if (ch == chQuote) {
|
|
chQuote = null;
|
|
}
|
|
}
|
|
else if (ch == chSep && !chQuote || !ch) {
|
|
/*
|
|
* Recall that substring() accepts starting (inclusive) and ending (exclusive)
|
|
* indexes, whereas substr() accepts a starting index and a length. We need the former.
|
|
*/
|
|
a.push(str.trim(sCmd.substring(iPrev, i)));
|
|
iPrev = i + 1;
|
|
}
|
|
}
|
|
}
|
|
return a;
|
|
};
|
|
|
|
/**
|
|
* shiftArgs(asArgs)
|
|
*
|
|
* Used with any command (eg, "r") that allows but doesn't require whitespace between command and first argument.
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {Array.<string>} asArgs
|
|
* @return {Array.<string>}
|
|
*/
|
|
Debugger8080.prototype.shiftArgs = function(asArgs)
|
|
{
|
|
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 {Debugger8080}
|
|
* @param {string} sCmd
|
|
* @param {boolean} [fQuiet]
|
|
* @return {boolean} true if command processed, false if unrecognized
|
|
*/
|
|
Debugger8080.prototype.doCommand = function(sCmd, fQuiet)
|
|
{
|
|
var result = true;
|
|
|
|
try {
|
|
if (!sCmd.length || sCmd == "end") {
|
|
if (this.fAssemble) {
|
|
this.println("ended assemble at " + this.toHexAddr(this.dbgAddrAssemble));
|
|
this.dbgAddrNextCode = this.dbgAddrAssemble;
|
|
this.fAssemble = false;
|
|
}
|
|
sCmd = "";
|
|
}
|
|
else if (!fQuiet) {
|
|
var sPrompt = ">> ";
|
|
this.println(sPrompt + 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.toHexAddr(this.dbgAddrAssemble) + ' ' + sCmd;
|
|
}
|
|
|
|
var asArgs = this.shiftArgs(sCmd.replace(/ +/g, ' ').split(' '));
|
|
|
|
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 'f':
|
|
this.doFreqs(asArgs[1]);
|
|
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;
|
|
}
|
|
if (asArgs[0] == "int") {
|
|
if (!this.doInt(asArgs[1])) {
|
|
result = false;
|
|
}
|
|
break;
|
|
}
|
|
this.doInput(asArgs[1]);
|
|
break;
|
|
case 'k':
|
|
this.doStackTrace(asArgs[0], asArgs[1]);
|
|
break;
|
|
case 'l':
|
|
if (asArgs[0] == "ln") {
|
|
this.doList(asArgs[1], true);
|
|
break;
|
|
}
|
|
break;
|
|
case 'm':
|
|
this.doMessages(asArgs);
|
|
break;
|
|
case 'o':
|
|
this.doOutput(asArgs[1], asArgs[2]);
|
|
break;
|
|
case 'p':
|
|
if (asArgs[0] == "print") {
|
|
this.doPrint(sCmd.substr(5));
|
|
break;
|
|
}
|
|
this.doStep(asArgs[0]);
|
|
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;
|
|
}
|
|
this.println((PC8080.APPNAME || "PC8080") + " version " + (XMLVERSION || PC8080.APPVERSION) + " (" + this.cpu.model + (PC8080.COMPILED? ",RELEASE" : (PC8080.DEBUG? ",DEBUG" : ",NODEBUG")) + (PC8080.TYPEDARRAYS? ",TYPEDARRAYS" : (PC8080.BYTEARRAYS? ",BYTEARRAYS" : ",LONGARRAYS")) + ')');
|
|
this.println(web.getUserAgent());
|
|
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:
|
|
this.println("unknown command: " + sCmd);
|
|
result = false;
|
|
break;
|
|
}
|
|
}
|
|
} catch(e) {
|
|
this.println("debugger error: " + (e.stack || e.message));
|
|
result = false;
|
|
}
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* doCommands(sCmds, fSave)
|
|
*
|
|
* @this {Debugger8080}
|
|
* @param {string} sCmds
|
|
* @param {boolean} [fSave]
|
|
* @return {boolean} true if all commands processed, false if not
|
|
*/
|
|
Debugger8080.prototype.doCommands = function(sCmds, fSave)
|
|
{
|
|
var a = this.parseCommand(sCmds, fSave);
|
|
for (var s in a) {
|
|
if (!this.doCommand(a[s])) return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Debugger8080.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.
|
|
*/
|
|
Debugger8080.init = function()
|
|
{
|
|
var aeDbg = Component.getElementsByClass(document, PC8080.APPCLASS, "debugger");
|
|
for (var iDbg = 0; iDbg < aeDbg.length; iDbg++) {
|
|
var eDbg = aeDbg[iDbg];
|
|
var parmsDbg = Component.getComponentParms(eDbg);
|
|
var dbg = new Debugger8080(parmsDbg);
|
|
Component.bindComponentControls(dbg, eDbg, PC8080.APPCLASS);
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Initialize every Debugger module on the page (as IF there's ever going to be more than one ;-))
|
|
*/
|
|
web.onInit(Debugger8080.init);
|
|
|
|
} // endif DEBUGGER
|
|
|
|
if (NODE) module.exports = Debugger8080;
|