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).
8243 lines
364 KiB
JavaScript
8243 lines
364 KiB
JavaScript
/**
|
|
* @fileoverview Implements the PCx86 Debugger component.
|
|
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
|
* @version 1.0
|
|
* Created 2012-Jun-21
|
|
*
|
|
* 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 PCX86 = require("./defines");
|
|
var CPU = require("./cpu");
|
|
var X86 = require("./x86");
|
|
var X86Seg = require("./x86seg");
|
|
var Interrupts = require("./interrupts");
|
|
var Keyboard = require("./keyboard");
|
|
var Messages = require("./messages");
|
|
var Memory = require("./memory");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Debugger Address Object
|
|
*
|
|
* off offset, if any
|
|
* sel selector, if any (if null, addr should be set to a linear address)
|
|
* addr linear address, if any (if null, addr will be recomputed from sel:off)
|
|
* type one of the Debugger.ADDRTYPE values
|
|
* fData32 true if 32-bit operand size in effect
|
|
* fAddr32 true if 32-bit address size in effect
|
|
* fData32Orig original fData32 value, if any
|
|
* fAddr32Orig original fAddr32 value, if any
|
|
* cOverrides non-zero if any overrides were processed with this address
|
|
* fComplete true if a complete instruction was processed with this address
|
|
* fTempBreak 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 {{
|
|
* off:(number|null|undefined),
|
|
* sel:(number|null|undefined),
|
|
* addr:(number|null|undefined),
|
|
* type:(number|undefined),
|
|
* fData32:(boolean|undefined),
|
|
* fAddr32:(boolean|undefined),
|
|
* fData32Orig:(boolean|undefined),
|
|
* fAddr32Orig:(boolean|undefined),
|
|
* cOverrides:(number|undefined),
|
|
* fComplete:(boolean|undefined),
|
|
* fTempBreak:(boolean|undefined),
|
|
* sCmd:(string|undefined),
|
|
* aCmds:(Array.<string>|undefined)
|
|
* }}
|
|
*/
|
|
var DbgAddr;
|
|
|
|
/*
|
|
* Debugger Breakpoint Tips
|
|
*
|
|
* Here's an example of our powerful new breakpoint command capabilities:
|
|
*
|
|
* bp 0397:022B "?'GlobalAlloc(wFlags:[ss:sp+8],dwBytes:[ss:sp+6][ss:sp+4])';g [ss:sp+2]:[ss:sp] '?ax;if ax'"
|
|
*
|
|
* The above breakpoint will display a pleasing "GlobalAlloc()" string containing the current
|
|
* stack parameters, and will briefly stop execution on the return to print the result in AX,
|
|
* halting the CPU whenever AX is zero (the default behavior of "if" whenever the expression is
|
|
* false is to look for an "else" and automatically halt when there is no "else").
|
|
*
|
|
* How do you figure out where the code for GlobalAlloc is in the first place? You need to have
|
|
* BACKTRACK support enabled (which currently means running the non-COMPILED version), so that as
|
|
* the Disk component loads disk images, it will automatically extract symbolic information from all
|
|
* "NE" (New Executable) binaries on those disks, which the Debugger's "dt" command can then search
|
|
* for you; eg:
|
|
*
|
|
* ## dt globalalloc
|
|
* GLOBALALLOC: KRNL386.EXE 0001:022B len 0xC570
|
|
*
|
|
* And then you just need to do a bit more sleuthing to find the right CODE segment. And that just
|
|
* got easier, now that the PCx86 Debugger mimics portions of the Windows Debugger INT 0x41 interface;
|
|
* see intWindowsDebugger() for details. So even if you neglect to run WDEB386.EXE /E inside the
|
|
* machine before running Windows, you should still see notifications like:
|
|
*
|
|
* KERNEL!undefined code(0001)=#0397 len 0000C580
|
|
*
|
|
* in the PCx86 Debugger output window, as segments are being loaded by the Windows kernel.
|
|
*/
|
|
|
|
/**
|
|
* Debugger(parmsDbg)
|
|
*
|
|
* @constructor
|
|
* @extends Component
|
|
* @param {Object} parmsDbg
|
|
*
|
|
* The Debugger 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 Debugger component is an optional component that implements a variety of user
|
|
* commands for controlling the CPU, dumping and editing memory, etc.
|
|
*/
|
|
function Debugger(parmsDbg)
|
|
{
|
|
if (DEBUGGER) {
|
|
|
|
Component.call(this, "Debugger", parmsDbg, Debugger);
|
|
|
|
/*
|
|
* These keep track of instruction activity, but only when tracing or when Debugger 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;
|
|
|
|
/*
|
|
* Default number of hex chars in a register and a linear address (ie, for real-mode);
|
|
* updated by initBus().
|
|
*/
|
|
this.cchReg = 4;
|
|
this.cchAddr = 5;
|
|
this.maskAddr = 0xfffff;
|
|
|
|
/*
|
|
* 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.
|
|
*
|
|
* All dbgAddr variables contain properties off, sel, and addr, where sel:off represents the
|
|
* segmented address and addr is the corresponding linear address (if known). For certain
|
|
* segmented addresses (eg, breakpoint addresses), we pre-compute the linear address and save
|
|
* that in addr, so that the breakpoint will still operate as intended even if the mode changes
|
|
* later (eg, from real-mode to protected-mode).
|
|
*
|
|
* Finally, for TEMPORARY breakpoint addresses, we set fTempBreak 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
|
|
* nSegment
|
|
* sel
|
|
* off
|
|
* 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 Debugger message support
|
|
*/
|
|
this.afnDumpers = [];
|
|
this.messageInit(parmsDbg['messages']);
|
|
|
|
this.sInitCommands = parmsDbg['commands'];
|
|
|
|
/*
|
|
* Make it easier to access Debugger commands from an external REPL (eg, the WebStorm
|
|
* "live" console window); eg:
|
|
*
|
|
* pcx86('r')
|
|
* pcx86('dw 0:0')
|
|
* pcx86('h')
|
|
* ...
|
|
*/
|
|
var dbg = this;
|
|
if (window) {
|
|
if (window[PCX86.APPCLASS] === undefined) {
|
|
window[PCX86.APPCLASS] = function(s) { return dbg.doCommands(s); };
|
|
}
|
|
} else {
|
|
if (global[PCX86.APPCLASS] === undefined) {
|
|
global[PCX86.APPCLASS] = function(s) { return dbg.doCommands(s); };
|
|
}
|
|
}
|
|
|
|
} // endif DEBUGGER
|
|
}
|
|
|
|
if (DEBUGGER) {
|
|
|
|
Component.subclass(Debugger);
|
|
|
|
/*
|
|
* NOTE: Every Debugger 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.
|
|
*/
|
|
|
|
/*
|
|
* Information regarding interrupts of interest (used by messageInt() and others)
|
|
*/
|
|
Debugger.INT_MESSAGES = {
|
|
0x10: Messages.VIDEO,
|
|
0x13: Messages.FDC,
|
|
0x15: Messages.CHIPSET,
|
|
0x16: Messages.KEYBOARD,
|
|
// 0x1A: Messages.RTC, // ChipSet contains its own custom messageInt() handler for the RTC
|
|
0x1C: Messages.TIMER,
|
|
0x21: Messages.DOS,
|
|
0x33: Messages.MOUSE
|
|
};
|
|
|
|
/*
|
|
* Information regarding "annoying" interrupts (which aren't annoying so much as too frequent);
|
|
* note that some of these can still be enabled if you really want them (eg, RTC can be turned on
|
|
* with RTC messages, ALT_TIMER with TIMER messages, etc).
|
|
*/
|
|
Debugger.INT_ANNOYING = [Interrupts.RTC, Interrupts.ALT_TIMER, Interrupts.DOS_IDLE, Interrupts.DOS_NETBIOS, Interrupts.ALT_VIDEO];
|
|
|
|
Debugger.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",
|
|
'k': "stack trace",
|
|
'l': "load sector(s)",
|
|
"ln": "list nearest symbol(s)",
|
|
'm': "messages",
|
|
'mouse': "mouse action", // syntax: mouse {action} {delta} (eg, mouse x 10, mouse click 0, etc)
|
|
'o [#]': "output port #",
|
|
'p': "step over", // other variations: pr (step and dump registers)
|
|
'print': "print expression",
|
|
'r': "dump/set registers",
|
|
'reset': "reset machine",
|
|
't [#]': "trace", // other variations: tr (trace and dump registers)
|
|
'u [#]': "unassemble",
|
|
'x': "execution options",
|
|
'v': "print version",
|
|
'var': "assign variable"
|
|
};
|
|
|
|
/*
|
|
* Supported address types; the type field in a DbgAddr object may be one of:
|
|
*
|
|
* NONE, REAL, PROT, V86, LINEAR or PHYSICAL
|
|
*
|
|
* REAL and V86 addresses are specified with a '&' prefix, PROT addresses with a '#' prefix,
|
|
* LINEAR addresses with '%', and PHYSICAL addresses with '%%'.
|
|
*/
|
|
Debugger.ADDRTYPE = {
|
|
NONE: 0x00,
|
|
REAL: 0x01,
|
|
PROT: 0x02,
|
|
V86: 0x03,
|
|
LINEAR: 0x04,
|
|
PHYSICAL: 0x05
|
|
};
|
|
|
|
/*
|
|
* CPU instruction ordinals
|
|
*
|
|
* Note that individual instructions end with ordinal 162 and instruction groups begin with ordinal 163;
|
|
* the disassembler knows it's dealing with a group whenever the ordinal is not a valid index into INS_NAMES.
|
|
*
|
|
* NOTE: While this list started alphabetical, there are a few wrinkles; eg, POPA/POPF/PUSHF/PUSHA are
|
|
* sequential to make it easier to detect instructions that require a D suffix when the operand size is 32 bits.
|
|
*/
|
|
Debugger.INS = {
|
|
NONE: 0, AAA: 1, AAD: 2, AAM: 3, AAS: 4, ADC: 5, ADD: 6, AND: 7,
|
|
ARPL: 8, AS: 9, BOUND: 10, BSF: 11, BSR: 12, BT: 13, BTC: 14, BTR: 15,
|
|
BTS: 16, CALL: 17, CBW: 18, CLC: 19, CLD: 20, CLI: 21, CLTS: 22, CMC: 23,
|
|
CMP: 24, CMPSB: 25, CMPSW: 26, CS: 27, CWD: 28, DAA: 29, DAS: 30, DEC: 31,
|
|
DIV: 32, DS: 33, ENTER: 34, ES: 35, ESC: 36, FS: 37, GS: 38, HLT: 39,
|
|
IBTS: 40, IDIV: 41, IMUL: 42, IN: 43, INC: 44, INS: 45, INT: 46, INT3: 47,
|
|
INTO: 48, IRET: 49, JBE: 50, JC: 51, JCXZ: 52, JG: 53, JGE: 54, JL: 55,
|
|
JLE: 56, JMP: 57, JA: 58, JNC: 59, JNO: 60, JNP: 61, JNS: 62, JNZ: 63,
|
|
JO: 64, JP: 65, JS: 66, JZ: 67, LAHF: 68, LAR: 69, LDS: 70, LEA: 71,
|
|
LEAVE: 72, LES: 73, LFS: 74, LGDT: 75, LGS: 76, LIDT: 77, LLDT: 78, LMSW: 79,
|
|
LOADALL:80, LOCK: 81, LODSB: 82, LODSW: 83, LOOP: 84, LOOPNZ: 85, LOOPZ: 86, LSL: 87,
|
|
LSS: 88, LTR: 89, MOV: 90, MOVSB: 91, MOVSW: 92, MOVSX: 93, MOVZX: 94, MUL: 95,
|
|
NEG: 96, NOP: 97, NOT: 98, OR: 99, OS: 100, OUT: 101, OUTS: 102, POP: 103,
|
|
POPA: 104, POPF: 105, PUSHF: 106, PUSHA: 107, PUSH: 108, RCL: 109, RCR: 110, REPNZ: 111,
|
|
REPZ: 112, RET: 113, RETF: 114, ROL: 115, ROR: 116, SAHF: 117, SALC: 118, SAR: 119,
|
|
SBB: 120, SCASB: 121, SCASW: 122, SETBE: 123, SETC: 124, SETG: 125, SETGE: 126, SETL: 127,
|
|
SETLE: 128, SETNBE: 129, SETNC: 130, SETNO: 131, SETNP: 132, SETNS: 133, SETNZ: 134, SETO: 135,
|
|
SETP: 136, SETS: 137, SETZ: 138, SGDT: 139, SHL: 140, SHLD: 141, SHR: 142, SHRD: 143,
|
|
SIDT: 144, SLDT: 145, SMSW: 146, SS: 147, STC: 148, STD: 149, STI: 150, STOSB: 151,
|
|
STOSW: 152, STR: 153, SUB: 154, TEST: 155, VERR: 156, VERW: 157, WAIT: 158, XBTS: 159,
|
|
XCHG: 160, XLAT: 161, XOR: 162, GRP1B: 163, GRP1W: 164, GRP1SW: 165, GRP2B: 166, GRP2W: 167,
|
|
GRP2B1: 168, GRP2W1: 169, GRP2BC: 170, GRP2WC: 171, GRP3B: 172, GRP3W: 173, GRP4B: 174, GRP4W: 175,
|
|
OP0F: 176, GRP6: 177, GRP7: 178, GRP8: 179
|
|
};
|
|
|
|
/*
|
|
* CPU instruction names (mnemonics), indexed by CPU instruction ordinal (above)
|
|
*/
|
|
Debugger.INS_NAMES = [
|
|
"INVALID","AAA", "AAD", "AAM", "AAS", "ADC", "ADD", "AND",
|
|
"ARPL", "AS:", "BOUND", "BSF", "BSR", "BT", "BTC", "BTR",
|
|
"BTS", "CALL", "CBW", "CLC", "CLD", "CLI", "CLTS", "CMC",
|
|
"CMP", "CMPSB", "CMPSW", "CS:", "CWD", "DAA", "DAS", "DEC",
|
|
"DIV", "DS:", "ENTER", "ES:", "ESC", "FS:", "GS:", "HLT",
|
|
"IBTS", "IDIV", "IMUL", "IN", "INC", "INS", "INT", "INT3",
|
|
"INTO", "IRET", "JBE", "JC", "JCXZ", "JG", "JGE", "JL",
|
|
"JLE", "JMP", "JA", "JNC", "JNO", "JNP", "JNS", "JNZ",
|
|
"JO", "JP", "JS", "JZ", "LAHF", "LAR", "LDS", "LEA",
|
|
"LEAVE", "LES", "LFS", "LGDT", "LGS", "LIDT", "LLDT", "LMSW",
|
|
"LOADALL","LOCK", "LODSB", "LODSW", "LOOP", "LOOPNZ", "LOOPZ", "LSL",
|
|
"LSS", "LTR", "MOV", "MOVSB", "MOVSW", "MOVSX", "MOVZX", "MUL",
|
|
"NEG", "NOP", "NOT", "OR", "OS:", "OUT", "OUTS", "POP",
|
|
"POPA", "POPF", "PUSHF", "PUSHA", "PUSH", "RCL", "RCR", "REPNZ",
|
|
"REPZ", "RET", "RETF", "ROL", "ROR", "SAHF", "SALC", "SAR",
|
|
"SBB", "SCASB", "SCASW", "SETBE", "SETC", "SETG", "SETGE", "SETL",
|
|
"SETLE", "SETNBE", "SETNC", "SETNO", "SETNP", "SETNS", "SETNZ", "SETO",
|
|
"SETP", "SETS", "SETZ", "SGDT", "SHL", "SHLD", "SHR", "SHRD",
|
|
"SIDT", "SLDT", "SMSW", "SS:", "STC", "STD", "STI", "STOSB",
|
|
"STOSW", "STR", "SUB", "TEST", "VERR", "VERW", "WAIT", "XBTS",
|
|
"XCHG", "XLAT", "XOR"
|
|
];
|
|
|
|
/*
|
|
* FPU instruction ordinals
|
|
*
|
|
* Unlike CPU instruction ordinals, these are not organized alphabetically (which I did only for the
|
|
* sake of tidiness), but rather by functionality; ie:
|
|
*
|
|
* 0-3: real transfers
|
|
* 4-6: integer transfers
|
|
* 7-8: packed decimal transfers
|
|
* 9-11: addition
|
|
* 12-17: subtraction
|
|
* 18-20: multiplication
|
|
* 21-26: division
|
|
* 27-33: other
|
|
* 34-40: comparisons
|
|
* 41-45: transcendental
|
|
* 46-52: constants
|
|
* 53-77: coprocessor control
|
|
* 78---: new for 80287 or higher
|
|
*
|
|
* Also, unlike the CPU instructions, there is no NONE ("INVALID") instruction; if an ESC instruction
|
|
* can't be decoded as a valid FPU instruction, then it should remain an ESC instruction.
|
|
*/
|
|
Debugger.FINS = {
|
|
FLD: 0, FST: 1, FSTP: 2, FXCH: 3, FILD: 4, FIST: 5, FISTP: 6, FBLD: 7,
|
|
FBSTP: 8, FADD: 9, FADDP: 10, FIADD: 11, FSUB: 12, FSUBP: 13, FISUB: 14, FSUBR: 15,
|
|
FSUBRP: 16, FISUBR: 17, FMUL: 18, FMULP: 19, FIMUL: 20, FDIV: 21, FDIVP: 22, FIDIV: 23,
|
|
FDIVR: 24, FDIVRP: 25, FIDIVR: 26, FSQRT: 27, FSCALE: 28, FPREM: 29, FRNDINT:30, FXTRACT:31,
|
|
FABS: 32, FCHS: 33, FCOM: 34, FCOMP: 35, FCOMPP: 36, FICOM: 37, FICOMP: 38, FTST: 39,
|
|
FXAM: 40, FPTAN: 41, FPATAN: 42, F2XM1: 43, FYL2X: 44, FYL2XP1:45, FLDZ: 46, FLD1: 47,
|
|
FLDPI: 48, FLDL2T: 49, FLDL2E: 50, FLDLG2: 51, FLDLN2: 52, FINIT: 53, FNINIT: 54, FDISI: 55,
|
|
FNDISI: 56, FENI: 57, FNENI: 58, FLDCW: 59, FSTCW: 60, FNSTCW: 61, FSTSW: 62, FNSTSW: 63,
|
|
FCLEX: 64, FNCLEX: 65, FSTENV: 66, FNSTENV:67, FLDENV: 68, FSAVE: 69, FNSAVE: 70, FRSTOR: 71,
|
|
FINCSTP:72, FDECSTP:73, FFREE: 74, FFREEP: 75, FNOP: 76, FWAIT: 77, FSETPM: 78, FSINCOS:79,
|
|
FSTSWAX:80
|
|
};
|
|
|
|
/*
|
|
* FPU instruction names (mnemonics), indexed by FPU instruction ordinal (above)
|
|
*/
|
|
Debugger.FINS_NAMES = [
|
|
"FLD", "FST", "FSTP", "FXCH", "FILD", "FIST", "FISTP", "FBLD",
|
|
"FBSTP", "FADD", "FADDP", "FIADD", "FSUB", "FSUBP", "FISUB", "FSUBR",
|
|
"FSUBRP", "FISUBR", "FMUL", "FMULP", "FIMUL", "FDIV", "FDIVP", "FIDIV",
|
|
"FDIVR", "FDIVRP", "FIDIVR", "FSQRT", "FSCALE", "FPREM", "FRNDINT","FXTRACT",
|
|
"FABS", "FCHS", "FCOM", "FCOMP", "FCOMPP", "FICOM", "FICOMP", "FTST",
|
|
"FXAM", "FPTAN", "FPATAN", "F2XM1", "FYL2X", "FYL2XP1","FLDZ", "FLD1",
|
|
"FLDPI", "FLDL2T", "FLDL2E", "FLDLG2", "FLDLN2", "FINIT", "FNINIT", "FDISI",
|
|
"FNDISI", "FENI", "FNENI", "FLDCW", "FSTCW", "FNSTCW", "FSTSW", "FNSTSW",
|
|
"FCLEX", "FNCLEX", "FSTENV", "FNSTENV","FLDENV", "FSAVE", "FNSAVE", "FRSTOR",
|
|
"FINCSTP","FDECSTP","FFREE", "FFREEP", "FNOP", "FWAIT", "FSETPM", "FSINCOS",
|
|
"FSTSWAX"
|
|
];
|
|
|
|
Debugger.FPU_TAGS = ["VALID", "ZERO ", "SPEC ", "EMPTY"];
|
|
|
|
Debugger.CPU_8086 = 0;
|
|
Debugger.CPU_80186 = 1;
|
|
Debugger.CPU_80286 = 2;
|
|
Debugger.CPU_80386 = 3;
|
|
Debugger.CPUS = [8086, 80186, 80286, 80386];
|
|
|
|
/*
|
|
* ModRM masks and definitions
|
|
*/
|
|
Debugger.REG_AL = 0x00; // bits 0-2 are standard Reg encodings
|
|
Debugger.REG_CL = 0x01;
|
|
Debugger.REG_DL = 0x02;
|
|
Debugger.REG_BL = 0x03;
|
|
Debugger.REG_AH = 0x04;
|
|
Debugger.REG_CH = 0x05;
|
|
Debugger.REG_DH = 0x06;
|
|
Debugger.REG_BH = 0x07;
|
|
Debugger.REG_AX = 0x08;
|
|
Debugger.REG_CX = 0x09;
|
|
Debugger.REG_DX = 0x0A;
|
|
Debugger.REG_BX = 0x0B;
|
|
Debugger.REG_SP = 0x0C;
|
|
Debugger.REG_BP = 0x0D;
|
|
Debugger.REG_SI = 0x0E;
|
|
Debugger.REG_DI = 0x0F;
|
|
Debugger.REG_SEG = 0x10;
|
|
Debugger.REG_IP = 0x16;
|
|
Debugger.REG_PS = 0x17;
|
|
Debugger.REG_EAX = 0x18;
|
|
Debugger.REG_ECX = 0x19;
|
|
Debugger.REG_EDX = 0x1A;
|
|
Debugger.REG_EBX = 0x1B;
|
|
Debugger.REG_ESP = 0x1C;
|
|
Debugger.REG_EBP = 0x1D;
|
|
Debugger.REG_ESI = 0x1E;
|
|
Debugger.REG_EDI = 0x1F;
|
|
Debugger.REG_CR0 = 0x20;
|
|
Debugger.REG_CR1 = 0x21;
|
|
Debugger.REG_CR2 = 0x22;
|
|
Debugger.REG_CR3 = 0x23;
|
|
Debugger.REG_DR0 = 0x28;
|
|
Debugger.REG_DR1 = 0x29;
|
|
Debugger.REG_DR2 = 0x2A;
|
|
Debugger.REG_DR3 = 0x2B;
|
|
Debugger.REG_DR6 = 0x2E;
|
|
Debugger.REG_DR7 = 0x2F;
|
|
Debugger.REG_TR0 = 0x30;
|
|
Debugger.REG_TR6 = 0x36;
|
|
Debugger.REG_TR7 = 0x37;
|
|
Debugger.REG_EIP = 0x38;
|
|
|
|
Debugger.REGS = [
|
|
"AL", "CL", "DL", "BL", "AH", "CH", "DH", "BH",
|
|
"AX", "CX", "DX", "BX", "SP", "BP", "SI", "DI",
|
|
"ES", "CS", "SS", "DS", "FS", "GS", "IP", "PS",
|
|
"EAX", "ECX", "EDX", "EBX", "ESP", "EBP", "ESI", "EDI",
|
|
"CR0", "CR1", "CR2", "CR3", null, null, null, null, // register names used with TYPE_CTLREG
|
|
"DR0", "DR1", "DR2", "DR3", null, null, "DR6", "DR7", // register names used with TYPE_DBGREG
|
|
null, null, null, null, null, null, "TR6", "TR7", // register names used with TYPE_TSTREG
|
|
"EIP"
|
|
];
|
|
|
|
Debugger.REG_ES = 0x00; // bits 0-1 are standard SegReg encodings
|
|
Debugger.REG_CS = 0x01;
|
|
Debugger.REG_SS = 0x02;
|
|
Debugger.REG_DS = 0x03;
|
|
Debugger.REG_FS = 0x04;
|
|
Debugger.REG_GS = 0x05;
|
|
Debugger.REG_UNKNOWN = 0x00;
|
|
|
|
Debugger.MOD_NODISP = 0x00; // use RM below, no displacement
|
|
Debugger.MOD_DISP8 = 0x01; // use RM below + 8-bit displacement
|
|
Debugger.MOD_DISP16 = 0x02; // use RM below + 16-bit displacement
|
|
Debugger.MOD_REGISTER = 0x03; // use REG above
|
|
|
|
Debugger.RM_BXSI = 0x00;
|
|
Debugger.RM_BXDI = 0x01;
|
|
Debugger.RM_BPSI = 0x02;
|
|
Debugger.RM_BPDI = 0x03;
|
|
Debugger.RM_SI = 0x04;
|
|
Debugger.RM_DI = 0x05;
|
|
Debugger.RM_BP = 0x06;
|
|
Debugger.RM_IMMOFF = Debugger.RM_BP; // only if MOD_NODISP
|
|
Debugger.RM_BX = 0x07;
|
|
|
|
Debugger.RMS = [
|
|
"BX+SI", "BX+DI", "BP+SI", "BP+DI", "SI", "DI", "BP", "BX",
|
|
"EAX", "ECX", "EDX", "EBX", "ESP", "EBP", "ESI", "EDI"
|
|
];
|
|
|
|
/*
|
|
* Operand type descriptor masks and definitions
|
|
*
|
|
* Note that the letters in () in the comments refer to Intel's
|
|
* nomenclature used in Appendix A of the 80386 Programmers Reference Manual.
|
|
*/
|
|
Debugger.TYPE_SIZE = 0x000F; // size field
|
|
Debugger.TYPE_MODE = 0x00F0; // mode field
|
|
Debugger.TYPE_IREG = 0x0F00; // implied register field
|
|
Debugger.TYPE_OTHER = 0xF000; // "other" field
|
|
|
|
/*
|
|
* TYPE_SIZE values. Some definitions use duplicate values when the operands are the
|
|
* same size and the Debugger doesn't need to make a distinction.
|
|
*/
|
|
Debugger.TYPE_NONE = 0x0000; // (all other TYPE fields ignored)
|
|
Debugger.TYPE_BYTE = 0x0001; // (b) byte, regardless of operand size
|
|
Debugger.TYPE_SBYTE = 0x0002; // byte sign-extended to word
|
|
Debugger.TYPE_SHORT = 0x0003; // (w) 16-bit value
|
|
Debugger.TYPE_WORD = 0x0004; // (v) 16-bit or 32-bit value
|
|
Debugger.TYPE_LONG = 0x0005; // (d) 32-bit value
|
|
Debugger.TYPE_SEGP = 0x0006; // (p) 32-bit or 48-bit pointer
|
|
Debugger.TYPE_FARP = 0x0007; // (p) 32-bit or 48-bit pointer for JMP/CALL
|
|
Debugger.TYPE_PREFIX = 0x0008; // (treat similarly to TYPE_NONE)
|
|
/*
|
|
* The remaining TYPE_SIZE values are for the FPU. Note that there are not enough values
|
|
* within this nibble for every type to have a unique value, so to differentiate between two
|
|
* types of the same size (eg, SINT and SREAL), we can inspect the opcode string, because only
|
|
* FI* instructions use INT operands. Also, some FPU sizes are not in this list (eg, the
|
|
* so-called "word-integer"); since a word-integer is always 16 bits, we specify TYPE_SHORT,
|
|
* which the Debugger should display as "INT16" for FI* instructions.
|
|
*/
|
|
Debugger.TYPE_ST = 0x0009; // FPU ST (implicit stack top)
|
|
Debugger.TYPE_STREG = 0x000A; // FPU ST (explicit stack register, relative to top)
|
|
Debugger.TYPE_SINT = 0x000B; // FPU SI (short-integer; 32-bit); displayed as "INT32"
|
|
Debugger.TYPE_SREAL = 0x000B; // FPU SR (short-real; 32-bit); displayed as "REAL32"
|
|
Debugger.TYPE_LINT = 0x000C; // FPU LI (long-integer; 64-bit); displayed as "INT64"
|
|
Debugger.TYPE_LREAL = 0x000C; // FPU LR (long-real; 64-bit); displayed as "REAL64"
|
|
Debugger.TYPE_TREAL = 0x000D; // FPU TR (temp-real; 80-bit); displayed as "REAL80"
|
|
Debugger.TYPE_BCD80 = 0x000E; // FPU PD (packed-decimal; 18 BCD digits in 80 bits, bits 72-78 unused, sign in bit 79); displayed as "BCD80"
|
|
Debugger.TYPE_ENV = 0x000F; // FPU ENV (environment; 14 bytes in real-mode, 28 bytes in protected-mode)
|
|
Debugger.TYPE_FPU = 0x000F; // FPU SAVE (save/restore; 94 bytes in real-mode, 108 bytes in protected-mode)
|
|
|
|
/*
|
|
* TYPE_MODE values. Order is somewhat important, as all values implying
|
|
* the presence of a ModRM byte are assumed to be >= TYPE_MODRM.
|
|
*/
|
|
Debugger.TYPE_IMM = 0x0000; // (I) immediate data
|
|
Debugger.TYPE_ONE = 0x0010; // implicit 1 (eg, shifts/rotates)
|
|
Debugger.TYPE_IMMOFF = 0x0020; // (A) immediate offset
|
|
Debugger.TYPE_IMMREL = 0x0030; // (J) immediate relative
|
|
Debugger.TYPE_DSSI = 0x0040; // (X) memory addressed by DS:SI
|
|
Debugger.TYPE_ESDI = 0x0050; // (Y) memory addressed by ES:DI
|
|
Debugger.TYPE_IMPREG = 0x0060; // implicit register in TYPE_IREG
|
|
Debugger.TYPE_IMPSEG = 0x0070; // implicit segment reg in TYPE_IREG
|
|
Debugger.TYPE_MODRM = 0x0080; // (E) standard ModRM decoding
|
|
Debugger.TYPE_MODMEM = 0x0090; // (M) ModRM refers to memory only
|
|
Debugger.TYPE_MODREG = 0x00A0; // (R) ModRM refers to register only
|
|
Debugger.TYPE_REG = 0x00B0; // (G) standard Reg decoding
|
|
Debugger.TYPE_SEGREG = 0x00C0; // (S) Reg selects segment register
|
|
Debugger.TYPE_CTLREG = 0x00D0; // (C) Reg selects control register
|
|
Debugger.TYPE_DBGREG = 0x00E0; // (D) Reg selects debug register
|
|
Debugger.TYPE_TSTREG = 0x00F0; // (T) Reg selects test register
|
|
|
|
/*
|
|
* TYPE_IREG values, based on the REG_* constants.
|
|
* For convenience, they include TYPE_IMPREG or TYPE_IMPSEG as appropriate.
|
|
*/
|
|
Debugger.TYPE_AL = (Debugger.REG_AL << 8 | Debugger.TYPE_IMPREG | Debugger.TYPE_BYTE);
|
|
Debugger.TYPE_CL = (Debugger.REG_CL << 8 | Debugger.TYPE_IMPREG | Debugger.TYPE_BYTE);
|
|
Debugger.TYPE_DL = (Debugger.REG_DL << 8 | Debugger.TYPE_IMPREG | Debugger.TYPE_BYTE);
|
|
Debugger.TYPE_BL = (Debugger.REG_BL << 8 | Debugger.TYPE_IMPREG | Debugger.TYPE_BYTE);
|
|
Debugger.TYPE_AH = (Debugger.REG_AH << 8 | Debugger.TYPE_IMPREG | Debugger.TYPE_BYTE);
|
|
Debugger.TYPE_CH = (Debugger.REG_CH << 8 | Debugger.TYPE_IMPREG | Debugger.TYPE_BYTE);
|
|
Debugger.TYPE_DH = (Debugger.REG_DH << 8 | Debugger.TYPE_IMPREG | Debugger.TYPE_BYTE);
|
|
Debugger.TYPE_BH = (Debugger.REG_BH << 8 | Debugger.TYPE_IMPREG | Debugger.TYPE_BYTE);
|
|
Debugger.TYPE_AX = (Debugger.REG_AX << 8 | Debugger.TYPE_IMPREG | Debugger.TYPE_WORD);
|
|
Debugger.TYPE_CX = (Debugger.REG_CX << 8 | Debugger.TYPE_IMPREG | Debugger.TYPE_WORD);
|
|
Debugger.TYPE_DX = (Debugger.REG_DX << 8 | Debugger.TYPE_IMPREG | Debugger.TYPE_WORD);
|
|
Debugger.TYPE_BX = (Debugger.REG_BX << 8 | Debugger.TYPE_IMPREG | Debugger.TYPE_WORD);
|
|
Debugger.TYPE_SP = (Debugger.REG_SP << 8 | Debugger.TYPE_IMPREG | Debugger.TYPE_WORD);
|
|
Debugger.TYPE_BP = (Debugger.REG_BP << 8 | Debugger.TYPE_IMPREG | Debugger.TYPE_WORD);
|
|
Debugger.TYPE_SI = (Debugger.REG_SI << 8 | Debugger.TYPE_IMPREG | Debugger.TYPE_WORD);
|
|
Debugger.TYPE_DI = (Debugger.REG_DI << 8 | Debugger.TYPE_IMPREG | Debugger.TYPE_WORD);
|
|
Debugger.TYPE_ES = (Debugger.REG_ES << 8 | Debugger.TYPE_IMPSEG | Debugger.TYPE_SHORT);
|
|
Debugger.TYPE_CS = (Debugger.REG_CS << 8 | Debugger.TYPE_IMPSEG | Debugger.TYPE_SHORT);
|
|
Debugger.TYPE_SS = (Debugger.REG_SS << 8 | Debugger.TYPE_IMPSEG | Debugger.TYPE_SHORT);
|
|
Debugger.TYPE_DS = (Debugger.REG_DS << 8 | Debugger.TYPE_IMPSEG | Debugger.TYPE_SHORT);
|
|
Debugger.TYPE_FS = (Debugger.REG_FS << 8 | Debugger.TYPE_IMPSEG | Debugger.TYPE_SHORT);
|
|
Debugger.TYPE_GS = (Debugger.REG_GS << 8 | Debugger.TYPE_IMPSEG | Debugger.TYPE_SHORT);
|
|
|
|
/*
|
|
* TYPE_OTHER bit definitions
|
|
*/
|
|
Debugger.TYPE_IN = 0x1000; // operand is input
|
|
Debugger.TYPE_OUT = 0x2000; // operand is output
|
|
Debugger.TYPE_BOTH = (Debugger.TYPE_IN | Debugger.TYPE_OUT);
|
|
Debugger.TYPE_8086 = (Debugger.CPU_8086 << 14);
|
|
Debugger.TYPE_8087 = Debugger.TYPE_8086;
|
|
Debugger.TYPE_80186 = (Debugger.CPU_80186 << 14);
|
|
Debugger.TYPE_80286 = (Debugger.CPU_80286 << 14);
|
|
Debugger.TYPE_80287 = Debugger.TYPE_80286;
|
|
Debugger.TYPE_80386 = (Debugger.CPU_80386 << 14);
|
|
Debugger.TYPE_80387 = Debugger.TYPE_80386;
|
|
Debugger.TYPE_CPU_SHIFT = 14;
|
|
|
|
/*
|
|
* 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).
|
|
*/
|
|
Debugger.MESSAGES = {
|
|
"cpu": Messages.CPU,
|
|
"seg": Messages.SEG,
|
|
"desc": Messages.DESC,
|
|
"tss": Messages.TSS,
|
|
"int": Messages.INT,
|
|
"fault": Messages.FAULT,
|
|
"bus": Messages.BUS,
|
|
"mem": Messages.MEM,
|
|
"port": Messages.PORT,
|
|
"dma": Messages.DMA,
|
|
"pic": Messages.PIC,
|
|
"timer": Messages.TIMER,
|
|
"cmos": Messages.CMOS,
|
|
"rtc": Messages.RTC,
|
|
"8042": Messages.C8042,
|
|
"chipset": Messages.CHIPSET, // ie, anything else in ChipSet besides DMA, PIC, TIMER, CMOS, RTC and 8042
|
|
"keyboard": Messages.KEYBOARD, // "kbd" is also allowed as shorthand for "keyboard"; see doMessages()
|
|
"key": Messages.KEYS, // using "key" instead of "keys", since the latter is a method on JavasScript objects
|
|
"video": Messages.VIDEO,
|
|
"fdc": Messages.FDC,
|
|
"hdc": Messages.HDC,
|
|
"disk": Messages.DISK,
|
|
"parallel": Messages.PARALLEL,
|
|
"serial": Messages.SERIAL,
|
|
"mouse": Messages.MOUSE,
|
|
"speaker": Messages.SPEAKER,
|
|
"computer": Messages.COMPUTER,
|
|
"dos": Messages.DOS,
|
|
"data": Messages.DATA,
|
|
"log": Messages.LOG,
|
|
"warn": Messages.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.
|
|
*/
|
|
"halt": Messages.HALT
|
|
};
|
|
|
|
Debugger.HISTORY_LIMIT = DEBUG? 100000 : 1000;
|
|
|
|
/*
|
|
* Opcode 0x0F has a distinguished history:
|
|
*
|
|
* On the 8086, it functioned as POP CS
|
|
* On the 80186, it generated an Invalid Opcode (UD_FAULT) exception
|
|
* On the 80286, it introduced a new (and growing) series of two-byte opcodes
|
|
*
|
|
* Based on the active CPU model, we make every effort to execute and disassemble this (and every other)
|
|
* opcode appropriately, by setting the opcode's entry in aaOpDescs accordingly. 0x0F in aaOpDescs points
|
|
* to the 8086 table: aOpDescPopCS.
|
|
*
|
|
* Note that we must NOT modify aaOpDescs directly. this.aaOpDescs will point to Debugger.aaOpDescs
|
|
* if the processor is an 8086, because that's the processor that the hard-coded contents of the table
|
|
* represent; for all other processors, this.aaOpDescs will contain a copy of the table that we can modify.
|
|
*/
|
|
Debugger.aOpDescPopCS = [Debugger.INS.POP, Debugger.TYPE_CS | Debugger.TYPE_OUT];
|
|
Debugger.aOpDescUndefined = [Debugger.INS.NONE, Debugger.TYPE_NONE];
|
|
Debugger.aOpDesc0F = [Debugger.INS.OP0F, Debugger.TYPE_SHORT | Debugger.TYPE_BOTH];
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
Debugger.aaOpDescs = [
|
|
/* 0x00 */ [Debugger.INS.ADD, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_REG | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x01 */ [Debugger.INS.ADD, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x02 */ [Debugger.INS.ADD, Debugger.TYPE_REG | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x03 */ [Debugger.INS.ADD, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x04 */ [Debugger.INS.ADD, Debugger.TYPE_AL | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x05 */ [Debugger.INS.ADD, Debugger.TYPE_AX | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x06 */ [Debugger.INS.PUSH, Debugger.TYPE_ES | Debugger.TYPE_IN],
|
|
/* 0x07 */ [Debugger.INS.POP, Debugger.TYPE_ES | Debugger.TYPE_OUT],
|
|
|
|
/* 0x08 */ [Debugger.INS.OR, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_REG | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x09 */ [Debugger.INS.OR, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x0A */ [Debugger.INS.OR, Debugger.TYPE_REG | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x0B */ [Debugger.INS.OR, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x0C */ [Debugger.INS.OR, Debugger.TYPE_AL | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x0D */ [Debugger.INS.OR, Debugger.TYPE_AX | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x0E */ [Debugger.INS.PUSH, Debugger.TYPE_CS | Debugger.TYPE_IN],
|
|
/* 0x0F */ Debugger.aOpDescPopCS,
|
|
|
|
/* 0x10 */ [Debugger.INS.ADC, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_REG | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x11 */ [Debugger.INS.ADC, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x12 */ [Debugger.INS.ADC, Debugger.TYPE_REG | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x13 */ [Debugger.INS.ADC, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x14 */ [Debugger.INS.ADC, Debugger.TYPE_AL | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x15 */ [Debugger.INS.ADC, Debugger.TYPE_AX | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x16 */ [Debugger.INS.PUSH, Debugger.TYPE_SS | Debugger.TYPE_IN],
|
|
/* 0x17 */ [Debugger.INS.POP, Debugger.TYPE_SS | Debugger.TYPE_OUT],
|
|
|
|
/* 0x18 */ [Debugger.INS.SBB, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_REG | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x19 */ [Debugger.INS.SBB, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x1A */ [Debugger.INS.SBB, Debugger.TYPE_REG | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x1B */ [Debugger.INS.SBB, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x1C */ [Debugger.INS.SBB, Debugger.TYPE_AL | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x1D */ [Debugger.INS.SBB, Debugger.TYPE_AX | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x1E */ [Debugger.INS.PUSH, Debugger.TYPE_DS | Debugger.TYPE_IN],
|
|
/* 0x1F */ [Debugger.INS.POP, Debugger.TYPE_DS | Debugger.TYPE_OUT],
|
|
|
|
/* 0x20 */ [Debugger.INS.AND, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_REG | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x21 */ [Debugger.INS.AND, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x22 */ [Debugger.INS.AND, Debugger.TYPE_REG | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x23 */ [Debugger.INS.AND, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x24 */ [Debugger.INS.AND, Debugger.TYPE_AL | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x25 */ [Debugger.INS.AND, Debugger.TYPE_AX | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x26 */ [Debugger.INS.ES, Debugger.TYPE_PREFIX],
|
|
/* 0x27 */ [Debugger.INS.DAA],
|
|
|
|
/* 0x28 */ [Debugger.INS.SUB, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_REG | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x29 */ [Debugger.INS.SUB, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x2A */ [Debugger.INS.SUB, Debugger.TYPE_REG | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x2B */ [Debugger.INS.SUB, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x2C */ [Debugger.INS.SUB, Debugger.TYPE_AL | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x2D */ [Debugger.INS.SUB, Debugger.TYPE_AX | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x2E */ [Debugger.INS.CS, Debugger.TYPE_PREFIX],
|
|
/* 0x2F */ [Debugger.INS.DAS],
|
|
|
|
/* 0x30 */ [Debugger.INS.XOR, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_REG | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x31 */ [Debugger.INS.XOR, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x32 */ [Debugger.INS.XOR, Debugger.TYPE_REG | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x33 */ [Debugger.INS.XOR, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x34 */ [Debugger.INS.XOR, Debugger.TYPE_AL | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x35 */ [Debugger.INS.XOR, Debugger.TYPE_AX | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x36 */ [Debugger.INS.SS, Debugger.TYPE_PREFIX],
|
|
/* 0x37 */ [Debugger.INS.AAA],
|
|
|
|
/* 0x38 */ [Debugger.INS.CMP, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_IN, Debugger.TYPE_REG | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x39 */ [Debugger.INS.CMP, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x3A */ [Debugger.INS.CMP, Debugger.TYPE_REG | Debugger.TYPE_BYTE | Debugger.TYPE_IN, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x3B */ [Debugger.INS.CMP, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x3C */ [Debugger.INS.CMP, Debugger.TYPE_AL | Debugger.TYPE_IN, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x3D */ [Debugger.INS.CMP, Debugger.TYPE_AX | Debugger.TYPE_IN, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x3E */ [Debugger.INS.DS, Debugger.TYPE_PREFIX],
|
|
/* 0x3F */ [Debugger.INS.AAS],
|
|
|
|
/* 0x40 */ [Debugger.INS.INC, Debugger.TYPE_AX | Debugger.TYPE_BOTH],
|
|
/* 0x41 */ [Debugger.INS.INC, Debugger.TYPE_CX | Debugger.TYPE_BOTH],
|
|
/* 0x42 */ [Debugger.INS.INC, Debugger.TYPE_DX | Debugger.TYPE_BOTH],
|
|
/* 0x43 */ [Debugger.INS.INC, Debugger.TYPE_BX | Debugger.TYPE_BOTH],
|
|
/* 0x44 */ [Debugger.INS.INC, Debugger.TYPE_SP | Debugger.TYPE_BOTH],
|
|
/* 0x45 */ [Debugger.INS.INC, Debugger.TYPE_BP | Debugger.TYPE_BOTH],
|
|
/* 0x46 */ [Debugger.INS.INC, Debugger.TYPE_SI | Debugger.TYPE_BOTH],
|
|
/* 0x47 */ [Debugger.INS.INC, Debugger.TYPE_DI | Debugger.TYPE_BOTH],
|
|
|
|
/* 0x48 */ [Debugger.INS.DEC, Debugger.TYPE_AX | Debugger.TYPE_BOTH],
|
|
/* 0x49 */ [Debugger.INS.DEC, Debugger.TYPE_CX | Debugger.TYPE_BOTH],
|
|
/* 0x4A */ [Debugger.INS.DEC, Debugger.TYPE_DX | Debugger.TYPE_BOTH],
|
|
/* 0x4B */ [Debugger.INS.DEC, Debugger.TYPE_BX | Debugger.TYPE_BOTH],
|
|
/* 0x4C */ [Debugger.INS.DEC, Debugger.TYPE_SP | Debugger.TYPE_BOTH],
|
|
/* 0x4D */ [Debugger.INS.DEC, Debugger.TYPE_BP | Debugger.TYPE_BOTH],
|
|
/* 0x4E */ [Debugger.INS.DEC, Debugger.TYPE_SI | Debugger.TYPE_BOTH],
|
|
/* 0x4F */ [Debugger.INS.DEC, Debugger.TYPE_DI | Debugger.TYPE_BOTH],
|
|
|
|
/* 0x50 */ [Debugger.INS.PUSH, Debugger.TYPE_AX | Debugger.TYPE_IN],
|
|
/* 0x51 */ [Debugger.INS.PUSH, Debugger.TYPE_CX | Debugger.TYPE_IN],
|
|
/* 0x52 */ [Debugger.INS.PUSH, Debugger.TYPE_DX | Debugger.TYPE_IN],
|
|
/* 0x53 */ [Debugger.INS.PUSH, Debugger.TYPE_BX | Debugger.TYPE_IN],
|
|
/* 0x54 */ [Debugger.INS.PUSH, Debugger.TYPE_SP | Debugger.TYPE_IN],
|
|
/* 0x55 */ [Debugger.INS.PUSH, Debugger.TYPE_BP | Debugger.TYPE_IN],
|
|
/* 0x56 */ [Debugger.INS.PUSH, Debugger.TYPE_SI | Debugger.TYPE_IN],
|
|
/* 0x57 */ [Debugger.INS.PUSH, Debugger.TYPE_DI | Debugger.TYPE_IN],
|
|
|
|
/* 0x58 */ [Debugger.INS.POP, Debugger.TYPE_AX | Debugger.TYPE_OUT],
|
|
/* 0x59 */ [Debugger.INS.POP, Debugger.TYPE_CX | Debugger.TYPE_OUT],
|
|
/* 0x5A */ [Debugger.INS.POP, Debugger.TYPE_DX | Debugger.TYPE_OUT],
|
|
/* 0x5B */ [Debugger.INS.POP, Debugger.TYPE_BX | Debugger.TYPE_OUT],
|
|
/* 0x5C */ [Debugger.INS.POP, Debugger.TYPE_SP | Debugger.TYPE_OUT],
|
|
/* 0x5D */ [Debugger.INS.POP, Debugger.TYPE_BP | Debugger.TYPE_OUT],
|
|
/* 0x5E */ [Debugger.INS.POP, Debugger.TYPE_SI | Debugger.TYPE_OUT],
|
|
/* 0x5F */ [Debugger.INS.POP, Debugger.TYPE_DI | Debugger.TYPE_OUT],
|
|
|
|
/* 0x60 */ [Debugger.INS.PUSHA, Debugger.TYPE_NONE | Debugger.TYPE_80286],
|
|
/* 0x61 */ [Debugger.INS.POPA, Debugger.TYPE_NONE | Debugger.TYPE_80286],
|
|
/* 0x62 */ [Debugger.INS.BOUND, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_80286, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x63 */ [Debugger.INS.ARPL, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT | Debugger.TYPE_OUT, Debugger.TYPE_REG | Debugger.TYPE_SHORT | Debugger.TYPE_IN],
|
|
/* 0x64 */ [Debugger.INS.FS, Debugger.TYPE_PREFIX | Debugger.TYPE_80386],
|
|
/* 0x65 */ [Debugger.INS.GS, Debugger.TYPE_PREFIX | Debugger.TYPE_80386],
|
|
/* 0x66 */ [Debugger.INS.OS, Debugger.TYPE_PREFIX | Debugger.TYPE_80386],
|
|
/* 0x67 */ [Debugger.INS.AS, Debugger.TYPE_PREFIX | Debugger.TYPE_80386],
|
|
|
|
/* 0x68 */ [Debugger.INS.PUSH, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_80286],
|
|
/* 0x69 */ [Debugger.INS.IMUL, Debugger.TYPE_REG | Debugger.TYPE_SHORT | Debugger.TYPE_BOTH | Debugger.TYPE_80286, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x6A */ [Debugger.INS.PUSH, Debugger.TYPE_IMM | Debugger.TYPE_SBYTE | Debugger.TYPE_IN | Debugger.TYPE_80286],
|
|
/* 0x6B */ [Debugger.INS.IMUL, Debugger.TYPE_REG | Debugger.TYPE_SHORT | Debugger.TYPE_OUT | Debugger.TYPE_80286, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x6C */ [Debugger.INS.INS, Debugger.TYPE_ESDI | Debugger.TYPE_BYTE | Debugger.TYPE_OUT | Debugger.TYPE_80286, Debugger.TYPE_DX | Debugger.TYPE_IN],
|
|
/* 0x6D */ [Debugger.INS.INS, Debugger.TYPE_ESDI | Debugger.TYPE_WORD | Debugger.TYPE_OUT | Debugger.TYPE_80286, Debugger.TYPE_DX | Debugger.TYPE_IN],
|
|
/* 0x6E */ [Debugger.INS.OUTS, Debugger.TYPE_DX | Debugger.TYPE_IN | Debugger.TYPE_80286, Debugger.TYPE_DSSI | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x6F */ [Debugger.INS.OUTS, Debugger.TYPE_DX | Debugger.TYPE_IN | Debugger.TYPE_80286, Debugger.TYPE_DSSI | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
|
|
/* 0x70 */ [Debugger.INS.JO, Debugger.TYPE_IMMREL | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x71 */ [Debugger.INS.JNO, Debugger.TYPE_IMMREL | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x72 */ [Debugger.INS.JC, Debugger.TYPE_IMMREL | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x73 */ [Debugger.INS.JNC, Debugger.TYPE_IMMREL | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x74 */ [Debugger.INS.JZ, Debugger.TYPE_IMMREL | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x75 */ [Debugger.INS.JNZ, Debugger.TYPE_IMMREL | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x76 */ [Debugger.INS.JBE, Debugger.TYPE_IMMREL | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x77 */ [Debugger.INS.JA, Debugger.TYPE_IMMREL | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
|
|
/* 0x78 */ [Debugger.INS.JS, Debugger.TYPE_IMMREL | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x79 */ [Debugger.INS.JNS, Debugger.TYPE_IMMREL | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x7A */ [Debugger.INS.JP, Debugger.TYPE_IMMREL | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x7B */ [Debugger.INS.JNP, Debugger.TYPE_IMMREL | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x7C */ [Debugger.INS.JL, Debugger.TYPE_IMMREL | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x7D */ [Debugger.INS.JGE, Debugger.TYPE_IMMREL | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x7E */ [Debugger.INS.JLE, Debugger.TYPE_IMMREL | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x7F */ [Debugger.INS.JG, Debugger.TYPE_IMMREL | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
|
|
/* 0x80 */ [Debugger.INS.GRP1B, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x81 */ [Debugger.INS.GRP1W, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x82 */ [Debugger.INS.GRP1B, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x83 */ [Debugger.INS.GRP1SW,Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x84 */ [Debugger.INS.TEST, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_IN, Debugger.TYPE_REG | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x85 */ [Debugger.INS.TEST, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x86 */ [Debugger.INS.XCHG, Debugger.TYPE_REG | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH],
|
|
/* 0x87 */ [Debugger.INS.XCHG, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH],
|
|
|
|
/* 0x88 */ [Debugger.INS.MOV, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_OUT, Debugger.TYPE_REG | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x89 */ [Debugger.INS.MOV, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_OUT, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x8A */ [Debugger.INS.MOV, Debugger.TYPE_REG | Debugger.TYPE_BYTE | Debugger.TYPE_OUT, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0x8B */ [Debugger.INS.MOV, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_OUT, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x8C */ [Debugger.INS.MOV, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_OUT, Debugger.TYPE_SEGREG | Debugger.TYPE_SHORT | Debugger.TYPE_IN],
|
|
/* 0x8D */ [Debugger.INS.LEA, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_OUT, Debugger.TYPE_MODMEM | Debugger.TYPE_WORD ],
|
|
/* 0x8E */ [Debugger.INS.MOV, Debugger.TYPE_SEGREG | Debugger.TYPE_SHORT | Debugger.TYPE_OUT, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0x8F */ [Debugger.INS.POP, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_OUT],
|
|
|
|
/* 0x90 */ [Debugger.INS.NOP],
|
|
/* 0x91 */ [Debugger.INS.XCHG, Debugger.TYPE_AX | Debugger.TYPE_BOTH, Debugger.TYPE_CX | Debugger.TYPE_BOTH],
|
|
/* 0x92 */ [Debugger.INS.XCHG, Debugger.TYPE_AX | Debugger.TYPE_BOTH, Debugger.TYPE_DX | Debugger.TYPE_BOTH],
|
|
/* 0x93 */ [Debugger.INS.XCHG, Debugger.TYPE_AX | Debugger.TYPE_BOTH, Debugger.TYPE_BX | Debugger.TYPE_BOTH],
|
|
/* 0x94 */ [Debugger.INS.XCHG, Debugger.TYPE_AX | Debugger.TYPE_BOTH, Debugger.TYPE_SP | Debugger.TYPE_BOTH],
|
|
/* 0x95 */ [Debugger.INS.XCHG, Debugger.TYPE_AX | Debugger.TYPE_BOTH, Debugger.TYPE_BP | Debugger.TYPE_BOTH],
|
|
/* 0x96 */ [Debugger.INS.XCHG, Debugger.TYPE_AX | Debugger.TYPE_BOTH, Debugger.TYPE_SI | Debugger.TYPE_BOTH],
|
|
/* 0x97 */ [Debugger.INS.XCHG, Debugger.TYPE_AX | Debugger.TYPE_BOTH, Debugger.TYPE_DI | Debugger.TYPE_BOTH],
|
|
|
|
/* 0x98 */ [Debugger.INS.CBW],
|
|
/* 0x99 */ [Debugger.INS.CWD],
|
|
/* 0x9A */ [Debugger.INS.CALL, Debugger.TYPE_IMM | Debugger.TYPE_FARP | Debugger.TYPE_IN],
|
|
/* 0x9B */ [Debugger.INS.WAIT],
|
|
/* 0x9C */ [Debugger.INS.PUSHF],
|
|
/* 0x9D */ [Debugger.INS.POPF],
|
|
/* 0x9E */ [Debugger.INS.SAHF],
|
|
/* 0x9F */ [Debugger.INS.LAHF],
|
|
|
|
/* 0xA0 */ [Debugger.INS.MOV, Debugger.TYPE_AL | Debugger.TYPE_OUT, Debugger.TYPE_IMMOFF | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xA1 */ [Debugger.INS.MOV, Debugger.TYPE_AX | Debugger.TYPE_OUT, Debugger.TYPE_IMMOFF | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0xA2 */ [Debugger.INS.MOV, Debugger.TYPE_IMMOFF | Debugger.TYPE_BYTE | Debugger.TYPE_OUT, Debugger.TYPE_AL | Debugger.TYPE_IN],
|
|
/* 0xA3 */ [Debugger.INS.MOV, Debugger.TYPE_IMMOFF | Debugger.TYPE_WORD | Debugger.TYPE_OUT, Debugger.TYPE_AX | Debugger.TYPE_IN],
|
|
/* 0xA4 */ [Debugger.INS.MOVSB, Debugger.TYPE_ESDI | Debugger.TYPE_BYTE | Debugger.TYPE_OUT, Debugger.TYPE_DSSI | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xA5 */ [Debugger.INS.MOVSW, Debugger.TYPE_ESDI | Debugger.TYPE_WORD | Debugger.TYPE_OUT, Debugger.TYPE_DSSI | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0xA6 */ [Debugger.INS.CMPSB, Debugger.TYPE_ESDI | Debugger.TYPE_BYTE | Debugger.TYPE_IN, Debugger.TYPE_DSSI | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xA7 */ [Debugger.INS.CMPSW, Debugger.TYPE_ESDI | Debugger.TYPE_WORD | Debugger.TYPE_IN, Debugger.TYPE_DSSI | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
|
|
/* 0xA8 */ [Debugger.INS.TEST, Debugger.TYPE_AL | Debugger.TYPE_IN, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xA9 */ [Debugger.INS.TEST, Debugger.TYPE_AX | Debugger.TYPE_IN, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0xAA */ [Debugger.INS.STOSB, Debugger.TYPE_ESDI | Debugger.TYPE_BYTE | Debugger.TYPE_OUT, Debugger.TYPE_AL | Debugger.TYPE_IN],
|
|
/* 0xAB */ [Debugger.INS.STOSW, Debugger.TYPE_ESDI | Debugger.TYPE_WORD | Debugger.TYPE_OUT, Debugger.TYPE_AX | Debugger.TYPE_IN],
|
|
/* 0xAC */ [Debugger.INS.LODSB, Debugger.TYPE_AL | Debugger.TYPE_OUT, Debugger.TYPE_DSSI | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xAD */ [Debugger.INS.LODSW, Debugger.TYPE_AX | Debugger.TYPE_OUT, Debugger.TYPE_DSSI | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0xAE */ [Debugger.INS.SCASB, Debugger.TYPE_AL | Debugger.TYPE_IN, Debugger.TYPE_ESDI | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xAF */ [Debugger.INS.SCASW, Debugger.TYPE_AX | Debugger.TYPE_IN, Debugger.TYPE_ESDI | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
|
|
/* 0xB0 */ [Debugger.INS.MOV, Debugger.TYPE_AL | Debugger.TYPE_OUT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xB1 */ [Debugger.INS.MOV, Debugger.TYPE_CL | Debugger.TYPE_OUT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xB2 */ [Debugger.INS.MOV, Debugger.TYPE_DL | Debugger.TYPE_OUT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xB3 */ [Debugger.INS.MOV, Debugger.TYPE_BL | Debugger.TYPE_OUT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xB4 */ [Debugger.INS.MOV, Debugger.TYPE_AH | Debugger.TYPE_OUT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xB5 */ [Debugger.INS.MOV, Debugger.TYPE_CH | Debugger.TYPE_OUT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xB6 */ [Debugger.INS.MOV, Debugger.TYPE_DH | Debugger.TYPE_OUT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xB7 */ [Debugger.INS.MOV, Debugger.TYPE_BH | Debugger.TYPE_OUT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
|
|
/* 0xB8 */ [Debugger.INS.MOV, Debugger.TYPE_AX | Debugger.TYPE_OUT, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0xB9 */ [Debugger.INS.MOV, Debugger.TYPE_CX | Debugger.TYPE_OUT, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0xBA */ [Debugger.INS.MOV, Debugger.TYPE_DX | Debugger.TYPE_OUT, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0xBB */ [Debugger.INS.MOV, Debugger.TYPE_BX | Debugger.TYPE_OUT, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0xBC */ [Debugger.INS.MOV, Debugger.TYPE_SP | Debugger.TYPE_OUT, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0xBD */ [Debugger.INS.MOV, Debugger.TYPE_BP | Debugger.TYPE_OUT, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0xBE */ [Debugger.INS.MOV, Debugger.TYPE_SI | Debugger.TYPE_OUT, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0xBF */ [Debugger.INS.MOV, Debugger.TYPE_DI | Debugger.TYPE_OUT, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
|
|
/* 0xC0 */ [Debugger.INS.GRP2B, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH | Debugger.TYPE_80186, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xC1 */ [Debugger.INS.GRP2W, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH | Debugger.TYPE_80186, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xC2 */ [Debugger.INS.RET, Debugger.TYPE_IMM | Debugger.TYPE_SHORT | Debugger.TYPE_IN],
|
|
/* 0xC3 */ [Debugger.INS.RET],
|
|
/* 0xC4 */ [Debugger.INS.LES, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_OUT, Debugger.TYPE_MODMEM | Debugger.TYPE_SEGP | Debugger.TYPE_IN],
|
|
/* 0xC5 */ [Debugger.INS.LDS, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_OUT, Debugger.TYPE_MODMEM | Debugger.TYPE_SEGP | Debugger.TYPE_IN],
|
|
/* 0xC6 */ [Debugger.INS.MOV, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_OUT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xC7 */ [Debugger.INS.MOV, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_OUT, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
|
|
/* 0xC8 */ [Debugger.INS.ENTER, Debugger.TYPE_IMM | Debugger.TYPE_SHORT | Debugger.TYPE_IN | Debugger.TYPE_80286, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xC9 */ [Debugger.INS.LEAVE, Debugger.TYPE_NONE | Debugger.TYPE_80286],
|
|
/* 0xCA */ [Debugger.INS.RETF, Debugger.TYPE_IMM | Debugger.TYPE_SHORT | Debugger.TYPE_IN],
|
|
/* 0xCB */ [Debugger.INS.RETF],
|
|
/* 0xCC */ [Debugger.INS.INT3],
|
|
/* 0xCD */ [Debugger.INS.INT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xCE */ [Debugger.INS.INTO],
|
|
/* 0xCF */ [Debugger.INS.IRET],
|
|
|
|
/* 0xD0 */ [Debugger.INS.GRP2B1,Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_ONE | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xD1 */ [Debugger.INS.GRP2W1,Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_ONE | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xD2 */ [Debugger.INS.GRP2BC,Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_CL | Debugger.TYPE_IN],
|
|
/* 0xD3 */ [Debugger.INS.GRP2WC,Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_CL | Debugger.TYPE_IN],
|
|
/* 0xD4 */ [Debugger.INS.AAM, Debugger.TYPE_IMM | Debugger.TYPE_BYTE],
|
|
/* 0xD5 */ [Debugger.INS.AAD, Debugger.TYPE_IMM | Debugger.TYPE_BYTE],
|
|
/* 0xD6 */ [Debugger.INS.SALC],
|
|
/* 0xD7 */ [Debugger.INS.XLAT],
|
|
|
|
/* 0xD8 */ [Debugger.INS.ESC, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0xD9 */ [Debugger.INS.ESC, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0xDA */ [Debugger.INS.ESC, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0xDB */ [Debugger.INS.ESC, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0xDC */ [Debugger.INS.ESC, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0xDD */ [Debugger.INS.ESC, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0xDE */ [Debugger.INS.ESC, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0xDF */ [Debugger.INS.ESC, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
|
|
/* 0xE0 */ [Debugger.INS.LOOPNZ,Debugger.TYPE_IMMREL | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xE1 */ [Debugger.INS.LOOPZ, Debugger.TYPE_IMMREL | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xE2 */ [Debugger.INS.LOOP, Debugger.TYPE_IMMREL | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xE3 */ [Debugger.INS.JCXZ, Debugger.TYPE_IMMREL | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xE4 */ [Debugger.INS.IN, Debugger.TYPE_AL | Debugger.TYPE_OUT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xE5 */ [Debugger.INS.IN, Debugger.TYPE_AX | Debugger.TYPE_OUT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xE6 */ [Debugger.INS.OUT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN, Debugger.TYPE_AL | Debugger.TYPE_IN],
|
|
/* 0xE7 */ [Debugger.INS.OUT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN, Debugger.TYPE_AX | Debugger.TYPE_IN],
|
|
|
|
/* 0xE8 */ [Debugger.INS.CALL, Debugger.TYPE_IMMREL | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0xE9 */ [Debugger.INS.JMP, Debugger.TYPE_IMMREL | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
/* 0xEA */ [Debugger.INS.JMP, Debugger.TYPE_IMM | Debugger.TYPE_FARP | Debugger.TYPE_IN],
|
|
/* 0xEB */ [Debugger.INS.JMP, Debugger.TYPE_IMMREL | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
/* 0xEC */ [Debugger.INS.IN, Debugger.TYPE_AL | Debugger.TYPE_OUT, Debugger.TYPE_DX | Debugger.TYPE_SHORT | Debugger.TYPE_IN],
|
|
/* 0xED */ [Debugger.INS.IN, Debugger.TYPE_AX | Debugger.TYPE_OUT, Debugger.TYPE_DX | Debugger.TYPE_SHORT | Debugger.TYPE_IN],
|
|
/* 0xEE */ [Debugger.INS.OUT, Debugger.TYPE_DX | Debugger.TYPE_SHORT | Debugger.TYPE_IN, Debugger.TYPE_AL | Debugger.TYPE_IN],
|
|
/* 0xEF */ [Debugger.INS.OUT, Debugger.TYPE_DX | Debugger.TYPE_SHORT | Debugger.TYPE_IN, Debugger.TYPE_AX | Debugger.TYPE_IN],
|
|
|
|
/* 0xF0 */ [Debugger.INS.LOCK, Debugger.TYPE_PREFIX],
|
|
/* 0xF1 */ [Debugger.INS.NONE],
|
|
/* 0xF2 */ [Debugger.INS.REPNZ, Debugger.TYPE_PREFIX],
|
|
/* 0xF3 */ [Debugger.INS.REPZ, Debugger.TYPE_PREFIX],
|
|
/* 0xF4 */ [Debugger.INS.HLT],
|
|
/* 0xF5 */ [Debugger.INS.CMC],
|
|
/* 0xF6 */ [Debugger.INS.GRP3B, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH],
|
|
/* 0xF7 */ [Debugger.INS.GRP3W, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH],
|
|
|
|
/* 0xF8 */ [Debugger.INS.CLC],
|
|
/* 0xF9 */ [Debugger.INS.STC],
|
|
/* 0xFA */ [Debugger.INS.CLI],
|
|
/* 0xFB */ [Debugger.INS.STI],
|
|
/* 0xFC */ [Debugger.INS.CLD],
|
|
/* 0xFD */ [Debugger.INS.STD],
|
|
/* 0xFE */ [Debugger.INS.GRP4B, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH],
|
|
/* 0xFF */ [Debugger.INS.GRP4W, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH]
|
|
];
|
|
|
|
Debugger.aaOp0FDescs = {
|
|
0x00: [Debugger.INS.GRP6, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT | Debugger.TYPE_BOTH],
|
|
0x01: [Debugger.INS.GRP7, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT | Debugger.TYPE_BOTH],
|
|
0x02: [Debugger.INS.LAR, Debugger.TYPE_REG | Debugger.TYPE_SHORT | Debugger.TYPE_OUT | Debugger.TYPE_80286, Debugger.TYPE_MODMEM | Debugger.TYPE_SHORT| Debugger.TYPE_IN],
|
|
0x03: [Debugger.INS.LSL, Debugger.TYPE_REG | Debugger.TYPE_SHORT | Debugger.TYPE_OUT | Debugger.TYPE_80286, Debugger.TYPE_MODMEM | Debugger.TYPE_SHORT| Debugger.TYPE_IN],
|
|
0x05: [Debugger.INS.LOADALL,Debugger.TYPE_80286],
|
|
0x06: [Debugger.INS.CLTS, Debugger.TYPE_80286],
|
|
0x07: [Debugger.INS.LOADALL,Debugger.TYPE_80386], // TODO: implied operand is ES:[(E)DI]
|
|
0x20: [Debugger.INS.MOV, Debugger.TYPE_MODREG | Debugger.TYPE_LONG | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_CTLREG | Debugger.TYPE_LONG | Debugger.TYPE_IN],
|
|
0x21: [Debugger.INS.MOV, Debugger.TYPE_MODREG | Debugger.TYPE_LONG | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_DBGREG | Debugger.TYPE_LONG | Debugger.TYPE_IN],
|
|
0x22: [Debugger.INS.MOV, Debugger.TYPE_CTLREG | Debugger.TYPE_LONG | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_MODREG | Debugger.TYPE_LONG | Debugger.TYPE_IN],
|
|
0x23: [Debugger.INS.MOV, Debugger.TYPE_DBGREG | Debugger.TYPE_LONG | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_MODREG | Debugger.TYPE_LONG | Debugger.TYPE_IN],
|
|
0x24: [Debugger.INS.MOV, Debugger.TYPE_MODREG | Debugger.TYPE_LONG | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_TSTREG | Debugger.TYPE_LONG | Debugger.TYPE_IN],
|
|
0x26: [Debugger.INS.MOV, Debugger.TYPE_TSTREG | Debugger.TYPE_LONG | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_MODREG | Debugger.TYPE_LONG | Debugger.TYPE_IN],
|
|
0x80: [Debugger.INS.JO, Debugger.TYPE_IMMREL | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_80386],
|
|
0x81: [Debugger.INS.JNO, Debugger.TYPE_IMMREL | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_80386],
|
|
0x82: [Debugger.INS.JC, Debugger.TYPE_IMMREL | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_80386],
|
|
0x83: [Debugger.INS.JNC, Debugger.TYPE_IMMREL | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_80386],
|
|
0x84: [Debugger.INS.JZ, Debugger.TYPE_IMMREL | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_80386],
|
|
0x85: [Debugger.INS.JNZ, Debugger.TYPE_IMMREL | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_80386],
|
|
0x86: [Debugger.INS.JBE, Debugger.TYPE_IMMREL | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_80386],
|
|
0x87: [Debugger.INS.JA, Debugger.TYPE_IMMREL | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_80386],
|
|
0x88: [Debugger.INS.JS, Debugger.TYPE_IMMREL | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_80386],
|
|
0x89: [Debugger.INS.JNS, Debugger.TYPE_IMMREL | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_80386],
|
|
0x8A: [Debugger.INS.JP, Debugger.TYPE_IMMREL | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_80386],
|
|
0x8B: [Debugger.INS.JNP, Debugger.TYPE_IMMREL | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_80386],
|
|
0x8C: [Debugger.INS.JL, Debugger.TYPE_IMMREL | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_80386],
|
|
0x8D: [Debugger.INS.JGE, Debugger.TYPE_IMMREL | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_80386],
|
|
0x8E: [Debugger.INS.JLE, Debugger.TYPE_IMMREL | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_80386],
|
|
0x8F: [Debugger.INS.JG, Debugger.TYPE_IMMREL | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_80386],
|
|
0x90: [Debugger.INS.SETO, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_OUT | Debugger.TYPE_80386],
|
|
0x91: [Debugger.INS.SETNO, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_OUT | Debugger.TYPE_80386],
|
|
0x92: [Debugger.INS.SETC, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_OUT | Debugger.TYPE_80386],
|
|
0x93: [Debugger.INS.SETNC, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_OUT | Debugger.TYPE_80386],
|
|
0x94: [Debugger.INS.SETZ, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_OUT | Debugger.TYPE_80386],
|
|
0x95: [Debugger.INS.SETNZ, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_OUT | Debugger.TYPE_80386],
|
|
0x96: [Debugger.INS.SETBE, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_OUT | Debugger.TYPE_80386],
|
|
0x97: [Debugger.INS.SETNBE, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_OUT | Debugger.TYPE_80386],
|
|
0x98: [Debugger.INS.SETS, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_OUT | Debugger.TYPE_80386],
|
|
0x99: [Debugger.INS.SETNS, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_OUT | Debugger.TYPE_80386],
|
|
0x9A: [Debugger.INS.SETP, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_OUT | Debugger.TYPE_80386],
|
|
0x9B: [Debugger.INS.SETNP, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_OUT | Debugger.TYPE_80386],
|
|
0x9C: [Debugger.INS.SETL, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_OUT | Debugger.TYPE_80386],
|
|
0x9D: [Debugger.INS.SETGE, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_OUT | Debugger.TYPE_80386],
|
|
0x9E: [Debugger.INS.SETLE, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_OUT | Debugger.TYPE_80386],
|
|
0x9F: [Debugger.INS.SETG, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_OUT | Debugger.TYPE_80386],
|
|
0xA0: [Debugger.INS.PUSH, Debugger.TYPE_FS | Debugger.TYPE_IN | Debugger.TYPE_80386],
|
|
0xA1: [Debugger.INS.POP, Debugger.TYPE_FS | Debugger.TYPE_OUT | Debugger.TYPE_80386],
|
|
0xA3: [Debugger.INS.BT, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_80386, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
0xA4: [Debugger.INS.SHLD, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
0xA5: [Debugger.INS.SHLD, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN, Debugger.TYPE_CL | Debugger.TYPE_IN],
|
|
0xA6: [Debugger.INS.XBTS, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN, Debugger.TYPE_AX | Debugger.TYPE_IN, Debugger.TYPE_CL | Debugger.TYPE_IN],
|
|
0xA7: [Debugger.INS.IBTS, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_AX | Debugger.TYPE_IN, Debugger.TYPE_CL | Debugger.TYPE_IN, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
0xA8: [Debugger.INS.PUSH, Debugger.TYPE_GS | Debugger.TYPE_IN | Debugger.TYPE_80386],
|
|
0xA9: [Debugger.INS.POP, Debugger.TYPE_GS | Debugger.TYPE_OUT | Debugger.TYPE_80386],
|
|
0xAB: [Debugger.INS.BTS, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
0xAC: [Debugger.INS.SHRD, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
0xAD: [Debugger.INS.SHRD, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN, Debugger.TYPE_CL | Debugger.TYPE_IN],
|
|
0xAF: [Debugger.INS.IMUL, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH | Debugger.TYPE_80386, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
0xB2: [Debugger.INS.LSS, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_OUT, Debugger.TYPE_MODMEM | Debugger.TYPE_SEGP | Debugger.TYPE_IN],
|
|
0xB3: [Debugger.INS.BTR, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
0xB4: [Debugger.INS.LFS, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_OUT, Debugger.TYPE_MODMEM | Debugger.TYPE_SEGP | Debugger.TYPE_IN],
|
|
0xB5: [Debugger.INS.LGS, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_OUT, Debugger.TYPE_MODMEM | Debugger.TYPE_SEGP | Debugger.TYPE_IN],
|
|
0xB6: [Debugger.INS.MOVZX, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
0xB7: [Debugger.INS.MOVZX, Debugger.TYPE_REG | Debugger.TYPE_LONG | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT | Debugger.TYPE_IN],
|
|
0xBA: [Debugger.INS.GRP8, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH | Debugger.TYPE_80386, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
0xBB: [Debugger.INS.BTC, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
0xBC: [Debugger.INS.BSF, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
0xBD: [Debugger.INS.BSR, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
0xBE: [Debugger.INS.MOVSX, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
0xBF: [Debugger.INS.MOVSX, Debugger.TYPE_REG | Debugger.TYPE_LONG | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT | Debugger.TYPE_IN]
|
|
};
|
|
|
|
/*
|
|
* Be sure to keep the following table in sync with X86FPU.aaOps
|
|
*/
|
|
Debugger.aaaOpFPUDescs = {
|
|
0xD8: {
|
|
0x00: [Debugger.FINS.FADD, Debugger.TYPE_MODRM | Debugger.TYPE_SREAL | Debugger.TYPE_IN],
|
|
0x01: [Debugger.FINS.FMUL, Debugger.TYPE_MODRM | Debugger.TYPE_SREAL | Debugger.TYPE_IN],
|
|
0x02: [Debugger.FINS.FCOM, Debugger.TYPE_MODRM | Debugger.TYPE_SREAL | Debugger.TYPE_IN],
|
|
0x03: [Debugger.FINS.FCOMP, Debugger.TYPE_MODRM | Debugger.TYPE_SREAL | Debugger.TYPE_IN],
|
|
0x04: [Debugger.FINS.FSUB, Debugger.TYPE_MODRM | Debugger.TYPE_SREAL | Debugger.TYPE_IN],
|
|
0x05: [Debugger.FINS.FSUBR, Debugger.TYPE_MODRM | Debugger.TYPE_SREAL | Debugger.TYPE_IN],
|
|
0x06: [Debugger.FINS.FDIV, Debugger.TYPE_MODRM | Debugger.TYPE_SREAL | Debugger.TYPE_IN],
|
|
0x07: [Debugger.FINS.FDIVR, Debugger.TYPE_MODRM | Debugger.TYPE_SREAL | Debugger.TYPE_IN],
|
|
0x30: [Debugger.FINS.FADD, Debugger.TYPE_IMPREG | Debugger.TYPE_ST | Debugger.TYPE_OUT, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_IN],
|
|
0x31: [Debugger.FINS.FMUL, Debugger.TYPE_IMPREG | Debugger.TYPE_ST | Debugger.TYPE_OUT, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_IN],
|
|
0x32: [Debugger.FINS.FCOM, Debugger.TYPE_IMPREG | Debugger.TYPE_ST | Debugger.TYPE_OUT, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_IN],
|
|
0x33: [Debugger.FINS.FCOMP, Debugger.TYPE_IMPREG | Debugger.TYPE_ST | Debugger.TYPE_OUT, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_IN],
|
|
0x34: [Debugger.FINS.FSUB, Debugger.TYPE_IMPREG | Debugger.TYPE_ST | Debugger.TYPE_OUT, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_IN],
|
|
0x35: [Debugger.FINS.FSUBR, Debugger.TYPE_IMPREG | Debugger.TYPE_ST | Debugger.TYPE_OUT, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_IN],
|
|
0x36: [Debugger.FINS.FDIV, Debugger.TYPE_IMPREG | Debugger.TYPE_ST | Debugger.TYPE_OUT, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_IN],
|
|
0x37: [Debugger.FINS.FDIVR, Debugger.TYPE_IMPREG | Debugger.TYPE_ST | Debugger.TYPE_OUT, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_IN]
|
|
},
|
|
0xD9: {
|
|
0x00: [Debugger.FINS.FLD, Debugger.TYPE_MODRM | Debugger.TYPE_SREAL | Debugger.TYPE_IN],
|
|
0x02: [Debugger.FINS.FST, Debugger.TYPE_MODRM | Debugger.TYPE_SREAL | Debugger.TYPE_OUT],
|
|
0x03: [Debugger.FINS.FSTP, Debugger.TYPE_MODRM | Debugger.TYPE_SREAL | Debugger.TYPE_OUT],
|
|
0x04: [Debugger.FINS.FLDENV, Debugger.TYPE_MODRM | Debugger.TYPE_ENV | Debugger.TYPE_IN],
|
|
0x05: [Debugger.FINS.FLDCW, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT | Debugger.TYPE_IN],
|
|
0x06: [Debugger.FINS.FSTENV, Debugger.TYPE_MODRM | Debugger.TYPE_ENV | Debugger.TYPE_OUT],
|
|
0x07: [Debugger.FINS.FSTCW, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT | Debugger.TYPE_OUT],
|
|
0x30: [Debugger.FINS.FLD, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_OUT],
|
|
0x31: [Debugger.FINS.FXCH, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_OUT],
|
|
0x32: [Debugger.FINS.FNOP],
|
|
0x33: [Debugger.FINS.FSTP, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_OUT], // Obsolete encoding
|
|
0x40: [Debugger.FINS.FCHS],
|
|
0x41: [Debugger.FINS.FABS],
|
|
0x44: [Debugger.FINS.FTST],
|
|
0x45: [Debugger.FINS.FXAM],
|
|
0x50: [Debugger.FINS.FLD1],
|
|
0x51: [Debugger.FINS.FLDL2T],
|
|
0x52: [Debugger.FINS.FLDL2E],
|
|
0x53: [Debugger.FINS.FLDPI],
|
|
0x54: [Debugger.FINS.FLDLG2],
|
|
0x55: [Debugger.FINS.FLDLN2],
|
|
0x56: [Debugger.FINS.FLDZ],
|
|
0x60: [Debugger.FINS.F2XM1],
|
|
0x61: [Debugger.FINS.FYL2X],
|
|
0x62: [Debugger.FINS.FPTAN],
|
|
0x63: [Debugger.FINS.FPATAN],
|
|
0x64: [Debugger.FINS.FXTRACT],
|
|
0x66: [Debugger.FINS.FDECSTP],
|
|
0x67: [Debugger.FINS.FINCSTP],
|
|
0x70: [Debugger.FINS.FPREM],
|
|
0x71: [Debugger.FINS.FYL2XP1],
|
|
0x72: [Debugger.FINS.FSQRT],
|
|
0x74: [Debugger.FINS.FRNDINT],
|
|
0x75: [Debugger.FINS.FSCALE]
|
|
},
|
|
0xDA: {
|
|
0x00: [Debugger.FINS.FIADD, Debugger.TYPE_MODRM | Debugger.TYPE_SINT | Debugger.TYPE_IN],
|
|
0x01: [Debugger.FINS.FIMUL, Debugger.TYPE_MODRM | Debugger.TYPE_SINT | Debugger.TYPE_IN],
|
|
0x02: [Debugger.FINS.FICOM, Debugger.TYPE_MODRM | Debugger.TYPE_SINT | Debugger.TYPE_IN],
|
|
0x03: [Debugger.FINS.FICOMP, Debugger.TYPE_MODRM | Debugger.TYPE_SINT | Debugger.TYPE_IN],
|
|
0x04: [Debugger.FINS.FISUB, Debugger.TYPE_MODRM | Debugger.TYPE_SINT | Debugger.TYPE_IN],
|
|
0x05: [Debugger.FINS.FISUBR, Debugger.TYPE_MODRM | Debugger.TYPE_SINT | Debugger.TYPE_IN],
|
|
0x06: [Debugger.FINS.FIDIV, Debugger.TYPE_MODRM | Debugger.TYPE_SINT | Debugger.TYPE_IN],
|
|
0x07: [Debugger.FINS.FIDIVR, Debugger.TYPE_MODRM | Debugger.TYPE_SINT | Debugger.TYPE_IN]
|
|
},
|
|
0xDB: {
|
|
0x00: [Debugger.FINS.FILD, Debugger.TYPE_MODRM | Debugger.TYPE_SINT | Debugger.TYPE_IN],
|
|
0x02: [Debugger.FINS.FIST, Debugger.TYPE_MODRM | Debugger.TYPE_SINT | Debugger.TYPE_OUT],
|
|
0x03: [Debugger.FINS.FISTP, Debugger.TYPE_MODRM | Debugger.TYPE_SINT | Debugger.TYPE_OUT],
|
|
0x05: [Debugger.FINS.FLD, Debugger.TYPE_MODRM | Debugger.TYPE_TREAL | Debugger.TYPE_IN],
|
|
0x07: [Debugger.FINS.FSTP, Debugger.TYPE_MODRM | Debugger.TYPE_TREAL | Debugger.TYPE_OUT],
|
|
0x40: [Debugger.FINS.FENI],
|
|
0x41: [Debugger.FINS.FDISI],
|
|
0x42: [Debugger.FINS.FCLEX],
|
|
0x43: [Debugger.FINS.FINIT],
|
|
0x44: [Debugger.FINS.FSETPM, Debugger.TYPE_80287],
|
|
0x73: [Debugger.FINS.FSINCOS, Debugger.TYPE_80387]
|
|
},
|
|
0xDC: {
|
|
0x00: [Debugger.FINS.FADD, Debugger.TYPE_MODRM | Debugger.TYPE_LREAL | Debugger.TYPE_IN],
|
|
0x01: [Debugger.FINS.FMUL, Debugger.TYPE_MODRM | Debugger.TYPE_LREAL | Debugger.TYPE_IN],
|
|
0x02: [Debugger.FINS.FCOM, Debugger.TYPE_MODRM | Debugger.TYPE_LREAL | Debugger.TYPE_IN],
|
|
0x03: [Debugger.FINS.FCOMP, Debugger.TYPE_MODRM | Debugger.TYPE_LREAL | Debugger.TYPE_IN],
|
|
0x04: [Debugger.FINS.FSUB, Debugger.TYPE_MODRM | Debugger.TYPE_LREAL | Debugger.TYPE_IN],
|
|
0x05: [Debugger.FINS.FSUBR, Debugger.TYPE_MODRM | Debugger.TYPE_LREAL | Debugger.TYPE_IN],
|
|
0x06: [Debugger.FINS.FDIV, Debugger.TYPE_MODRM | Debugger.TYPE_LREAL | Debugger.TYPE_IN],
|
|
0x07: [Debugger.FINS.FDIVR, Debugger.TYPE_MODRM | Debugger.TYPE_LREAL | Debugger.TYPE_IN],
|
|
0x30: [Debugger.FINS.FADD, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_OUT, Debugger.TYPE_IMPREG | Debugger.TYPE_ST | Debugger.TYPE_IN],
|
|
0x31: [Debugger.FINS.FMUL, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_OUT, Debugger.TYPE_IMPREG | Debugger.TYPE_ST | Debugger.TYPE_IN],
|
|
0x32: [Debugger.FINS.FCOM, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_IN], // Obsolete encoding
|
|
0x33: [Debugger.FINS.FCOMP, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_IN], // Obsolete encoding
|
|
0x34: [Debugger.FINS.FSUBR, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_OUT, Debugger.TYPE_IMPREG | Debugger.TYPE_ST | Debugger.TYPE_IN],
|
|
0x35: [Debugger.FINS.FSUB, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_OUT, Debugger.TYPE_IMPREG | Debugger.TYPE_ST | Debugger.TYPE_IN],
|
|
0x36: [Debugger.FINS.FDIVR, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_OUT, Debugger.TYPE_IMPREG | Debugger.TYPE_ST | Debugger.TYPE_IN],
|
|
0x37: [Debugger.FINS.FDIV, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_OUT, Debugger.TYPE_IMPREG | Debugger.TYPE_ST | Debugger.TYPE_IN]
|
|
},
|
|
0xDD: {
|
|
0x00: [Debugger.FINS.FLD, Debugger.TYPE_MODRM | Debugger.TYPE_LREAL | Debugger.TYPE_IN],
|
|
0x02: [Debugger.FINS.FST, Debugger.TYPE_MODRM | Debugger.TYPE_LREAL | Debugger.TYPE_OUT],
|
|
0x03: [Debugger.FINS.FSTP, Debugger.TYPE_MODRM | Debugger.TYPE_LREAL | Debugger.TYPE_OUT],
|
|
0x04: [Debugger.FINS.FRSTOR, Debugger.TYPE_MODRM | Debugger.TYPE_FPU | Debugger.TYPE_IN],
|
|
0x06: [Debugger.FINS.FSAVE, Debugger.TYPE_MODRM | Debugger.TYPE_FPU | Debugger.TYPE_OUT],
|
|
0x07: [Debugger.FINS.FSTSW, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT | Debugger.TYPE_OUT],
|
|
0x30: [Debugger.FINS.FFREE, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_IN],
|
|
0x31: [Debugger.FINS.FXCH, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_OUT], // Obsolete encoding
|
|
0x32: [Debugger.FINS.FST, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_IN],
|
|
0x33: [Debugger.FINS.FSTP, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_IN]
|
|
},
|
|
0xDE: {
|
|
0x00: [Debugger.FINS.FIADD, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT | Debugger.TYPE_IN],
|
|
0x01: [Debugger.FINS.FIMUL, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT | Debugger.TYPE_IN],
|
|
0x02: [Debugger.FINS.FICOM, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT | Debugger.TYPE_IN],
|
|
0x03: [Debugger.FINS.FICOMP, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT | Debugger.TYPE_IN],
|
|
0x04: [Debugger.FINS.FISUB, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT | Debugger.TYPE_IN],
|
|
0x05: [Debugger.FINS.FISUBR, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT | Debugger.TYPE_IN],
|
|
0x06: [Debugger.FINS.FIDIV, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT | Debugger.TYPE_IN],
|
|
0x07: [Debugger.FINS.FIDIVR, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT | Debugger.TYPE_IN],
|
|
0x30: [Debugger.FINS.FADDP, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_OUT, Debugger.TYPE_IMPREG | Debugger.TYPE_ST | Debugger.TYPE_IN],
|
|
0x31: [Debugger.FINS.FMULP, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_OUT, Debugger.TYPE_IMPREG | Debugger.TYPE_ST | Debugger.TYPE_IN],
|
|
0x32: [Debugger.FINS.FCOMP, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_IN], // Obsolete encoding
|
|
0x33: [Debugger.FINS.FCOMPP, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_IN],
|
|
0x34: [Debugger.FINS.FSUBRP, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_OUT, Debugger.TYPE_IMPREG | Debugger.TYPE_ST | Debugger.TYPE_IN],
|
|
0x35: [Debugger.FINS.FSUBP, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_OUT, Debugger.TYPE_IMPREG | Debugger.TYPE_ST | Debugger.TYPE_IN],
|
|
0x36: [Debugger.FINS.FDIVRP, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_OUT, Debugger.TYPE_IMPREG | Debugger.TYPE_ST | Debugger.TYPE_IN],
|
|
0x37: [Debugger.FINS.FDIVP, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_OUT, Debugger.TYPE_IMPREG | Debugger.TYPE_ST | Debugger.TYPE_IN]
|
|
},
|
|
0xDF: {
|
|
0x00: [Debugger.FINS.FILD, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT | Debugger.TYPE_IN],
|
|
0x02: [Debugger.FINS.FIST, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT | Debugger.TYPE_OUT],
|
|
0x03: [Debugger.FINS.FISTP, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT | Debugger.TYPE_OUT],
|
|
0x04: [Debugger.FINS.FBLD, Debugger.TYPE_MODRM | Debugger.TYPE_BCD80 | Debugger.TYPE_IN],
|
|
0x05: [Debugger.FINS.FILD, Debugger.TYPE_MODRM | Debugger.TYPE_LINT | Debugger.TYPE_IN],
|
|
0x06: [Debugger.FINS.FBSTP, Debugger.TYPE_MODRM | Debugger.TYPE_BCD80 | Debugger.TYPE_OUT],
|
|
0x07: [Debugger.FINS.FISTP, Debugger.TYPE_MODRM | Debugger.TYPE_LINT | Debugger.TYPE_OUT],
|
|
0x30: [Debugger.FINS.FFREEP, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_IN], // Obsolete encoding
|
|
0x31: [Debugger.FINS.FXCH, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_OUT], // Obsolete encoding
|
|
0x32: [Debugger.FINS.FSTP, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_IN], // Obsolete encoding
|
|
0x33: [Debugger.FINS.FSTP, Debugger.TYPE_IMPREG | Debugger.TYPE_STREG | Debugger.TYPE_IN], // Obsolete encoding
|
|
0x34: [Debugger.FINS.FSTSWAX, Debugger.TYPE_80287]
|
|
}
|
|
};
|
|
|
|
Debugger.aaGrpDescs = [
|
|
[
|
|
/* GRP1B */
|
|
[Debugger.INS.ADD, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.OR, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.ADC, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.SBB, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.AND, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.SUB, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.XOR, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.CMP, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_IN, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN]
|
|
],
|
|
[
|
|
/* GRP1W */
|
|
[Debugger.INS.ADD, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
[Debugger.INS.OR, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
[Debugger.INS.ADC, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
[Debugger.INS.SBB, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
[Debugger.INS.AND, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
[Debugger.INS.SUB, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
[Debugger.INS.XOR, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
[Debugger.INS.CMP, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN]
|
|
],
|
|
[
|
|
/* GRP1SW */
|
|
[Debugger.INS.ADD, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_SBYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.OR, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_SBYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.ADC, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_SBYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.SBB, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_SBYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.AND, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_SBYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.SUB, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_SBYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.XOR, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_IMM | Debugger.TYPE_SBYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.CMP, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN, Debugger.TYPE_IMM | Debugger.TYPE_SBYTE | Debugger.TYPE_IN]
|
|
],
|
|
[
|
|
/* GRP2B */
|
|
[Debugger.INS.ROL, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH | Debugger.TYPE_80286, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.ROR, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH | Debugger.TYPE_80286, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.RCL, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH | Debugger.TYPE_80286, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.RCR, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH | Debugger.TYPE_80286, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.SHL, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH | Debugger.TYPE_80286, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.SHR, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH | Debugger.TYPE_80286, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
Debugger.aOpDescUndefined,
|
|
[Debugger.INS.SAR, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH | Debugger.TYPE_80286, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN]
|
|
],
|
|
[
|
|
/* GRP2W */
|
|
[Debugger.INS.ROL, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH | Debugger.TYPE_80286, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.ROR, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH | Debugger.TYPE_80286, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.RCL, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH | Debugger.TYPE_80286, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.RCR, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH | Debugger.TYPE_80286, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.SHL, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH | Debugger.TYPE_80286, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.SHR, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH | Debugger.TYPE_80286, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
Debugger.aOpDescUndefined,
|
|
[Debugger.INS.SAR, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH | Debugger.TYPE_80286, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN]
|
|
],
|
|
[
|
|
/* GRP2B1 */
|
|
[Debugger.INS.ROL, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_ONE | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.ROR, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_ONE | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.RCL, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_ONE | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.RCR, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_ONE | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.SHL, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_ONE | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.SHR, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_ONE | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
Debugger.aOpDescUndefined,
|
|
[Debugger.INS.SAR, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_ONE | Debugger.TYPE_BYTE | Debugger.TYPE_IN]
|
|
],
|
|
[
|
|
/* GRP2W1 */
|
|
[Debugger.INS.ROL, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_ONE | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.ROR, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_ONE | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.RCL, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_ONE | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.RCR, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_ONE | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.SHL, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_ONE | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.SHR, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_ONE | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
Debugger.aOpDescUndefined,
|
|
[Debugger.INS.SAR, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_ONE | Debugger.TYPE_BYTE | Debugger.TYPE_IN]
|
|
],
|
|
[
|
|
/* GRP2BC */
|
|
[Debugger.INS.ROL, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_CL | Debugger.TYPE_IN],
|
|
[Debugger.INS.ROR, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_CL | Debugger.TYPE_IN],
|
|
[Debugger.INS.RCL, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_CL | Debugger.TYPE_IN],
|
|
[Debugger.INS.RCR, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_CL | Debugger.TYPE_IN],
|
|
[Debugger.INS.SHL, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_CL | Debugger.TYPE_IN],
|
|
[Debugger.INS.SHR, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_CL | Debugger.TYPE_IN],
|
|
Debugger.aOpDescUndefined,
|
|
[Debugger.INS.SAR, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH, Debugger.TYPE_CL | Debugger.TYPE_IN]
|
|
],
|
|
[
|
|
/* GRP2WC */
|
|
[Debugger.INS.ROL, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_CL | Debugger.TYPE_IN],
|
|
[Debugger.INS.ROR, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_CL | Debugger.TYPE_IN],
|
|
[Debugger.INS.RCL, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_CL | Debugger.TYPE_IN],
|
|
[Debugger.INS.RCR, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_CL | Debugger.TYPE_IN],
|
|
[Debugger.INS.SHL, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_CL | Debugger.TYPE_IN],
|
|
[Debugger.INS.SHR, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_CL | Debugger.TYPE_IN],
|
|
Debugger.aOpDescUndefined,
|
|
[Debugger.INS.SAR, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH, Debugger.TYPE_CL | Debugger.TYPE_IN]
|
|
],
|
|
[
|
|
/* GRP3B */
|
|
[Debugger.INS.TEST, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_IN, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
Debugger.aOpDescUndefined,
|
|
[Debugger.INS.NOT, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH],
|
|
[Debugger.INS.NEG, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH],
|
|
[Debugger.INS.MUL, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.IMUL, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH],
|
|
[Debugger.INS.DIV, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.IDIV, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH]
|
|
],
|
|
[
|
|
/* GRP3W */
|
|
[Debugger.INS.TEST, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
Debugger.aOpDescUndefined,
|
|
[Debugger.INS.NOT, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH],
|
|
[Debugger.INS.NEG, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH],
|
|
[Debugger.INS.MUL, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
[Debugger.INS.IMUL, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH],
|
|
[Debugger.INS.DIV, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
[Debugger.INS.IDIV, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH]
|
|
],
|
|
[
|
|
/* GRP4B */
|
|
[Debugger.INS.INC, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH],
|
|
[Debugger.INS.DEC, Debugger.TYPE_MODRM | Debugger.TYPE_BYTE | Debugger.TYPE_BOTH],
|
|
Debugger.aOpDescUndefined,
|
|
Debugger.aOpDescUndefined,
|
|
Debugger.aOpDescUndefined,
|
|
Debugger.aOpDescUndefined,
|
|
Debugger.aOpDescUndefined,
|
|
Debugger.aOpDescUndefined
|
|
],
|
|
[
|
|
/* GRP4W */
|
|
[Debugger.INS.INC, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH],
|
|
[Debugger.INS.DEC, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH],
|
|
[Debugger.INS.CALL, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
[Debugger.INS.CALL, Debugger.TYPE_MODRM | Debugger.TYPE_FARP | Debugger.TYPE_IN],
|
|
[Debugger.INS.JMP, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
[Debugger.INS.JMP, Debugger.TYPE_MODRM | Debugger.TYPE_FARP | Debugger.TYPE_IN],
|
|
[Debugger.INS.PUSH, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
|
Debugger.aOpDescUndefined
|
|
],
|
|
[ /* OP0F */ ],
|
|
[
|
|
/* GRP6 */
|
|
[Debugger.INS.SLDT, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT| Debugger.TYPE_OUT | Debugger.TYPE_80286],
|
|
[Debugger.INS.STR, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT| Debugger.TYPE_OUT | Debugger.TYPE_80286],
|
|
[Debugger.INS.LLDT, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT| Debugger.TYPE_IN | Debugger.TYPE_80286],
|
|
[Debugger.INS.LTR, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT| Debugger.TYPE_IN | Debugger.TYPE_80286],
|
|
[Debugger.INS.VERR, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT| Debugger.TYPE_IN | Debugger.TYPE_80286],
|
|
[Debugger.INS.VERW, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT| Debugger.TYPE_IN | Debugger.TYPE_80286],
|
|
Debugger.aOpDescUndefined,
|
|
Debugger.aOpDescUndefined
|
|
],
|
|
[
|
|
/* GRP7 */
|
|
[Debugger.INS.SGDT, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT| Debugger.TYPE_OUT | Debugger.TYPE_80286],
|
|
[Debugger.INS.SIDT, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT| Debugger.TYPE_OUT | Debugger.TYPE_80286],
|
|
[Debugger.INS.LGDT, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT| Debugger.TYPE_IN | Debugger.TYPE_80286],
|
|
[Debugger.INS.LIDT, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT| Debugger.TYPE_IN | Debugger.TYPE_80286],
|
|
[Debugger.INS.SMSW, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT| Debugger.TYPE_OUT | Debugger.TYPE_80286],
|
|
Debugger.aOpDescUndefined,
|
|
[Debugger.INS.LMSW, Debugger.TYPE_MODRM | Debugger.TYPE_SHORT| Debugger.TYPE_IN | Debugger.TYPE_80286],
|
|
Debugger.aOpDescUndefined
|
|
],
|
|
[
|
|
/* GRP8 */
|
|
Debugger.aOpDescUndefined,
|
|
Debugger.aOpDescUndefined,
|
|
Debugger.aOpDescUndefined,
|
|
Debugger.aOpDescUndefined,
|
|
[Debugger.INS.BT, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_80386, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.BTS, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.BTR, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN],
|
|
[Debugger.INS.BTC, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_OUT | Debugger.TYPE_80386, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_IN]
|
|
]
|
|
];
|
|
|
|
/**
|
|
* initBus(bus, cpu, dbg)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {Computer} cmp
|
|
* @param {Bus} bus
|
|
* @param {X86CPU} cpu
|
|
* @param {Debugger} dbg
|
|
*/
|
|
Debugger.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|
{
|
|
this.bus = bus;
|
|
this.cpu = cpu;
|
|
this.cmp = cmp;
|
|
this.fdc = cmp.getMachineComponent("FDC");
|
|
this.hdc = cmp.getMachineComponent("HDC");
|
|
this.fpu = cmp.getMachineComponent("FPU");
|
|
this.mouse = cmp.getMachineComponent("Mouse");
|
|
if (MAXDEBUG) this.chipset = cmp.getMachineComponent("ChipSet");
|
|
|
|
/*
|
|
* Re-initialize Debugger message support if necessary
|
|
*/
|
|
var sMessages = cmp.getMachineParm('messages');
|
|
if (sMessages) this.messageInit(sMessages);
|
|
|
|
this.cchAddr = bus.getWidth() >> 2;
|
|
this.maskAddr = bus.nBusLimit;
|
|
|
|
/*
|
|
* Allocate a special segment "register", for use whenever a requested selector is not currently loaded
|
|
*/
|
|
this.segDebugger = new X86Seg(this.cpu, X86Seg.ID.DBG, "DBG");
|
|
|
|
this.aaOpDescs = Debugger.aaOpDescs;
|
|
if (this.cpu.model >= X86.MODEL_80186) {
|
|
this.aaOpDescs = Debugger.aaOpDescs.slice();
|
|
this.aaOpDescs[0x0F] = Debugger.aOpDescUndefined;
|
|
if (this.cpu.model >= X86.MODEL_80286) {
|
|
/*
|
|
* TODO: Consider whether the aOpDesc0F table should be split in two: one for 80286-only instructions,
|
|
* and one for both 80286 and 80386. For now, the Debugger is not as strict as the X86CPU is about
|
|
* the instructions it supports for each type of CPU, in part because an 80286 machine could still be
|
|
* presented with 80386-only code that is simply "skipped over" when then CPU doesn't support it.
|
|
*
|
|
* Obviously I'm not being entirely consistent, since I don't disassemble *any* 0x0F opcodes for any
|
|
* pre-80286 CPUs. But at least I'm being up front about it.
|
|
*/
|
|
this.aaOpDescs[0x0F] = Debugger.aOpDesc0F;
|
|
if (I386 && this.cpu.model >= X86.MODEL_80386) this.cchReg = 8;
|
|
}
|
|
}
|
|
|
|
this.messageDump(Messages.BUS, function onDumpBus(asArgs) { dbg.dumpBus(asArgs); });
|
|
this.messageDump(Messages.DESC, function onDumpSel(asArgs) { dbg.dumpSel(asArgs); });
|
|
this.messageDump(Messages.DOS, function onDumpDOS(asArgs) { dbg.dumpDOS(asArgs); });
|
|
this.messageDump(Messages.MEM, function onDumpMem(asArgs) { dbg.dumpMem(asArgs); });
|
|
this.messageDump(Messages.TSS, function onDumpTSS(asArgs) { dbg.dumpTSS(asArgs); });
|
|
|
|
if (Interrupts.WINDBG.ENABLED || Interrupts.WINDBGRM.ENABLED) {
|
|
this.fWinDbg = null;
|
|
this.cTrapFaults = 0;
|
|
this.fIgnoreNextCheckFault = false;
|
|
this.cpu.addIntNotify(Interrupts.WINCB.VECTOR, this.intWindowsCallBack.bind(this));
|
|
this.cpu.addIntNotify(Interrupts.WINDBG.VECTOR, this.intWindowsDebugger.bind(this));
|
|
}
|
|
if (Interrupts.WINDBGRM.ENABLED) {
|
|
this.fWinDbgRM = null;
|
|
this.cpu.addIntNotify(Interrupts.WINDBGRM.VECTOR, this.intWindowsDebuggerRM.bind(this));
|
|
}
|
|
|
|
this.setReady();
|
|
};
|
|
|
|
if (Interrupts.WINDBG.ENABLED || Interrupts.WINDBGRM.ENABLED) {
|
|
/**
|
|
* addSegmentInfo(dbgAddr, nSegment, sel, fCode, fPrint)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {DbgAddr} dbgAddr (address of module name)
|
|
* @param {number} nSegment (logical segment number)
|
|
* @param {number} sel (current selector)
|
|
* @param {boolean} fCode (true if code segment, false if data segment)
|
|
* @param {boolean} [fPrint] (false means we're merely monitoring, so let WDEB386 print its own notifications)
|
|
*/
|
|
Debugger.prototype.addSegmentInfo = function(dbgAddr, nSegment, sel, fCode, fPrint)
|
|
{
|
|
var sModule = this.getSZ(dbgAddr);
|
|
var seg = this.getSegment(sel);
|
|
var len = seg? seg.limit + 1 : 0;
|
|
var sSection = (fCode? "_CODE" : "_DATA") + str.toHex(nSegment, 2);
|
|
if (fPrint && this.messageEnabled(Messages.MEM)) {
|
|
this.message(sModule + ' ' + (fCode? "code" : "data") + '(' + str.toHex(nSegment, 4) + ")=#" + str.toHex(sel, 4) + " len " + str.toHex(len));
|
|
}
|
|
var off = 0;
|
|
var aSymbols = this.findModuleInfo(sModule, nSegment);
|
|
aSymbols[sModule + sSection] = off;
|
|
this.addSymbols(sModule, nSegment, sel, off, null, len, aSymbols);
|
|
};
|
|
|
|
/**
|
|
* removeSegmentInfo(sel, fPrint)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number} sel
|
|
* @param {boolean} [fPrint] (false means we're merely monitoring OR we don't really care about these notifications)
|
|
*/
|
|
Debugger.prototype.removeSegmentInfo = function(sel, fPrint)
|
|
{
|
|
var sModuleRemoved = this.removeSymbols(null, sel);
|
|
if (fPrint && this.messageEnabled(Messages.MEM)) {
|
|
if (sModuleRemoved) {
|
|
this.message(sModuleRemoved + " #" + str.toHex(sel, 4) + " removed");
|
|
} else {
|
|
this.message("unable to remove module for segment #" + str.toHex(sel, 4));
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* addSectionInfo(dbgAddr, fCode, fPrint)
|
|
*
|
|
* dbgAddr -> D386_Device_Params structure:
|
|
* DD_logical_seg dw ? ; logical segment # from map
|
|
* DD_actual_sel dw ? ; actual selector value
|
|
* DD_base dd ? ; linear address offset for start of segment
|
|
* DD_length dd ? ; actual length of segment
|
|
* DD_name df ? ; 16:32 ptr to null terminated module name
|
|
* DD_sym_name df ? ; 16:32 ptr to null terminated parent name (eg, "DOS386")
|
|
* DD_alias_sel dw ? ; alias selector value (0 = none)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {DbgAddr} dbgAddr (address of D386_Device_Params)
|
|
* @param {boolean} fCode (true if code section, false if data section)
|
|
* @param {boolean} [fPrint] (false means we're merely monitoring, so let WDEB386 print its own notifications)
|
|
*/
|
|
Debugger.prototype.addSectionInfo = function(dbgAddr, fCode, fPrint)
|
|
{
|
|
var nSegment = this.getShort(dbgAddr, 2);
|
|
var sel = this.getShort(dbgAddr, 2);
|
|
var off = this.getLong(dbgAddr, 4);
|
|
var len = this.getLong(dbgAddr, 4);
|
|
var dbgAddrModule = this.newAddr(this.getLong(dbgAddr, 4), this.getShort(dbgAddr, 2));
|
|
var dbgAddrParent = this.newAddr(this.getLong(dbgAddr, 4), this.getShort(dbgAddr, 2));
|
|
// sel = this.getShort(dbgAddr, 2) || sel;
|
|
var sParent = this.getSZ(dbgAddrParent).toUpperCase();
|
|
var sModule = this.getSZ(dbgAddrModule).toUpperCase();
|
|
if (sParent == sModule) {
|
|
sParent = "";
|
|
} else {
|
|
sParent += '!';
|
|
}
|
|
var sSection = (fCode? "_CODE" : "_DATA") + str.toHex(nSegment, 2);
|
|
if (fPrint && this.messageEnabled(Messages.MEM)) {
|
|
/*
|
|
* Mimics WDEB386 output, except that WDEB386 only displays a linear address, omitting the selector.
|
|
*/
|
|
this.message(sParent + sModule + ' ' + (fCode? "code" : "data") + '(' + str.toHex(nSegment, 4) + ")=" + str.toHex(sel, 4) + ':' + str.toHex(off) + " len " + str.toHex(len));
|
|
}
|
|
/*
|
|
* TODO: Add support for 32-bit symbols; findModuleInfo() relies on Disk.getModuleInfo(), and the Disk
|
|
* component doesn't yet know how to parse 32-bit executables.
|
|
*/
|
|
var aSymbols = this.findModuleInfo(sModule, nSegment);
|
|
aSymbols[sModule + sSection] = off;
|
|
this.addSymbols(sModule, nSegment, sel, off, null, len, aSymbols);
|
|
};
|
|
|
|
/**
|
|
* removeSectionInfo(nSegment, dbgAddr, fPrint)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number} nSegment (logical segment number)
|
|
* @param {DbgAddr} dbgAddr (address of module)
|
|
* @param {boolean} [fPrint] (false means we're merely monitoring OR we don't really care about these notifications)
|
|
*/
|
|
Debugger.prototype.removeSectionInfo = function(nSegment, dbgAddr, fPrint)
|
|
{
|
|
var sModule = this.getSZ(dbgAddr).toUpperCase();
|
|
var sModuleRemoved = this.removeSymbols(sModule, nSegment);
|
|
if (fPrint && this.messageEnabled(Messages.MEM)) {
|
|
if (sModuleRemoved) {
|
|
this.message(sModule + ' ' + str.toHex(nSegment, 4) + " removed");
|
|
} else {
|
|
this.message("unable to remove " + sModule + " for section " + str.toHex(nSegment, 4));
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* intWindowsCallBack()
|
|
*
|
|
* This intercepts calls to Windows callback addresses, which use INT 0x30 (aka Transfer Space Faults).
|
|
*
|
|
* We're only interested in one particular callback: the VW32_Int41Dispatch (0x002A002A) that KERNEL32
|
|
* issues as 32-bit executable sections are loaded.
|
|
*
|
|
* At the time that INT 0x30 occurs, a far 32-bit call has been made, preceded by a near 32-bit call,
|
|
* preceded by a 32-bit push of the Windows Debugger function # that would normally be in EAX if this had
|
|
* been an actual INT 0x41.
|
|
*
|
|
* NOTE: Regardless whether we're "handling" INT 0x41 or merely "monitoring" INT 0x41, as far as THIS
|
|
* interrupt is concerned, we always let the system process it, because execution never continues at the
|
|
* instruction following an INT 0x30; in fact, execution doesn't even continue after the far 32-bit call
|
|
* (even though the kernel places a "RET 4" after that call). So, rather than recreate all that automatic
|
|
* address popping, we let the system do it for us, since it's designed to work whether a debugger (eg,
|
|
* WDEB386's DEBUG VxD) is installed or not.
|
|
*
|
|
* TODO: Consider "consuming" all VW32_Int41Dispatch callbacks, because the Windows 95 kernel goes to
|
|
* great effort to pass those requests on to the DEBUG VxD, which end up going nowhere when the VxD isn't
|
|
* loaded (to load it, you must either run WDEB386.EXE or install the VxD via SYSTEM.INI). Regrettably,
|
|
* Windows 95 assumes that if WDEB386 support is present, then a DEBUG VxD must be present as well.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number} addr
|
|
* @return {boolean} true to proceed with the INT 0x30 software interrupt
|
|
*/
|
|
Debugger.prototype.intWindowsCallBack = function(addr)
|
|
{
|
|
var cpu = this.cpu;
|
|
|
|
if (this.fWinDbg != null && cpu.regEAX == 0x002A002A) {
|
|
|
|
var DX = cpu.regEDX & 0xffff;
|
|
var SI = cpu.regESI & 0xffff;
|
|
var dbgAddr = this.newAddr(cpu.getSP() + 0x0C, cpu.getSS());
|
|
var EAX = this.getLong(dbgAddr);
|
|
|
|
switch(EAX) {
|
|
case Interrupts.WINDBG.LOADSEG32:
|
|
/*
|
|
* SI == segment type:
|
|
* 0x0 code selector
|
|
* 0x1 data selector
|
|
* DX:EBX -> D386_Device_Params structure (see addSectionInfo() for details)
|
|
*/
|
|
this.addSectionInfo(this.newAddr(cpu.regEBX, DX), !SI, !!this.fWinDbg);
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* intWindowsDebugger()
|
|
*
|
|
* This intercepts calls to the Windows Debugger protected-mode interface (INT 0x41).
|
|
*
|
|
* It's enabled if Interrupts.WINDBG.ENABLED is true, but it must ALSO be enabled if
|
|
* Interrupts.WINDBGRM.ENABLED is true, because if the latter decides to respond to requests,
|
|
* then we must start responding, too. Windows assumes that if INT 0x68 support is present,
|
|
* then INT 0x41 support must be present as well.
|
|
*
|
|
* That is why intWindowsDebuggerRM() will also set this.fWinDbg to true: we MUST return false
|
|
* for all INT 0x41 requests, so that all requests are consumed, since there's no guarantee
|
|
* that a valid INT 0x41 handler will exist inside the machine.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number} addr
|
|
* @return {boolean} true to proceed with the INT 0x41 software interrupt, false to skip
|
|
*/
|
|
Debugger.prototype.intWindowsDebugger = function(addr)
|
|
{
|
|
var dbgAddr;
|
|
var cpu = this.cpu;
|
|
var AX = cpu.regEAX & 0xffff;
|
|
var BX = cpu.regEBX & 0xffff;
|
|
var CX = cpu.regECX & 0xffff;
|
|
var DX = cpu.regEDX & 0xffff;
|
|
var SI = cpu.regESI & 0xffff;
|
|
var DI = cpu.regEDI & 0xffff;
|
|
var ES = cpu.segES.sel;
|
|
|
|
if (this.fWinDbg == null) {
|
|
if (AX == Interrupts.WINDBG.IS_LOADED) {
|
|
/*
|
|
* We're only going to respond to this function if no one else did, in which case,
|
|
* we'll set fWinDbg to true and handle additional notifications.
|
|
*/
|
|
cpu.addIntReturn(addr, function(dbg) {
|
|
return function onInt41Return(nLevel) {
|
|
if ((cpu.regEAX & 0xffff) != Interrupts.WINDBG.LOADED) {
|
|
cpu.regEAX = (cpu.regEAX & ~0xffff) | Interrupts.WINDBG.LOADED;
|
|
/*
|
|
* TODO: We need a DEBUGGER message category; using the MEM category for now.
|
|
*/
|
|
dbg.printMessage("INT 0x41 handling enabled", Messages.MEM);
|
|
dbg.fWinDbg = true;
|
|
} else {
|
|
dbg.printMessage("INT 0x41 monitoring enabled", Messages.MEM);
|
|
dbg.fWinDbg = false;
|
|
}
|
|
};
|
|
}(this));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* NOTE: If this.fWinDbg is true, then all cases should return false, because we're taking full
|
|
* responsibility for all requests (don't assume there's valid interrupt handler inside the machine).
|
|
*/
|
|
switch(AX) {
|
|
case Interrupts.WINDBG.IS_LOADED: // 0x004F
|
|
if (this.fWinDbg) {
|
|
cpu.regEAX = (cpu.regEAX & ~0xffff) | Interrupts.WINDBG.LOADED;
|
|
this.printMessage("INT 0x41 handling enabled", Messages.MEM);
|
|
}
|
|
break;
|
|
|
|
case Interrupts.WINDBG.LOADSEG: // 0x0050
|
|
this.addSegmentInfo(this.newAddr(DI, ES), BX+1, CX, !(SI & 0x1), !!this.fWinDbg);
|
|
break;
|
|
|
|
case Interrupts.WINDBG.FREESEG: // 0x0052
|
|
this.removeSegmentInfo(BX);
|
|
break;
|
|
|
|
case Interrupts.WINDBG.KRNLVARS: // 0x005A
|
|
/*
|
|
* BX = version number of this data (0x3A0)
|
|
* DX:CX points to:
|
|
* WORD hGlobalHeap ****
|
|
* WORD pGlobalHeap ****
|
|
* WORD hExeHead ****
|
|
* WORD hExeSweep
|
|
* WORD topPDB
|
|
* WORD headPDB
|
|
* WORD topsizePDB
|
|
* WORD headTDB ****
|
|
* WORD curTDB ****
|
|
* WORD loadTDB
|
|
* WORD LockTDB
|
|
* WORD SelTableLen ****
|
|
* DWORD SelTableStart ****
|
|
*/
|
|
break;
|
|
|
|
case Interrupts.WINDBG.RELSEG: // 0x005C
|
|
case Interrupts.WINDBG.EXITCALL: // 0x0062
|
|
case Interrupts.WINDBG.LOADDLL: // 0x0064
|
|
case Interrupts.WINDBG.DELMODULE: // 0x0065
|
|
case Interrupts.WINDBG.UNKNOWN66: // 0x0066
|
|
case Interrupts.WINDBG.UNKNOWN67: // 0x0067
|
|
/*
|
|
* TODO: Figure out what to do with these notifications, if anything
|
|
*/
|
|
break;
|
|
|
|
case Interrupts.WINDBG.LOADHIGH: // 0x005D
|
|
case Interrupts.WINDBG.REGDOTCMD: // 0x0070
|
|
case Interrupts.WINDBG.CONDBP: // 0xF001
|
|
break;
|
|
|
|
case Interrupts.WINDBG.CHECKFAULT: // 0x007F
|
|
if (this.fWinDbg) {
|
|
/*
|
|
* AX == 0 means handle fault normally, 1 means issue TRAPFAULT
|
|
*/
|
|
cpu.regEAX = (cpu.regEAX & ~0xffff) | (this.fIgnoreNextCheckFault? 0 : 1);
|
|
if (DEBUG) this.println("INT 0x41 CHECKFAULT: fault=" + str.toHexWord(BX) + " type=" + str.toHexWord(CX) + " trap=" + !this.fIgnoreNextCheckFault);
|
|
}
|
|
break;
|
|
|
|
case Interrupts.WINDBG.TRAPFAULT: // 0x0083
|
|
/*
|
|
* If we responded with AX == 1 to a preceding CHECKFAULT notification, then we should receive the
|
|
* following TRAPFAULT notification; additionally, a TRAPFAULT notification may be issued without
|
|
* any CHECKFAULT warning if the user was presented with a fault dialog containing a "Debug" button,
|
|
* and the user clicked it.
|
|
*
|
|
* Regardless, whenever we receive this notification, we allocate a temporary breakpoint at the
|
|
* reported fault address.
|
|
*/
|
|
if (this.fWinDbg) {
|
|
dbgAddr = this.newAddr(cpu.regEDX, CX);
|
|
if (!this.cTrapFaults++) {
|
|
this.println("INT 0x41 TRAPFAULT: fault=" + str.toHexWord(BX) + " error=" + str.toHexLong(cpu.regESI) + " addr=" + this.toHexAddr(dbgAddr));
|
|
this.addBreakpoint(this.aBreakExec, dbgAddr, true);
|
|
this.historyInit(true); // temporary breakpoints don't normally trigger history, but in this case, we want it to
|
|
} else {
|
|
this.println("TRAPFAULT failed");
|
|
this.findBreakpoint(this.aBreakExec, dbgAddr, true, true, true);
|
|
this.cTrapFaults = 0;
|
|
this.stopCPU();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Interrupts.WINDBG.GETSYMBOL: // 0x008D
|
|
if (this.fWinDbg) cpu.regEAX = (cpu.regEAX & ~0xffff)|1; // AX == 1 means not found
|
|
break;
|
|
|
|
case Interrupts.WINDBG.LOADSEG32: // 0x0150
|
|
/*
|
|
* SI == segment type:
|
|
* 0x0 code selector
|
|
* 0x1 data selector
|
|
* DX:EBX -> D386_Device_Params structure (see addSectionInfo() for details)
|
|
*/
|
|
this.addSectionInfo(this.newAddr(cpu.regEBX, DX), !SI, !!this.fWinDbg);
|
|
break;
|
|
|
|
case Interrupts.WINDBG.FREESEG32: // 0x0152
|
|
/*
|
|
* BX == segment number
|
|
* DX:EDI -> module name
|
|
*/
|
|
this.removeSectionInfo(BX, this.newAddr(cpu.regEDI, DX));
|
|
break;
|
|
|
|
default:
|
|
if (DEBUG && this.fWinDbg) {
|
|
this.println("INT 0x41: " + str.toHexWord(AX));
|
|
}
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Let's try to limit the scope of any "gt" command by resetting this flag after any INT 0x41
|
|
*/
|
|
this.fIgnoreNextCheckFault = false;
|
|
|
|
return !this.fWinDbg;
|
|
};
|
|
}
|
|
|
|
if (Interrupts.WINDBGRM.ENABLED) {
|
|
/**
|
|
* intWindowsDebuggerRM()
|
|
*
|
|
* This intercepts calls to the Windows Debugger real-mode interface (INT 0x68).
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number} addr
|
|
* @return {boolean} true to proceed with the INT 0x68 software interrupt, false to skip
|
|
*/
|
|
Debugger.prototype.intWindowsDebuggerRM = function(addr)
|
|
{
|
|
var cpu = this.cpu;
|
|
var AL = cpu.regEAX & 0xff;
|
|
var AH = (cpu.regEAX >> 8) & 0xff;
|
|
var BX = cpu.regEBX & 0xffff;
|
|
var CX = cpu.regECX & 0xffff;
|
|
var DX = cpu.regEDX & 0xffff;
|
|
var DI = cpu.regEDI & 0xffff;
|
|
var ES = cpu.segES.sel;
|
|
|
|
if (this.fWinDbgRM == null) {
|
|
if (AH == Interrupts.WINDBGRM.IS_LOADED) {
|
|
/*
|
|
* It looks like IFSHLP.SYS issues a preliminary INT 0x68 before Windows 95 gets rolling,
|
|
* and the Windows Debugger will not have had a chance to load yet, so we need to ignore
|
|
* that call. We detect IFSHLP.SYS by looking for "IFS$" in the caller's code segment,
|
|
* where the IFSHLP device driver header is located.
|
|
*/
|
|
if (cpu.getLong((cpu.segCS.sel << 4) + 0x0A) == 0x24534649) {
|
|
if (DEBUG) this.println("Ignoring INT 0x68 from IFSHLP.SYS");
|
|
return true;
|
|
}
|
|
/*
|
|
* Ditto for WDEB386 itself, which presumably wants to avoid loading on top of itself.
|
|
*/
|
|
if (cpu.getLong((cpu.segCS.sel << 4) + 0x5F) == 0x42454457) {
|
|
if (DEBUG) this.println("Ignoring INT 0x68 from WDEB386.EXE");
|
|
return true;
|
|
}
|
|
/*
|
|
* We're only going to respond to this function if no one else did, in which case, we'll set
|
|
* fWinDbgRM to true and handle additional notifications.
|
|
*/
|
|
cpu.addIntReturn(addr, function(dbg) {
|
|
return function onInt68Return(nLevel) {
|
|
if ((cpu.regEAX & 0xffff) != Interrupts.WINDBGRM.LOADED) {
|
|
cpu.regEAX = (cpu.regEAX & ~0xffff) | Interrupts.WINDBGRM.LOADED;
|
|
dbg.printMessage("INT 0x68 handling enabled", Messages.MEM);
|
|
/*
|
|
* If we turn on INT 0x68 handling, we must also turn on INT 0x41 handling,
|
|
* because Windows assumes that the latter handler exists whenever the former does.
|
|
*/
|
|
dbg.fWinDbg = dbg.fWinDbgRM = true;
|
|
} else {
|
|
dbg.printMessage("INT 0x68 monitoring enabled", Messages.MEM);
|
|
dbg.fWinDbgRM = false;
|
|
}
|
|
};
|
|
}(this));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* NOTE: If this.fWinDbgRM is true, then all cases should return false, because we're taking full
|
|
* responsibility for all requests (don't assume there's valid interrupt handler inside the machine).
|
|
*/
|
|
switch(AH) {
|
|
case Interrupts.WINDBGRM.IS_LOADED: // 0x43
|
|
if (this.fWinDbgRM) {
|
|
cpu.regEAX = (cpu.regEAX & ~0xffff) | Interrupts.WINDBGRM.LOADED;
|
|
}
|
|
break;
|
|
|
|
case Interrupts.WINDBGRM.PREP_PMODE: // 0x44
|
|
if (this.fWinDbgRM) {
|
|
/*
|
|
* Use our fancy new "call break" mechanism to obtain a special address that will
|
|
* trap all calls, routing control to the specified function (callWindowsDebuggerPMInit).
|
|
*/
|
|
var a = cpu.segCS.addCallBreak(this.callWindowsDebuggerPMInit.bind(this));
|
|
if (a) {
|
|
cpu.regEDI = a[0]; // ES:EDI receives the "call break" address
|
|
cpu.setES(a[1]);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Interrupts.WINDBGRM.FREESEG: // 0x48
|
|
this.removeSegmentInfo(BX);
|
|
break;
|
|
|
|
case Interrupts.WINDBGRM.REMOVESEGS: // 0x4F
|
|
/*
|
|
* TODO: This probably just signals the end of module loading; nothing is required, but we should
|
|
* clean up whatever we can....
|
|
*/
|
|
break;
|
|
|
|
case Interrupts.WINDBGRM.LOADSEG: // 0x50
|
|
if (AL == 0x20) {
|
|
/*
|
|
* Real-mode EXE
|
|
* CX == paragraph
|
|
* ES:DI -> module name
|
|
*/
|
|
this.addSegmentInfo(this.newAddr(DI, ES), 0, CX, true, !!this.fWinDbgRM);
|
|
}
|
|
else if (AL < 0x80) {
|
|
/*
|
|
* AL == segment type:
|
|
* 0x00 code selector
|
|
* 0x01 data selector
|
|
* 0x10 code segment
|
|
* 0x11 data segment
|
|
* 0x40 code segment & sel
|
|
* 0x41 data segment & sel
|
|
* BX == segment #
|
|
* CX == actual segment/selector
|
|
* DX == actual selector (if 0x40 or 0x41)
|
|
* ES:DI -> module name
|
|
*/
|
|
this.addSegmentInfo(this.newAddr(DI, ES), BX+1, (AL & 0x40)? DX : CX, !(AL & 0x1), !!this.fWinDbgRM);
|
|
}
|
|
else {
|
|
/*
|
|
* AL == segment type:
|
|
* 0x80 device driver code seg
|
|
* 0x81 device driver data seg
|
|
* ES:DI -> D386_Device_Params structure (see addSectionInfo() for details)
|
|
*/
|
|
this.addSectionInfo(this.newAddr(DI, ES), !(AL & 0x1), !!this.fWinDbgRM);
|
|
}
|
|
if (this.fWinDbgRM) {
|
|
cpu.regEAX = (cpu.regEAX & ~0xff) | 0x01;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if (DEBUG && this.fWinDbgRM) {
|
|
this.println("INT 0x68: " + str.toHexByte(AH));
|
|
}
|
|
break;
|
|
}
|
|
|
|
return !this.fWinDbgRM;
|
|
};
|
|
|
|
/**
|
|
* callWindowsDebuggerPMInit()
|
|
*
|
|
* This intercepts calls to the Windows Debugger "PMInit" interface; eg:
|
|
*
|
|
* AL = function code
|
|
*
|
|
* 0 - initialize IDT
|
|
* ES:EDI points to protected mode IDT
|
|
*
|
|
* 1 - initialize page checking
|
|
* BX = physical selector
|
|
* ECX = linear bias
|
|
*
|
|
* 2 - specify that debug queries are supported
|
|
*
|
|
* 3 - initialize spare PTE
|
|
* EBX = linear address of spare PTE
|
|
* EDX = linear address the PTE represents
|
|
*
|
|
* 4 - set Enter/Exit VMM routine address
|
|
* EBX = Enter VMM routine address
|
|
* ECX = Exit VMM routine address
|
|
* EDX = $_Debug_Out_Service address
|
|
* ESI = $_Trace_Out_Service address
|
|
* The VMM enter/exit routines must return with a retfd
|
|
*
|
|
* 5 - get debugger size/physical address
|
|
* returns: AL = 0 (don't call AL = 1)
|
|
* ECX = size in bytes
|
|
* ESI = starting physical code/data address
|
|
*
|
|
* 6 - set debugger base/initialize spare PTE
|
|
* EBX = linear address of spare PTE
|
|
* EDX = linear address the PTE represents
|
|
* ESI = starting linear address of debug code/data
|
|
*
|
|
* 7 - enable memory context functions
|
|
*
|
|
* @this {Debugger}
|
|
* @return {boolean} (must always return false to skip the call, because the call is using a CALLBREAK address)
|
|
*/
|
|
Debugger.prototype.callWindowsDebuggerPMInit = function()
|
|
{
|
|
var cpu = this.cpu;
|
|
var AL = cpu.regEAX & 0xff;
|
|
if (MAXDEBUG) this.println("INT 0x68 callback: " + str.toHexByte(AL));
|
|
if (AL == 5) {
|
|
cpu.regECX = cpu.regESI = 0; // our in-machine debugger footprint is zero
|
|
cpu.regEAX = (cpu.regEAX & ~0xff) | 0x01; // TODO: Returning a "don't call" response sounds good, but what does it REALLY mean?
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* setBinding(sHTMLType, sBinding, control, sValue)
|
|
*
|
|
* @this {Debugger}
|
|
* @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
|
|
*/
|
|
Debugger.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 == Keyboard.KEYCODE.CR) {
|
|
sCmds = control.value;
|
|
control.value = "";
|
|
dbg.doCommands(sCmds, true);
|
|
}
|
|
else if (event.keyCode == Keyboard.KEYCODE.ESC) {
|
|
control.value = sCmds = "";
|
|
}
|
|
else {
|
|
if (event.keyCode == Keyboard.KEYCODE.UP) {
|
|
if (dbg.iPrevCmd < dbg.aPrevCmds.length - 1) {
|
|
sCmds = dbg.aPrevCmds[++dbg.iPrevCmd];
|
|
}
|
|
}
|
|
else if (event.keyCode == Keyboard.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 {Debugger}
|
|
*/
|
|
Debugger.prototype.updateFocus = function()
|
|
{
|
|
if (this.controlDebug) this.controlDebug.focus();
|
|
};
|
|
|
|
/**
|
|
* getCPUMode()
|
|
*
|
|
* @this {Debugger}
|
|
* @return {boolean} (true if protected mode, false if not)
|
|
*/
|
|
Debugger.prototype.getCPUMode = function()
|
|
{
|
|
return this.cpu && !!(this.cpu.regCR0 & X86.CR0.MSW.PE) && !(this.cpu.regPS & X86.PS.VM);
|
|
};
|
|
|
|
/**
|
|
* getAddressType()
|
|
*
|
|
* @this {Debugger}
|
|
* @return {number}
|
|
*/
|
|
Debugger.prototype.getAddressType = function()
|
|
{
|
|
return this.getCPUMode()? Debugger.ADDRTYPE.PROT : Debugger.ADDRTYPE.REAL;
|
|
};
|
|
|
|
/**
|
|
* getSegment(sel, type)
|
|
*
|
|
* If the selector matches that of any of the CPU segment registers, then return the CPU's segment
|
|
* register, instead of using our own segDebugger segment register. This makes it possible for us to
|
|
* see what the CPU is seeing at certain critical junctures, such as after an LMSW instruction has
|
|
* switched the processor from real to protected mode. Actually loading the selector from the GDT/LDT
|
|
* should be done only as a last resort.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number|null|undefined} sel
|
|
* @param {number} [type] (defaults to getAddressType())
|
|
* @return {X86Seg|null} seg
|
|
*/
|
|
Debugger.prototype.getSegment = function(sel, type)
|
|
{
|
|
var typeDefault = this.getAddressType();
|
|
|
|
if (!type) type = typeDefault;
|
|
|
|
if (type == typeDefault) {
|
|
if (sel === this.cpu.getCS()) return this.cpu.segCS;
|
|
if (sel === this.cpu.getDS()) return this.cpu.segDS;
|
|
if (sel === this.cpu.getES()) return this.cpu.segES;
|
|
if (sel === this.cpu.getSS()) return this.cpu.segSS;
|
|
if (I386 && this.cpu.model >= X86.MODEL_80386) {
|
|
if (sel === this.cpu.getFS()) return this.cpu.segFS;
|
|
if (sel === this.cpu.getGS()) return this.cpu.segGS;
|
|
}
|
|
/*
|
|
* Even if nSuppressBreaks is set, we'll allow the call in real-mode,
|
|
* because a loadReal() request using segDebugger should generally be safe.
|
|
*/
|
|
if (this.nSuppressBreaks && type == Debugger.ADDRTYPE.PROT || !this.segDebugger) return null;
|
|
}
|
|
var seg = this.segDebugger;
|
|
if (type != Debugger.ADDRTYPE.PROT) {
|
|
seg.loadReal(sel);
|
|
seg.limit = 0xffff; // although an ACTUAL real-mode segment load would not modify the limit,
|
|
seg.offMax = 0x10000; // proper segDebugger operation requires that we update the limit ourselves
|
|
} else {
|
|
seg.probeDesc(sel);
|
|
}
|
|
return seg;
|
|
};
|
|
|
|
/**
|
|
* getAddr(dbgAddr, fWrite, nb)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {DbgAddr|null|undefined} dbgAddr
|
|
* @param {boolean} [fWrite]
|
|
* @param {number} [nb] number of bytes to check (1, 2 or 4); default is 1
|
|
* @return {number} is the corresponding linear address, or X86.ADDR_INVALID
|
|
*/
|
|
Debugger.prototype.getAddr = function(dbgAddr, fWrite, nb)
|
|
{
|
|
/*
|
|
* Some addresses (eg, breakpoint addresses) save their original linear address in dbgAddr.addr,
|
|
* so we want to use that if it's there, but otherwise, dbgAddr is assumed to be a segmented address
|
|
* whose linear address must always be (re)calculated based on current machine state (mode, active
|
|
* descriptor tables, etc).
|
|
*/
|
|
var addr = dbgAddr && dbgAddr.addr;
|
|
if (addr == null) {
|
|
addr = X86.ADDR_INVALID;
|
|
if (dbgAddr) {
|
|
/*
|
|
* TODO: We should try to cache the seg inside dbgAddr, to avoid unnecessary calls to getSegment().
|
|
*/
|
|
var seg = this.getSegment(dbgAddr.sel, dbgAddr.type);
|
|
if (seg) {
|
|
if (!fWrite) {
|
|
addr = seg.checkReadDebugger(dbgAddr.off || 0, nb || 1);
|
|
} else {
|
|
addr = seg.checkWriteDebugger(dbgAddr.off || 0, nb || 1);
|
|
}
|
|
dbgAddr.addr = addr;
|
|
}
|
|
}
|
|
}
|
|
return addr;
|
|
};
|
|
|
|
/**
|
|
* getByte(dbgAddr, inc)
|
|
*
|
|
* We must route all our memory requests through the CPU now, in case paging is enabled.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {DbgAddr} dbgAddr
|
|
* @param {number} [inc]
|
|
* @return {number}
|
|
*/
|
|
Debugger.prototype.getByte = function(dbgAddr, inc)
|
|
{
|
|
var b = 0xff;
|
|
var addr = this.getAddr(dbgAddr, false, 1);
|
|
if (addr !== X86.ADDR_INVALID) {
|
|
b = this.cpu.probeAddr(addr, 1, dbgAddr.type == Debugger.ADDRTYPE.PHYSICAL) | 0;
|
|
if (inc) this.incAddr(dbgAddr, inc);
|
|
}
|
|
return b;
|
|
};
|
|
|
|
/**
|
|
* getWord(dbgAddr, fAdvance)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {DbgAddr} dbgAddr
|
|
* @param {boolean} [fAdvance]
|
|
* @return {number}
|
|
*/
|
|
Debugger.prototype.getWord = function(dbgAddr, fAdvance)
|
|
{
|
|
return dbgAddr.fData32? this.getLong(dbgAddr, fAdvance? 4 : 0) : this.getShort(dbgAddr, fAdvance? 2 : 0);
|
|
};
|
|
|
|
/**
|
|
* getShort(dbgAddr, inc)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {DbgAddr} dbgAddr
|
|
* @param {number} [inc]
|
|
* @return {number}
|
|
*/
|
|
Debugger.prototype.getShort = function(dbgAddr, inc)
|
|
{
|
|
var w = 0xffff;
|
|
var addr = this.getAddr(dbgAddr, false, 2);
|
|
if (addr !== X86.ADDR_INVALID) {
|
|
w = this.cpu.probeAddr(addr, 2, dbgAddr.type == Debugger.ADDRTYPE.PHYSICAL);
|
|
if (inc) this.incAddr(dbgAddr, inc);
|
|
}
|
|
return w;
|
|
};
|
|
|
|
/**
|
|
* getLong(dbgAddr, inc)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {DbgAddr} dbgAddr
|
|
* @param {number} [inc]
|
|
* @return {number}
|
|
*/
|
|
Debugger.prototype.getLong = function(dbgAddr, inc)
|
|
{
|
|
var l = -1;
|
|
var addr = this.getAddr(dbgAddr, false, 4);
|
|
if (addr !== X86.ADDR_INVALID) {
|
|
l = this.cpu.probeAddr(addr, 4, dbgAddr.type == Debugger.ADDRTYPE.PHYSICAL);
|
|
if (inc) this.incAddr(dbgAddr, inc);
|
|
}
|
|
return l;
|
|
};
|
|
|
|
/**
|
|
* setByte(dbgAddr, b, inc, fNoUpdate)
|
|
*
|
|
* NOTE: If you need to patch a ROM, you MUST use the ROM location's physical address.
|
|
*
|
|
* WARNING: Be careful with the editing commands that use function, because we don't have a safe
|
|
* counterpart to cpu.probeAddr().
|
|
*
|
|
* @this {Debugger}
|
|
* @param {DbgAddr} dbgAddr
|
|
* @param {number} b
|
|
* @param {number} [inc]
|
|
* @param {boolean} [fNoUpdate] (when doing a large number of setByte() calls, set this to true and call cpu.updateCPU() when you're done)
|
|
*/
|
|
Debugger.prototype.setByte = function(dbgAddr, b, inc, fNoUpdate)
|
|
{
|
|
var addr = this.getAddr(dbgAddr, true, 1);
|
|
if (addr !== X86.ADDR_INVALID) {
|
|
if (dbgAddr.type != Debugger.ADDRTYPE.PHYSICAL) {
|
|
this.cpu.setByte(addr, b);
|
|
} else {
|
|
this.bus.setByteDirect(addr, b);
|
|
}
|
|
if (inc) this.incAddr(dbgAddr, inc);
|
|
if (!fNoUpdate) this.cpu.updateCPU(true); // we set fForce to true in case video memory was the target
|
|
}
|
|
};
|
|
|
|
/**
|
|
* setShort(dbgAddr, w, inc)
|
|
*
|
|
* NOTE: If you need to patch a ROM, you MUST use the ROM location's physical address.
|
|
*
|
|
* WARNING: Be careful with the editing commands that use function, because we don't have a safe
|
|
* counterpart to cpu.probeAddr().
|
|
*
|
|
* @this {Debugger}
|
|
* @param {DbgAddr} dbgAddr
|
|
* @param {number} w
|
|
* @param {number} [inc]
|
|
*/
|
|
Debugger.prototype.setShort = function(dbgAddr, w, inc)
|
|
{
|
|
var addr = this.getAddr(dbgAddr, true, 2);
|
|
if (addr !== X86.ADDR_INVALID) {
|
|
if (dbgAddr.type != Debugger.ADDRTYPE.PHYSICAL) {
|
|
this.cpu.setShort(addr, w);
|
|
} else {
|
|
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(off, sel, addr, type, fData32, fAddr32)
|
|
*
|
|
* Returns a NEW DbgAddr object, initialized with specified values and/or defaults.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number|null|undefined} [off] (default is zero)
|
|
* @param {number|null|undefined} [sel] (default is undefined)
|
|
* @param {number|null|undefined} [addr] (default is undefined)
|
|
* @param {number} [type] (default is based on current CPU mode)
|
|
* @param {boolean} [fData32] (default is the current CPU operand size)
|
|
* @param {boolean} [fAddr32] (default is the current CPU address size)
|
|
* @return {DbgAddr}
|
|
*/
|
|
Debugger.prototype.newAddr = function(off, sel, addr, type, fData32, fAddr32)
|
|
{
|
|
return this.setAddr({}, off, sel, addr, type, fData32, fAddr32);
|
|
};
|
|
|
|
/**
|
|
* getAddrPrefix(dbgAddr)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {DbgAddr} dbgAddr
|
|
* @return {string}
|
|
*/
|
|
Debugger.prototype.getAddrPrefix = function(dbgAddr)
|
|
{
|
|
var ch;
|
|
|
|
switch (dbgAddr.type) {
|
|
case Debugger.ADDRTYPE.REAL:
|
|
case Debugger.ADDRTYPE.V86:
|
|
ch = '&';
|
|
break;
|
|
case Debugger.ADDRTYPE.PROT:
|
|
ch = '#';
|
|
break;
|
|
case Debugger.ADDRTYPE.LINEAR:
|
|
ch = '%';
|
|
break;
|
|
case Debugger.ADDRTYPE.PHYSICAL:
|
|
ch = '%%';
|
|
break;
|
|
default:
|
|
ch = dbgAddr.sel? '' : '%';
|
|
break;
|
|
}
|
|
return ch;
|
|
};
|
|
|
|
/**
|
|
* setAddr(dbgAddr, off, sel, addr, type, fData32, fAddr32)
|
|
*
|
|
* Updates an EXISTING DbgAddr object, initialized with specified values and/or defaults.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {DbgAddr} dbgAddr
|
|
* @param {number|null|undefined} [off] (default is zero)
|
|
* @param {number|null|undefined} [sel] (default is undefined)
|
|
* @param {number|null|undefined} [addr] (default is undefined)
|
|
* @param {number} [type] (default is based on current CPU mode)
|
|
* @param {boolean} [fData32] (default is the current CPU operand size)
|
|
* @param {boolean} [fAddr32] (default is the current CPU address size)
|
|
* @return {DbgAddr}
|
|
*/
|
|
Debugger.prototype.setAddr = function(dbgAddr, off, sel, addr, type, fData32, fAddr32)
|
|
{
|
|
dbgAddr.off = off || 0;
|
|
dbgAddr.sel = sel;
|
|
dbgAddr.addr = addr;
|
|
dbgAddr.type = type || this.getAddressType();
|
|
dbgAddr.fData32 = (fData32 != null)? fData32 : (this.cpu && this.cpu.segCS.sizeData == 4);
|
|
dbgAddr.fAddr32 = (fAddr32 != null)? fAddr32 : (this.cpu && this.cpu.segCS.sizeAddr == 4);
|
|
dbgAddr.fTempBreak = false;
|
|
return dbgAddr;
|
|
};
|
|
|
|
/**
|
|
* packAddr(dbgAddr)
|
|
*
|
|
* Packs a DbgAddr object into an Array suitable for saving in a machine state object.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {DbgAddr} dbgAddr
|
|
* @return {Array}
|
|
*/
|
|
Debugger.prototype.packAddr = function(dbgAddr)
|
|
{
|
|
return [dbgAddr.off, dbgAddr.sel, dbgAddr.addr, dbgAddr.fTempBreak, dbgAddr.fData32, dbgAddr.fAddr32, dbgAddr.cOverrides, dbgAddr.fComplete];
|
|
};
|
|
|
|
/**
|
|
* unpackAddr(aAddr)
|
|
*
|
|
* Unpacks a DbgAddr object from an Array created by packAddr() and restored from a saved machine state.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {Array} aAddr
|
|
* @return {DbgAddr}
|
|
*/
|
|
Debugger.prototype.unpackAddr = function(aAddr)
|
|
{
|
|
return {off: aAddr[0], sel: aAddr[1], addr: aAddr[2], fTempBreak: aAddr[3], fData32: aAddr[4], fAddr32: aAddr[5], cOverrides: aAddr[6], fComplete: aAddr[7]};
|
|
};
|
|
|
|
/**
|
|
* checkLimit(dbgAddr, fUpdate)
|
|
*
|
|
* Used by incAddr() and parseAddr() to ensure that the (updated) dbgAddr offset is within segment bounds.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {DbgAddr} dbgAddr
|
|
* @param {boolean} [fUpdate] (true to update segment info)
|
|
* @return {boolean}
|
|
*/
|
|
Debugger.prototype.checkLimit = function(dbgAddr, fUpdate)
|
|
{
|
|
if (dbgAddr.sel != null) {
|
|
var seg = this.getSegment(dbgAddr.sel, dbgAddr.type);
|
|
if (seg) {
|
|
var off = dbgAddr.off & seg.maskAddr;
|
|
if (!seg.fExpDown) {
|
|
if ((off >>> 0) >= seg.offMax) {
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
if ((off >>> 0) < seg.offMax) {
|
|
return false;
|
|
}
|
|
}
|
|
if (fUpdate) {
|
|
dbgAddr.off = off;
|
|
dbgAddr.fData32 = (seg.sizeData == 4);
|
|
dbgAddr.fAddr32 = (seg.sizeAddr == 4);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* parseAddr(sAddr, fCode, fNoChecks, fPrint)
|
|
*
|
|
* As discussed above, dbgAddr variables contain one or more of: off, sel, and addr. They represent
|
|
* a segmented address (sel:off) when sel is defined or a linear address (addr) when sel is undefined
|
|
* (or null).
|
|
*
|
|
* To create a segmented address, specify two values separated by ':'; for a linear address, use
|
|
* a '%' prefix. We check for ':' after '%', so if for some strange reason you specify both, the
|
|
* address will be treated as segmented, not linear.
|
|
*
|
|
* The '%' syntax is similar to that used by the Windows 80386 kernel debugger (wdeb386) for linear
|
|
* addresses. If/when we add support for processors with page tables, we will likely adopt the same
|
|
* convention for linear addresses and provide a different syntax (eg, "%%") physical memory references.
|
|
*
|
|
* Address evaluation and validation (eg, range checks) are no longer performed at this stage. That's
|
|
* done later, by getAddr(), which returns X86.ADDR_INVALID for invalid segments, out-of-range offsets,
|
|
* etc. The Debugger's low-level get/set memory functions verify all getAddr() results, but even if an
|
|
* invalid address is passed through to the Bus memory interfaces, the address will simply be masked with
|
|
* Bus.nBusLimit; in the case of X86.ADDR_INVALID, that will generally refer to the top of the physical
|
|
* address space.
|
|
*
|
|
* @this {Debugger}
|
|
* @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 {DbgAddr|null|undefined}
|
|
*/
|
|
Debugger.prototype.parseAddr = function(sAddr, fCode, fNoChecks, fPrint)
|
|
{
|
|
var dbgAddr;
|
|
var dbgAddrNext = (fCode? this.dbgAddrNextCode : this.dbgAddrNextData);
|
|
|
|
var type = fNoChecks? Debugger.ADDRTYPE.NONE : dbgAddrNext.type;
|
|
var off = dbgAddrNext.off, sel = dbgAddrNext.sel, addr = dbgAddrNext.addr;
|
|
|
|
if (sAddr !== undefined) {
|
|
|
|
sAddr = this.parseReference(sAddr);
|
|
|
|
var ch = sAddr.charAt(0);
|
|
var iColon = sAddr.indexOf(':');
|
|
|
|
switch(ch) {
|
|
case '&':
|
|
type = Debugger.ADDRTYPE.REAL;
|
|
break;
|
|
case '#':
|
|
type = Debugger.ADDRTYPE.PROT;
|
|
break;
|
|
case '%':
|
|
type = Debugger.ADDRTYPE.LINEAR;
|
|
ch = sAddr.charAt(1);
|
|
if (ch == '%') {
|
|
type = Debugger.ADDRTYPE.PHYSICAL;
|
|
ch += ch;
|
|
}
|
|
off = addr = 0;
|
|
sel = null; // we still have code that relies on this crutch, instead of the type field
|
|
break;
|
|
default:
|
|
if (iColon >= 0) type = Debugger.ADDRTYPE.NONE;
|
|
ch = '';
|
|
break;
|
|
}
|
|
|
|
if (ch) {
|
|
sAddr = sAddr.substr(ch.length);
|
|
iColon -= ch.length;
|
|
}
|
|
|
|
dbgAddr = this.findSymbolAddr(sAddr);
|
|
if (dbgAddr) return dbgAddr;
|
|
|
|
if (iColon < 0) {
|
|
if (sel != null) {
|
|
off = this.parseExpression(sAddr, fPrint);
|
|
addr = null;
|
|
} else {
|
|
addr = this.parseExpression(sAddr, fPrint);
|
|
if (addr == null) off = null;
|
|
}
|
|
}
|
|
else {
|
|
sel = this.parseExpression(sAddr.substring(0, iColon), fPrint);
|
|
off = this.parseExpression(sAddr.substring(iColon + 1), fPrint);
|
|
addr = null;
|
|
}
|
|
}
|
|
|
|
if (off != null) {
|
|
dbgAddr = this.newAddr(off, sel, addr, type);
|
|
if (!fNoChecks && !this.checkLimit(dbgAddr, true)) {
|
|
this.println("invalid offset: " + this.toHexAddr(dbgAddr));
|
|
dbgAddr = null;
|
|
}
|
|
}
|
|
return dbgAddr;
|
|
};
|
|
|
|
/**
|
|
* parseAddrOptions(dbgAddr, sOptions)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {DbgAddr} dbgAddr
|
|
* @param {string} [sOptions]
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {DbgAddr} dbgAddr
|
|
* @param {number} [inc] contains value to increment dbgAddr by (default is 1)
|
|
*/
|
|
Debugger.prototype.incAddr = function(dbgAddr, inc)
|
|
{
|
|
inc = inc || 1;
|
|
if (dbgAddr.addr != null) {
|
|
dbgAddr.addr += inc;
|
|
}
|
|
if (dbgAddr.sel != null) {
|
|
dbgAddr.off += inc;
|
|
if (!this.checkLimit(dbgAddr)) {
|
|
dbgAddr.off = 0;
|
|
dbgAddr.addr = null;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* toHexOffset(off, sel, fAddr32)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number|null|undefined} [off]
|
|
* @param {number|null|undefined} [sel]
|
|
* @param {boolean} [fAddr32] is true for 32-bit ADDRESS size
|
|
* @return {string} the hex representation of off (or sel:off)
|
|
*/
|
|
Debugger.prototype.toHexOffset = function(off, sel, fAddr32)
|
|
{
|
|
if (sel != null) {
|
|
return str.toHex(sel, 4) + ':' + str.toHex(off, (off & ~0xffff) || fAddr32? 8 : 4);
|
|
}
|
|
return str.toHex(off);
|
|
};
|
|
|
|
/**
|
|
* toHexAddr(dbgAddr)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {DbgAddr} dbgAddr
|
|
* @return {string} the hex representation of the address
|
|
*/
|
|
Debugger.prototype.toHexAddr = function(dbgAddr)
|
|
{
|
|
var ch = this.getAddrPrefix(dbgAddr);
|
|
/*
|
|
* TODO: Revisit the decision to check sel == null; I would rather see these decisions based on type.
|
|
*/
|
|
return (dbgAddr.type >= Debugger.ADDRTYPE.LINEAR || dbgAddr.sel == null)? (ch + str.toHex(dbgAddr.addr)) : (ch + this.toHexOffset(dbgAddr.off, dbgAddr.sel, dbgAddr.fAddr32));
|
|
};
|
|
|
|
/**
|
|
* 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 {Debugger}
|
|
* @param {DbgAddr} dbgAddr
|
|
* @param {number} [cchMax] (default is 256)
|
|
* @return {string} (and dbgAddr advanced past the terminating zero)
|
|
*/
|
|
Debugger.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;
|
|
};
|
|
|
|
/**
|
|
* dumpBackTrack(asArgs)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {Array.<string>} asArgs
|
|
*/
|
|
Debugger.prototype.dumpBackTrack = function(asArgs)
|
|
{
|
|
var sInfo = "no information";
|
|
if (BACKTRACK) {
|
|
var sAddr = asArgs[0];
|
|
var dbgAddr = this.parseAddr(sAddr, true, true, false);
|
|
if (dbgAddr) {
|
|
var addr = this.getAddr(dbgAddr);
|
|
if (dbgAddr.type != Debugger.ADDRTYPE.PHYSICAL) {
|
|
var pageInfo = this.getPageInfo(addr);
|
|
if (pageInfo) {
|
|
dbgAddr.addr = pageInfo.addrPhys;
|
|
dbgAddr.type = Debugger.ADDRTYPE.PHYSICAL;
|
|
}
|
|
}
|
|
sInfo = this.toHexAddr(dbgAddr) + ": " + (this.bus.getSymbol(addr, true) || sInfo);
|
|
} else {
|
|
var component, componentPrev = null;
|
|
while (component = this.cmp.getMachineComponent("Disk", componentPrev)) {
|
|
var aInfo = component.getSymbolInfo(sAddr);
|
|
if (aInfo.length) {
|
|
sInfo = "";
|
|
for (var i in aInfo) {
|
|
var a = aInfo[i];
|
|
if (sInfo) sInfo += '\n';
|
|
sInfo += a[0] + ": " + a[1] + ' ' + str.toHex(a[2], 4) + ':' + str.toHex(a[3], 4) + " len " + str.toHexWord(a[4]);
|
|
}
|
|
}
|
|
componentPrev = component;
|
|
}
|
|
}
|
|
}
|
|
return sInfo;
|
|
};
|
|
|
|
/**
|
|
* dumpBlocks(aBlocks, sAddr, fLinear)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {Array} aBlocks
|
|
* @param {string} [sAddr] (optional block address)
|
|
* @param {boolean} [fLinear] (true if linear, physical otherwise)
|
|
*/
|
|
Debugger.prototype.dumpBlocks = function(aBlocks, sAddr, fLinear)
|
|
{
|
|
var addr = 0, i = 0, n = aBlocks.length;
|
|
|
|
if (sAddr) {
|
|
addr = this.getAddr(this.parseAddr(sAddr));
|
|
if (addr === X86.ADDR_INVALID) {
|
|
this.println("invalid address: " + sAddr);
|
|
return;
|
|
}
|
|
i = addr >>> this.cpu.nBlockShift;
|
|
n = 1;
|
|
}
|
|
|
|
this.println("blockid " + (fLinear? "linear " : "physical") + " blockaddr used size type");
|
|
this.println("-------- --------- ---------- ------ ------ ----");
|
|
|
|
var typePrev = -1, cPrev = 0;
|
|
while (n--) {
|
|
var block = aBlocks[i];
|
|
/*
|
|
* We need to replicate a portion of what probeAddr() does, which is to "peek" at the
|
|
* underlying physical block of any UNPAGED block. An UNPAGED block doesn't imply
|
|
* that the page is invalid, but merely that the CPU has not yet been asked to perform
|
|
* the page directory/page table lookup.
|
|
*
|
|
* To do that, we use the same mapPageBlock() interface that the CPU uses, with fSuppress
|
|
* set, so that it doesn't 1) generate a fault or 2) modify the block. Blocks should only
|
|
* "validated" when a CPU operation touches the corresponding page, and they should be only
|
|
* be "invalidated" when the CPU wants to flush the TLB (ie, whenever CR3 is updated).
|
|
*/
|
|
if (block && block.type == Memory.TYPE.UNPAGED) {
|
|
block = this.cpu.mapPageBlock(addr, false, true);
|
|
}
|
|
if (block.type == typePrev) {
|
|
if (!cPrev++) this.println("...");
|
|
} else {
|
|
typePrev = block.type;
|
|
var sType = Memory.TYPE.NAMES[typePrev];
|
|
if (typePrev == Memory.TYPE.PAGED) {
|
|
block = block.blockPhys;
|
|
this.assert(block);
|
|
sType += " -> " + Memory.TYPE.NAMES[block.type];
|
|
}
|
|
if (block) {
|
|
this.println(str.toHex(block.id) + " %" + str.toHex(i << this.cpu.nBlockShift) + " %%" + str.toHex(block.addr) + " " + str.toHexWord(block.used) + " " + str.toHexWord(block.size) + " " + sType);
|
|
}
|
|
if (typePrev != Memory.TYPE.NONE && typePrev != Memory.TYPE.UNPAGED) typePrev = -1;
|
|
cPrev = 0;
|
|
}
|
|
addr += this.cpu.nBlockSize;
|
|
i++;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* dumpBus(asArgs)
|
|
*
|
|
* Dumps Bus allocations.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {Array.<string>} asArgs (asArgs[0] is an optional block address)
|
|
*/
|
|
Debugger.prototype.dumpBus = function(asArgs)
|
|
{
|
|
this.dumpBlocks(this.cpu.aBusBlocks, asArgs[0]);
|
|
};
|
|
|
|
/**
|
|
* dumpDOS(asArgs)
|
|
*
|
|
* Dumps DOS MCBs (Memory Control Blocks).
|
|
*
|
|
* TODO: Add some code to detect the current version of DOS (if any) and locate the first MCB automatically.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {Array.<string>} asArgs
|
|
*/
|
|
Debugger.prototype.dumpDOS = function(asArgs)
|
|
{
|
|
var mcb;
|
|
var sMCB = asArgs[0];
|
|
if (sMCB) {
|
|
mcb = this.parseValue(sMCB);
|
|
}
|
|
if (mcb === undefined) {
|
|
this.println("invalid MCB");
|
|
return;
|
|
}
|
|
this.println("dumpMCB(" + str.toHexWord(mcb) + ')');
|
|
while (mcb) {
|
|
var dbgAddr = this.newAddr(0, mcb);
|
|
var bSig = this.getByte(dbgAddr, 1);
|
|
var wPID = this.getShort(dbgAddr, 2);
|
|
var wParas = this.getShort(dbgAddr, 5);
|
|
if (bSig != 0x4D && bSig != 0x5A) break;
|
|
this.println(this.toHexOffset(0, mcb) + ": '" + String.fromCharCode(bSig) + "' PID=" + str.toHexWord(wPID) + " LEN=" + str.toHexWord(wParas) + ' "' + this.getSZ(dbgAddr, 8) + '"');
|
|
mcb += 1 + wParas;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* dumpIDT(asArgs)
|
|
*
|
|
* Dumps an IDT vector entry.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {Array.<string>} asArgs
|
|
*/
|
|
Debugger.prototype.dumpIDT = function(asArgs)
|
|
{
|
|
var sIDT = asArgs[0];
|
|
|
|
if (!sIDT) {
|
|
this.println("no IDT vector");
|
|
return;
|
|
}
|
|
|
|
var nIDT = this.parseValue(sIDT);
|
|
if (nIDT === undefined || nIDT < 0 || nIDT > 255) {
|
|
this.println("invalid vector: " + sIDT);
|
|
return;
|
|
}
|
|
|
|
var ch = '&', fProt = this.cpu.getProtMode(), fAddr32 = false;
|
|
var addrIDT = this.cpu.addrIDT + (nIDT << (fProt? 3 : 2));
|
|
var off = this.cpu.getShort(addrIDT + X86.DESC.LIMIT.OFFSET);
|
|
var sel = this.cpu.getShort(addrIDT + X86.DESC.BASE.OFFSET);
|
|
if (fProt) {
|
|
ch = '#';
|
|
var acc = this.cpu.getShort(addrIDT + X86.DESC.ACC.OFFSET);
|
|
if (acc & X86.DESC.ACC.TYPE.NONSEG_386) {
|
|
fAddr32 = true;
|
|
off |= this.cpu.getShort(addrIDT + X86.DESC.EXT.OFFSET) << 16;
|
|
}
|
|
}
|
|
|
|
this.println("dumpIDT(" + str.toHexWord(nIDT) + "): " + ch + str.toHex(sel, 4) + ':' + str.toHex(off, fAddr32? 8 : 4));
|
|
};
|
|
|
|
/**
|
|
* dumpMem(asArgs)
|
|
*
|
|
* Dumps page allocations.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {Array.<string>} asArgs (asArgs[0] is an optional block address)
|
|
*/
|
|
Debugger.prototype.dumpMem = function(asArgs)
|
|
{
|
|
this.dumpBlocks(this.cpu.aMemBlocks, asArgs[0], this.cpu.aMemBlocks !== this.cpu.aBusBlocks);
|
|
};
|
|
|
|
/**
|
|
* getPageEntry(addrPE, lPE, fPTE)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number} addrPE
|
|
* @param {number} lPE
|
|
* @param {boolean} [fPTE] (true if the entry is a PTE, false if it's a PDE)
|
|
* @return {string}
|
|
*/
|
|
Debugger.prototype.getPageEntry = function(addrPE, lPE, fPTE)
|
|
{
|
|
var s = str.toHex(addrPE) + ' ' + str.toHex(lPE) + ' ';
|
|
s += (fPTE && (lPE & X86.PTE.DIRTY))? 'D' : '-';
|
|
s += (lPE & X86.PTE.ACCESSED)? 'A' : '-';
|
|
s += (lPE & X86.PTE.USER)? 'U' : 'S';
|
|
s += (lPE & X86.PTE.READWRITE)? 'W' : 'R';
|
|
s += (lPE & X86.PTE.PRESENT)? 'P' : 'N';
|
|
return s;
|
|
};
|
|
|
|
/**
|
|
* getPageInfo(addr)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number} addr
|
|
* @return {Object|null}
|
|
*/
|
|
Debugger.prototype.getPageInfo = function(addr)
|
|
{
|
|
var pageInfo = null;
|
|
if (I386 && this.cpu.model >= X86.MODEL_80386) {
|
|
var bus = this.bus;
|
|
/*
|
|
* Here begins code remarkably similar to mapPageBlock() (with fSuppress set).
|
|
*/
|
|
pageInfo = {};
|
|
pageInfo.offPDE = (addr & X86.LADDR.PDE.MASK) >>> X86.LADDR.PDE.SHIFT;
|
|
pageInfo.addrPDE = this.cpu.regCR3 + pageInfo.offPDE;
|
|
pageInfo.blockPDE = bus.aMemBlocks[(pageInfo.addrPDE & bus.nBusMask) >>> bus.nBlockShift];
|
|
pageInfo.lPDE = pageInfo.blockPDE.readLong(pageInfo.offPDE);
|
|
pageInfo.offPTE = (addr & X86.LADDR.PTE.MASK) >>> X86.LADDR.PTE.SHIFT;
|
|
pageInfo.addrPTE = (pageInfo.lPDE & X86.PTE.FRAME) + pageInfo.offPTE;
|
|
pageInfo.blockPTE = bus.aMemBlocks[(pageInfo.addrPTE & bus.nBusMask) >>> bus.nBlockShift];
|
|
pageInfo.lPTE = pageInfo.blockPTE.readLong(pageInfo.offPTE);
|
|
pageInfo.addrPhys = (pageInfo.lPTE & X86.PTE.FRAME) + (addr & X86.LADDR.OFFSET);
|
|
//var blockPhys = bus.aMemBlocks[(addrPhys & bus.nBusMask) >>> bus.nBlockShift];
|
|
}
|
|
return pageInfo;
|
|
};
|
|
|
|
/**
|
|
* dumpPage(asArgs)
|
|
*
|
|
* Dumps page table information about the given linear address.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {Array.<string>} asArgs
|
|
*/
|
|
Debugger.prototype.dumpPage = function(asArgs)
|
|
{
|
|
var sAddr = asArgs[0];
|
|
if (!sAddr) {
|
|
this.println("missing address");
|
|
return;
|
|
}
|
|
|
|
var addr = this.getAddr(this.parseAddr(sAddr));
|
|
if (addr === X86.ADDR_INVALID) {
|
|
this.println("invalid address: " + sAddr);
|
|
return;
|
|
}
|
|
|
|
var pageInfo = this.getPageInfo(addr);
|
|
if (!pageInfo) {
|
|
this.println("unsupported operation");
|
|
return;
|
|
}
|
|
|
|
this.println("linear PDE addr PDE PTE addr PTE physical" );
|
|
this.println("--------- ---------- -------- ---------- -------- ----------");
|
|
var s = '%' + str.toHex(addr);
|
|
s += " %%" + this.getPageEntry(pageInfo.addrPDE, pageInfo.lPDE);
|
|
s += " %%" + this.getPageEntry(pageInfo.addrPTE, pageInfo.lPTE, true);
|
|
s += " %%" + str.toHex(pageInfo.addrPhys);
|
|
this.println(s);
|
|
};
|
|
|
|
/*
|
|
* Table of system (non-segment) descriptors, including indicators of which ones are gates.
|
|
*/
|
|
Debugger.SYSDESCS = {
|
|
0x0100: ["tss286", false],
|
|
0x0200: ["ldt", false],
|
|
0x0300: ["busy tss286", false],
|
|
0x0400: ["call gate", true],
|
|
0x0500: ["task gate", true],
|
|
0x0600: ["int gate286", true],
|
|
0x0700: ["trap gate286", true],
|
|
0x0900: ["tss386", false],
|
|
0x0B00: ["busy tss386", false],
|
|
0x0C00: ["call gate386", true],
|
|
0x0E00: ["int gate386", true],
|
|
0x0F00: ["trap gate386", true]
|
|
};
|
|
|
|
/**
|
|
* dumpSel(asArgs)
|
|
*
|
|
* Dumps a descriptor for the given selector.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {Array.<string>} asArgs
|
|
*/
|
|
Debugger.prototype.dumpSel = function(asArgs)
|
|
{
|
|
var sSel = asArgs[0];
|
|
|
|
if (!sSel) {
|
|
this.println("no selector");
|
|
return;
|
|
}
|
|
|
|
var sel = this.parseValue(sSel);
|
|
if (sel === undefined) {
|
|
this.println("invalid selector: " + sSel);
|
|
return;
|
|
}
|
|
|
|
var seg = this.getSegment(sel, Debugger.ADDRTYPE.PROT);
|
|
this.println("dumpSel(" + str.toHexWord(seg? seg.sel : sel) + "): %" + str.toHex(seg? seg.addrDesc : null, this.cchAddr));
|
|
if (!seg) return;
|
|
|
|
var sType;
|
|
var fGate = false;
|
|
if (seg.type & X86.DESC.ACC.TYPE.SEG) {
|
|
if (seg.type & X86.DESC.ACC.TYPE.CODE) {
|
|
sType = "code";
|
|
sType += (seg.type & X86.DESC.ACC.TYPE.READABLE)? ",readable" : ",execonly";
|
|
if (seg.type & X86.DESC.ACC.TYPE.CONFORMING) sType += ",conforming";
|
|
}
|
|
else {
|
|
sType = "data";
|
|
sType += (seg.type & X86.DESC.ACC.TYPE.WRITABLE)? ",writable" : ",readonly";
|
|
if (seg.type & X86.DESC.ACC.TYPE.EXPDOWN) sType += ",expdown";
|
|
}
|
|
if (seg.type & X86.DESC.ACC.TYPE.ACCESSED) sType += ",accessed";
|
|
}
|
|
else {
|
|
var sysDesc = Debugger.SYSDESCS[seg.type];
|
|
if (sysDesc) {
|
|
sType = sysDesc[0];
|
|
fGate = sysDesc[1];
|
|
}
|
|
}
|
|
|
|
if (sType && !(seg.acc & X86.DESC.ACC.PRESENT)) sType += ",not present";
|
|
|
|
var sDump;
|
|
if (fGate) {
|
|
sDump = "seg=" + str.toHexWord(seg.base & 0xffff) + " off=" + str.toHexWord(seg.limit);
|
|
} else {
|
|
sDump = "base=" + str.toHex(seg.base, this.cchAddr) + " limit=" + this.getLimitString(seg.limit);
|
|
}
|
|
/*
|
|
* When we dump the EXT word, we mask off the LIMIT1619 and BASE2431 bits, because those have already
|
|
* been incorporated into the limit and base properties of the segment register; all we care about here
|
|
* are whether EXT contains any of the AVAIL (0x10), BIG (0x40) or LIMITPAGES (0x80) bits.
|
|
*/
|
|
this.println(sDump + " type=" + str.toHexByte(seg.type >> 8) + " (" + sType + ')' + " ext=" + str.toHexWord(seg.ext & ~(X86.DESC.EXT.LIMIT1619 | X86.DESC.EXT.BASE2431)) + " dpl=" + str.toHexByte(seg.dpl));
|
|
};
|
|
|
|
/**
|
|
* 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 {Debugger}
|
|
* @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)
|
|
*/
|
|
Debugger.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].sel == 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.sel == 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.off, dbgAddr.sel, dbgAddr.addr, dbgAddr.type, dbgAddr.fData32, dbgAddr.fAddr32);
|
|
|
|
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;
|
|
}
|
|
};
|
|
|
|
/*
|
|
* TSS field names and offsets used by dumpTSS()
|
|
*/
|
|
Debugger.TSS286 = {
|
|
"PREV_TSS": 0x00,
|
|
"CPL0_SP": 0x02,
|
|
"CPL0_SS": 0x04,
|
|
"CPL1_SP": 0x06,
|
|
"CPL1_SS": 0x08,
|
|
"CPL2_SP": 0x0a,
|
|
"CPL2_SS": 0x0c,
|
|
"TASK_IP": 0x0e,
|
|
"TASK_PS": 0x10,
|
|
"TASK_AX": 0x12,
|
|
"TASK_CX": 0x14,
|
|
"TASK_DX": 0x16,
|
|
"TASK_BX": 0x18,
|
|
"TASK_SP": 0x1a,
|
|
"TASK_BP": 0x1c,
|
|
"TASK_SI": 0x1e,
|
|
"TASK_DI": 0x20,
|
|
"TASK_ES": 0x22,
|
|
"TASK_CS": 0x24,
|
|
"TASK_SS": 0x26,
|
|
"TASK_DS": 0x28,
|
|
"TASK_LDT": 0x2a
|
|
};
|
|
Debugger.TSS386 = {
|
|
"PREV_TSS": 0x00,
|
|
"CPL0_ESP": 0x04,
|
|
"CPL0_SS": 0x08,
|
|
"CPL1_ESP": 0x0c,
|
|
"CPL1_SS": 0x10,
|
|
"CPL2_ESP": 0x14,
|
|
"CPL2_SS": 0x18,
|
|
"TASK_CR3": 0x1C,
|
|
"TASK_EIP": 0x20,
|
|
"TASK_PS": 0x24,
|
|
"TASK_EAX": 0x28,
|
|
"TASK_ECX": 0x2C,
|
|
"TASK_EDX": 0x30,
|
|
"TASK_EBX": 0x34,
|
|
"TASK_ESP": 0x38,
|
|
"TASK_EBP": 0x3C,
|
|
"TASK_ESI": 0x40,
|
|
"TASK_EDI": 0x44,
|
|
"TASK_ES": 0x48,
|
|
"TASK_CS": 0x4C,
|
|
"TASK_SS": 0x50,
|
|
"TASK_DS": 0x54,
|
|
"TASK_FS": 0x58,
|
|
"TASK_GS": 0x5C,
|
|
"TASK_LDT": 0x60,
|
|
"TASK_IOPM": 0x64
|
|
};
|
|
|
|
/**
|
|
* dumpTSS(asArgs)
|
|
*
|
|
* This dumps a TSS using the given selector. If none is specified, the current TR is used.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {Array.<string>} asArgs
|
|
*/
|
|
Debugger.prototype.dumpTSS = function(asArgs)
|
|
{
|
|
var seg;
|
|
var sSel = asArgs[0];
|
|
|
|
if (!sSel) {
|
|
seg = this.cpu.segTSS;
|
|
} else {
|
|
var sel = this.parseValue(sSel);
|
|
if (sel === undefined) {
|
|
this.println("invalid task selector: " + sSel);
|
|
return;
|
|
}
|
|
seg = this.getSegment(sel, Debugger.ADDRTYPE.PROT);
|
|
}
|
|
|
|
this.println("dumpTSS(" + str.toHexWord(seg? seg.sel : sel) + "): %" + str.toHex(seg? seg.base : null, this.cchAddr));
|
|
if (!seg) return;
|
|
|
|
var sDump = "";
|
|
var type = seg.type & ~X86.DESC.ACC.TYPE.TSS_BUSY;
|
|
var cch = (type == X86.DESC.ACC.TYPE.TSS286? 4 : 8);
|
|
var aTSSFields = (type == X86.DESC.ACC.TYPE.TSS286? Debugger.TSS286 : Debugger.TSS386);
|
|
var off, addr, v;
|
|
for (var sField in aTSSFields) {
|
|
off = aTSSFields[sField];
|
|
addr = seg.base + off;
|
|
v = this.cpu.probeAddr(addr, 2);
|
|
if (type == X86.DESC.ACC.TYPE.TSS386) {
|
|
v |= this.cpu.probeAddr(addr + 2, 2) << 16;
|
|
}
|
|
if (sDump) sDump += '\n';
|
|
sDump += str.toHexWord(off) + ' ' + str.pad(sField + ':', 11) + str.toHex(v, cch);
|
|
}
|
|
if (type == X86.DESC.ACC.TYPE.TSS386) {
|
|
var iPort = 0;
|
|
off = (v >>> 16);
|
|
/*
|
|
* We arbitrarily cut the IOPM dump off at port 0x3FF; we're not currently interested in anything above that.
|
|
*/
|
|
while (off < seg.offMax && iPort < 0x3ff) {
|
|
addr = seg.base + off;
|
|
v = this.cpu.probeAddr(addr, 2);
|
|
sDump += "\n" + str.toHexWord(off) + " ports " + str.toHexWord(iPort) + '-' + str.toHexWord(iPort+15) + ": " + str.toBinBytes(v, 2);
|
|
iPort += 16;
|
|
off += 2;
|
|
}
|
|
}
|
|
this.println(sDump);
|
|
};
|
|
|
|
/**
|
|
* findModuleInfo(sModule, nSegment)
|
|
*
|
|
* Since we're not sure what Disk the module was loaded from, we have to check all of them.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {string} sModule
|
|
* @param {number} nSegment
|
|
* @return {Array}
|
|
*/
|
|
Debugger.prototype.findModuleInfo = function(sModule, nSegment)
|
|
{
|
|
var aSymbols = [];
|
|
if (SYMBOLS) {
|
|
var component, componentPrev = null;
|
|
while (component = this.cmp.getMachineComponent("Disk", componentPrev)) {
|
|
aSymbols = component.getModuleInfo(sModule, nSegment);
|
|
if (aSymbols.length) break;
|
|
componentPrev = component;
|
|
}
|
|
}
|
|
return aSymbols;
|
|
};
|
|
|
|
/**
|
|
* messageInit(sEnable)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {string|undefined} sEnable contains zero or more message categories to enable, separated by '|'
|
|
*/
|
|
Debugger.prototype.messageInit = function(sEnable)
|
|
{
|
|
this.dbg = this;
|
|
this.bitsMessage = this.bitsWarning = Messages.WARN;
|
|
this.sMessagePrev = null;
|
|
/*
|
|
* 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 Debugger.MESSAGES) {
|
|
if (usr.indexOf(aEnable, m) >= 0) {
|
|
this.bitsMessage |= Debugger.MESSAGES[m];
|
|
this.println(m + " messages enabled");
|
|
}
|
|
}
|
|
}
|
|
this.historyInit(); // call this just in case Messages.INT was turned on
|
|
};
|
|
|
|
/**
|
|
* messageDump(bitMessage, fnDumper)
|
|
*
|
|
* @this {Debugger}
|
|
* @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
|
|
*/
|
|
Debugger.prototype.messageDump = function(bitMessage, fnDumper)
|
|
{
|
|
for (var m in Debugger.MESSAGES) {
|
|
if (bitMessage == Debugger.MESSAGES[m]) {
|
|
this.afnDumpers[m] = fnDumper;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* getRegIndex(sReg, off)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {string} sReg
|
|
* @param {number} [off] optional offset into sReg
|
|
* @return {number} register index, or -1 if not found
|
|
*/
|
|
Debugger.prototype.getRegIndex = function(sReg, off)
|
|
{
|
|
var i;
|
|
sReg = sReg.toUpperCase();
|
|
if (off == null) {
|
|
i = usr.indexOf(Debugger.REGS, sReg);
|
|
} else {
|
|
i = usr.indexOf(Debugger.REGS, sReg.substr(off, 3));
|
|
if (i < 0) i = usr.indexOf(Debugger.REGS, sReg.substr(off, 2));
|
|
}
|
|
return i;
|
|
};
|
|
|
|
/**
|
|
* getRegString(iReg)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number} iReg
|
|
* @return {string}
|
|
*/
|
|
Debugger.prototype.getRegString = function(iReg)
|
|
{
|
|
var cch = 0;
|
|
var n = this.getRegValue(iReg);
|
|
if (n !== undefined) {
|
|
switch(iReg) {
|
|
case Debugger.REG_AL:
|
|
case Debugger.REG_CL:
|
|
case Debugger.REG_DL:
|
|
case Debugger.REG_BL:
|
|
case Debugger.REG_AH:
|
|
case Debugger.REG_CH:
|
|
case Debugger.REG_DH:
|
|
case Debugger.REG_BH:
|
|
cch = 2;
|
|
break;
|
|
case Debugger.REG_AX:
|
|
case Debugger.REG_CX:
|
|
case Debugger.REG_DX:
|
|
case Debugger.REG_BX:
|
|
case Debugger.REG_SP:
|
|
case Debugger.REG_BP:
|
|
case Debugger.REG_SI:
|
|
case Debugger.REG_DI:
|
|
case Debugger.REG_IP:
|
|
case Debugger.REG_SEG + Debugger.REG_ES:
|
|
case Debugger.REG_SEG + Debugger.REG_CS:
|
|
case Debugger.REG_SEG + Debugger.REG_SS:
|
|
case Debugger.REG_SEG + Debugger.REG_DS:
|
|
case Debugger.REG_SEG + Debugger.REG_FS:
|
|
case Debugger.REG_SEG + Debugger.REG_GS:
|
|
cch = 4;
|
|
break;
|
|
case Debugger.REG_EAX:
|
|
case Debugger.REG_ECX:
|
|
case Debugger.REG_EDX:
|
|
case Debugger.REG_EBX:
|
|
case Debugger.REG_ESP:
|
|
case Debugger.REG_EBP:
|
|
case Debugger.REG_ESI:
|
|
case Debugger.REG_EDI:
|
|
case Debugger.REG_CR0:
|
|
case Debugger.REG_CR1:
|
|
case Debugger.REG_CR2:
|
|
case Debugger.REG_CR3:
|
|
case Debugger.REG_EIP:
|
|
cch = 8;
|
|
break;
|
|
case Debugger.REG_PS:
|
|
cch = this.cchReg;
|
|
break;
|
|
}
|
|
}
|
|
return cch? str.toHex(n, cch) : "??";
|
|
};
|
|
|
|
/**
|
|
* getRegValue(iReg)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number} iReg
|
|
* @return {number|undefined}
|
|
*/
|
|
Debugger.prototype.getRegValue = function(iReg)
|
|
{
|
|
var n;
|
|
if (iReg >= 0) {
|
|
var cpu = this.cpu;
|
|
switch(iReg) {
|
|
case Debugger.REG_AL:
|
|
n = cpu.regEAX & 0xff;
|
|
break;
|
|
case Debugger.REG_CL:
|
|
n = cpu.regECX & 0xff;
|
|
break;
|
|
case Debugger.REG_DL:
|
|
n = cpu.regEDX & 0xff;
|
|
break;
|
|
case Debugger.REG_BL:
|
|
n = cpu.regEBX & 0xff;
|
|
break;
|
|
case Debugger.REG_AH:
|
|
n = (cpu.regEAX >> 8) & 0xff;
|
|
break;
|
|
case Debugger.REG_CH:
|
|
n = (cpu.regECX >> 8) & 0xff;
|
|
break;
|
|
case Debugger.REG_DH:
|
|
n = (cpu.regEDX >> 8) & 0xff;
|
|
break;
|
|
case Debugger.REG_BH:
|
|
n = (cpu.regEBX >> 8) & 0xff;
|
|
break;
|
|
case Debugger.REG_AX:
|
|
n = cpu.regEAX & 0xffff;
|
|
break;
|
|
case Debugger.REG_CX:
|
|
n = cpu.regECX & 0xffff;
|
|
break;
|
|
case Debugger.REG_DX:
|
|
n = cpu.regEDX & 0xffff;
|
|
break;
|
|
case Debugger.REG_BX:
|
|
n = cpu.regEBX & 0xffff;
|
|
break;
|
|
case Debugger.REG_SP:
|
|
n = cpu.getSP() & 0xffff;
|
|
break;
|
|
case Debugger.REG_BP:
|
|
n = cpu.regEBP & 0xffff;
|
|
break;
|
|
case Debugger.REG_SI:
|
|
n = cpu.regESI & 0xffff;
|
|
break;
|
|
case Debugger.REG_DI:
|
|
n = cpu.regEDI & 0xffff;
|
|
break;
|
|
case Debugger.REG_IP:
|
|
n = cpu.getIP() & 0xffff;
|
|
break;
|
|
case Debugger.REG_PS:
|
|
n = cpu.getPS();
|
|
break;
|
|
case Debugger.REG_SEG + Debugger.REG_ES:
|
|
n = cpu.getES();
|
|
break;
|
|
case Debugger.REG_SEG + Debugger.REG_CS:
|
|
n = cpu.getCS();
|
|
break;
|
|
case Debugger.REG_SEG + Debugger.REG_SS:
|
|
n = cpu.getSS();
|
|
break;
|
|
case Debugger.REG_SEG + Debugger.REG_DS:
|
|
n = cpu.getDS();
|
|
break;
|
|
default:
|
|
if (this.cpu.model == X86.MODEL_80286) {
|
|
if (iReg == Debugger.REG_CR0) {
|
|
n = cpu.regCR0;
|
|
}
|
|
}
|
|
else if (I386 && this.cpu.model >= X86.MODEL_80386) {
|
|
switch(iReg) {
|
|
case Debugger.REG_EAX:
|
|
n = cpu.regEAX;
|
|
break;
|
|
case Debugger.REG_ECX:
|
|
n = cpu.regECX;
|
|
break;
|
|
case Debugger.REG_EDX:
|
|
n = cpu.regEDX;
|
|
break;
|
|
case Debugger.REG_EBX:
|
|
n = cpu.regEBX;
|
|
break;
|
|
case Debugger.REG_ESP:
|
|
n = cpu.getSP();
|
|
break;
|
|
case Debugger.REG_EBP:
|
|
n = cpu.regEBP;
|
|
break;
|
|
case Debugger.REG_ESI:
|
|
n = cpu.regESI;
|
|
break;
|
|
case Debugger.REG_EDI:
|
|
n = cpu.regEDI;
|
|
break;
|
|
case Debugger.REG_CR0:
|
|
n = cpu.regCR0;
|
|
break;
|
|
case Debugger.REG_CR1:
|
|
n = cpu.regCR1;
|
|
break;
|
|
case Debugger.REG_CR2:
|
|
n = cpu.regCR2;
|
|
break;
|
|
case Debugger.REG_CR3:
|
|
n = cpu.regCR3;
|
|
break;
|
|
case Debugger.REG_SEG + Debugger.REG_FS:
|
|
n = cpu.getFS();
|
|
break;
|
|
case Debugger.REG_SEG + Debugger.REG_GS:
|
|
n = cpu.getGS();
|
|
break;
|
|
case Debugger.REG_EIP:
|
|
n = cpu.getIP();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return n;
|
|
};
|
|
|
|
/**
|
|
* replaceRegs(s)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {string} s
|
|
* @return {string}
|
|
*/
|
|
Debugger.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 + Debugger.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 {Debugger}
|
|
* @param {string} sMessage is any caller-defined message string
|
|
* @param {boolean} [fAddress] is true to display the current CS:IP
|
|
*/
|
|
Debugger.prototype.message = function(sMessage, fAddress)
|
|
{
|
|
if (fAddress) {
|
|
sMessage += " at " + this.toHexAddr(this.newAddr(this.cpu.getIP(), this.cpu.getCS())) + " (%" + str.toHex(this.cpu.regLIP) + ")";
|
|
}
|
|
|
|
if (this.sMessagePrev && sMessage == this.sMessagePrev) return;
|
|
this.sMessagePrev = sMessage;
|
|
|
|
if (this.bitsMessage & Messages.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 CPU.calcStartTime() for a discussion of why we might want to call yieldCPU() *before*
|
|
* we display the message.
|
|
*/
|
|
if (this.cpu) this.cpu.yieldCPU();
|
|
};
|
|
|
|
/**
|
|
* messageInt(nInt, addr, fForce)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number} nInt
|
|
* @param {number} addr (LIP after the "INT n" instruction has been fetched but not dispatched)
|
|
* @param {boolean} [fForce] (true if the message should be forced)
|
|
* @return {boolean} true if message generated (which in turn triggers addIntReturn() inside checkIntNotify()), false if not
|
|
*/
|
|
Debugger.prototype.messageInt = function(nInt, addr, fForce)
|
|
{
|
|
var AH, DL;
|
|
var fMessage = fForce;
|
|
|
|
/*
|
|
* We currently arrive here only because the CPU has already determined that INT messages are enabled,
|
|
* or because the ChipSet's RTC interrupt handler has already determined that INT messages are enabled.
|
|
*
|
|
* But software interrupts are very common, so we generally require additional categories to be enabled;
|
|
* unless the caller has set fForce, we check those additional categories now.
|
|
*/
|
|
if (!fMessage) {
|
|
/*
|
|
* Display all software interrupts if CPU messages are enabled (and it's not an "annoying" interrupt);
|
|
* note that in some cases, even "annoying" interrupts can be turned with an extra message category.
|
|
*/
|
|
fMessage = this.messageEnabled(Messages.CPU) && Debugger.INT_ANNOYING.indexOf(nInt) < 0;
|
|
if (!fMessage) {
|
|
/*
|
|
* Alternatively, display this software interrupt if its corresponding message category is enabled.
|
|
*/
|
|
var nCategory = Debugger.INT_MESSAGES[nInt];
|
|
if (nCategory) {
|
|
if (this.messageEnabled(nCategory)) {
|
|
fMessage = true;
|
|
} else {
|
|
/*
|
|
* Alternatively, display this FDC interrupt if HDC messages are enabled (since they share
|
|
* a common software interrupt). Normally, an HDC BIOS will copy the original DISK (0x13)
|
|
* vector to the ALT_DISK (0x40) vector, but it's a nuisance having to check different
|
|
* interrupts in different configurations for the same frickin' functionality, so we don't.
|
|
*/
|
|
fMessage = (nCategory == Messages.FDC && this.messageEnabled(nCategory = Messages.HDC));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (fMessage) {
|
|
AH = (this.cpu.regEAX >> 8) & 0xff;
|
|
DL = this.cpu.regEDX & 0xff;
|
|
if (nInt == Interrupts.DOS /* 0x21 */ && AH == 0x0b ||
|
|
nCategory == Messages.FDC && DL >= 0x80 || nCategory == Messages.HDC && DL < 0x80) {
|
|
fMessage = false;
|
|
}
|
|
}
|
|
if (fMessage) {
|
|
var aFuncs = Interrupts.FUNCS[nInt];
|
|
var sFunc = (aFuncs && aFuncs[AH]) || "";
|
|
if (sFunc) sFunc = ' ' + this.replaceRegs(sFunc);
|
|
/*
|
|
* For display purposes only, rewind addr to the address of the responsible "INT n" instruction;
|
|
* we know it's the two-byte "INT n" instruction because that's the only opcode handler that calls
|
|
* checkIntNotify() at the moment.
|
|
*/
|
|
addr -= 2;
|
|
this.message("INT " + str.toHexByte(nInt) + ": AH=" + str.toHexByte(AH) + " at " + this.toHexOffset(addr - this.cpu.segCS.base, this.cpu.getCS()) + sFunc);
|
|
}
|
|
return fMessage;
|
|
};
|
|
|
|
/**
|
|
* messageIntReturn(nInt, nLevel, nCycles)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number} nInt
|
|
* @param {number} nLevel
|
|
* @param {number} nCycles
|
|
* @param {string} [sResult]
|
|
*/
|
|
Debugger.prototype.messageIntReturn = function(nInt, nLevel, nCycles, sResult)
|
|
{
|
|
this.message("INT " + str.toHexByte(nInt) + ": C=" + (this.cpu.getCF()? 1 : 0) + (sResult || "") + " (cycles=" + nCycles + (nLevel? ",level=" + (nLevel+1) : "") + ')');
|
|
};
|
|
|
|
/**
|
|
* messageIO(component, port, bOut, addrFrom, name, bIn, bitsMessage)
|
|
*
|
|
* @this {Debugger}
|
|
* @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)
|
|
*/
|
|
Debugger.prototype.messageIO = function(component, port, bOut, addrFrom, name, bIn, bitsMessage)
|
|
{
|
|
bitsMessage |= Messages.PORT;
|
|
if (addrFrom == null || (this.bitsMessage & bitsMessage) == bitsMessage) {
|
|
var selFrom = null;
|
|
if (addrFrom != null) {
|
|
selFrom = this.cpu.getCS();
|
|
addrFrom -= this.cpu.segCS.base;
|
|
}
|
|
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, selFrom)) : ""));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* init()
|
|
*
|
|
* @this {Debugger}
|
|
*/
|
|
Debugger.prototype.init = function()
|
|
{
|
|
this.println("Type ? for help with PCx86 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 {Debugger}
|
|
* @param {boolean} [fQuiet]
|
|
*/
|
|
Debugger.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(Debugger.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 {Debugger}
|
|
* @param {boolean} [fUpdateFocus] is true to update focus
|
|
* @return {boolean} true if run request successful, false if not
|
|
*/
|
|
Debugger.prototype.runCPU = function(fUpdateFocus)
|
|
{
|
|
if (!this.isCPUAvail()) return false;
|
|
this.cpu.runCPU(fUpdateFocus);
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* stepCPU(nCycles, fRegs, fUpdateCPU)
|
|
*
|
|
* @this {Debugger}
|
|
* @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}
|
|
*/
|
|
Debugger.prototype.stepCPU = function(nCycles, fRegs, fUpdateCPU)
|
|
{
|
|
if (!this.isCPUAvail()) return false;
|
|
|
|
this.nCycles = 0;
|
|
do {
|
|
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.regLIP, 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);
|
|
}
|
|
}
|
|
} while (this.cpu.opFlags & X86.OPFLAG_PREFIXES);
|
|
|
|
/*
|
|
* 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 {Debugger}
|
|
* @param {boolean} [fComplete]
|
|
*/
|
|
Debugger.prototype.stopCPU = function(fComplete)
|
|
{
|
|
if (this.cpu) this.cpu.stopCPU(fComplete);
|
|
};
|
|
|
|
/**
|
|
* updateStatus(fRegs)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {boolean} [fRegs] (default is true)
|
|
*/
|
|
Debugger.prototype.updateStatus = function(fRegs)
|
|
{
|
|
if (fRegs === undefined) fRegs = true;
|
|
|
|
this.dbgAddrNextCode = this.newAddr(this.cpu.getIP(), this.cpu.getCS());
|
|
/*
|
|
* 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 {Debugger}
|
|
* @return {boolean}
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {Object|null} data
|
|
* @param {boolean} [fRepower]
|
|
* @return {boolean} true if successful, false if failure
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {boolean} [fSave]
|
|
* @param {boolean} [fShutdown]
|
|
* @return {Object|boolean}
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {boolean} fQuiet (true only when called from our own powerUp handler)
|
|
*/
|
|
Debugger.prototype.reset = function(fQuiet)
|
|
{
|
|
this.historyInit();
|
|
this.cOpcodes = this.cOpcodesStart = 0;
|
|
this.sMessagePrev = null;
|
|
this.nCycles = 0;
|
|
this.dbgAddrNextCode = this.newAddr(this.cpu.getIP(), this.cpu.getCS());
|
|
/*
|
|
* 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 {Debugger}
|
|
* @return {Object}
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {Object} data
|
|
* @return {boolean} true if successful, false if failure
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {number} ms
|
|
* @param {number} nCycles
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {number} ms
|
|
* @param {number} nCycles
|
|
*/
|
|
Debugger.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)";
|
|
if (MAXDEBUG && this.chipset) {
|
|
var i, c, n;
|
|
for (i = 0; i < this.chipset.acInterrupts.length; i++) {
|
|
c = this.chipset.acInterrupts[i];
|
|
if (!c) continue;
|
|
n = c / Math.round(msTotal / 1000);
|
|
this.println("IRQ" + i + ": " + c + " interrupts (" + n + " per sec)");
|
|
this.chipset.acInterrupts[i] = 0;
|
|
}
|
|
for (i = 0; i < this.chipset.acTimersFired.length; i++) {
|
|
c = this.chipset.acTimersFired[i];
|
|
if (!c) continue;
|
|
n = c / Math.round(msTotal / 1000);
|
|
this.println("TIMER" + i + ": " + c + " fires (" + n + " per sec)");
|
|
this.chipset.acTimersFired[i] = 0;
|
|
}
|
|
n = 0;
|
|
for (i = 0; i < this.chipset.acTimer0Counts.length; i++) {
|
|
var a = this.chipset.acTimer0Counts[i];
|
|
n += a[0];
|
|
this.println("TIMER0 update #" + i + ": [" + a[0] + ',' + a[1] + ',' + a[2] + ']');
|
|
}
|
|
this.chipset.acTimer0Counts = [];
|
|
}
|
|
} else {
|
|
if (this.messageEnabled(Messages.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.regLIP);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 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 {Debugger}
|
|
* @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
|
|
*/
|
|
Debugger.prototype.checksEnabled = function(fRelease)
|
|
{
|
|
return ((DEBUG && !fRelease)? true : (this.aBreakExec.length > 1 || !!this.nBreakIns || this.messageEnabled(Messages.INT) /* || this.aBreakRead.length > 1 || this.aBreakWrite.length > 1 */));
|
|
};
|
|
|
|
/**
|
|
* 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 {Debugger}
|
|
* @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
|
|
*/
|
|
Debugger.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;
|
|
}
|
|
/*
|
|
* Halt if running with interrupts disabled and IOPL < CPL, because that's likely an error
|
|
*/
|
|
if (MAXDEBUG && !(cpu.regPS & X86.PS.IF) && cpu.nIOPL < cpu.nCPL) {
|
|
this.printMessage("interrupts disabled at IOPL " + cpu.nIOPL + " and CPL " + cpu.nCPL, true);
|
|
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 Messages.INT messages.
|
|
*/
|
|
if (nState >= 0 && this.aaOpcodeCounts.length) {
|
|
this.cOpcodes++;
|
|
var bOpcode = cpu.probeAddr(addr);
|
|
if (bOpcode != null) {
|
|
this.aaOpcodeCounts[bOpcode][1]++;
|
|
var dbgAddr = this.aOpcodeHistory[this.iOpcodeHistory];
|
|
this.setAddr(dbgAddr, cpu.getIP(), cpu.getCS());
|
|
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 {Debugger}
|
|
* @param {number} addr
|
|
* @param {number} [nb] (# of bytes; default is 1)
|
|
* @return {boolean} true if breakpoint hit, false if not
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {number} addr
|
|
* @param {number} [nb] (# of bytes; default is 1)
|
|
* @return {boolean} true if breakpoint hit, false if not
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {number} port
|
|
* @param {number} size
|
|
* @param {number} data
|
|
* @return {boolean} true if breakpoint hit, false if not
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {number} port
|
|
* @param {number} size
|
|
* @param {number} data
|
|
* @return {boolean} true if breakpoint hit, false if not
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
*/
|
|
Debugger.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.cpu.removeMemBreak(this.getAddr(dbgAddr), false, dbgAddr.type == Debugger.ADDRTYPE.PHYSICAL);
|
|
}
|
|
}
|
|
this.aBreakRead = ["br"];
|
|
if (this.aBreakWrite !== undefined) {
|
|
for (i = 1; i < this.aBreakWrite.length; i++) {
|
|
dbgAddr = this.aBreakWrite[i];
|
|
this.cpu.removeMemBreak(this.getAddr(dbgAddr), true, dbgAddr.type == Debugger.ADDRTYPE.PHYSICAL);
|
|
}
|
|
}
|
|
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, fTempBreak)
|
|
*
|
|
* 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 {Debugger}
|
|
* @param {Array} aBreak
|
|
* @param {DbgAddr} dbgAddr
|
|
* @param {boolean} [fTempBreak]
|
|
* @return {boolean} true if breakpoint added, false if already exists
|
|
*/
|
|
Debugger.prototype.addBreakpoint = function(aBreak, dbgAddr, fTempBreak)
|
|
{
|
|
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 (!fTempBreak) {
|
|
this.findBreakpoint(aBreak, dbgAddr, true, false, true);
|
|
}
|
|
|
|
if (aBreak != this.aBreakExec) {
|
|
var addr = this.getAddr(dbgAddr);
|
|
if (addr === X86.ADDR_INVALID) {
|
|
this.println("invalid address: " + this.toHexAddr(dbgAddr));
|
|
fSuccess = false;
|
|
} else {
|
|
this.cpu.addMemBreak(addr, aBreak == this.aBreakWrite, dbgAddr.type == Debugger.ADDRTYPE.PHYSICAL);
|
|
}
|
|
}
|
|
|
|
if (fSuccess) {
|
|
aBreak.push(dbgAddr);
|
|
if (fTempBreak) {
|
|
/*
|
|
* Force temporary breakpoints to use their linear address, if one is available, by zapping
|
|
* the selector; this allows us to step over calls or interrupts that change the processor mode.
|
|
*
|
|
* TODO: Unfortunately, this will fail to "step" over a call in segment that moves during the call;
|
|
* consider alternatives.
|
|
*/
|
|
if (dbgAddr.addr != null) dbgAddr.sel = null;
|
|
dbgAddr.fTempBreak = true;
|
|
}
|
|
else {
|
|
this.printBreakpoint(aBreak, aBreak.length-1, "set");
|
|
this.historyInit();
|
|
}
|
|
}
|
|
|
|
// this.nSuppressBreaks--;
|
|
|
|
return fSuccess;
|
|
};
|
|
|
|
/**
|
|
* findBreakpoint(aBreak, dbgAddr, fRemove, fTempBreak, fQuiet)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {Array} aBreak
|
|
* @param {DbgAddr} dbgAddr
|
|
* @param {boolean} [fRemove]
|
|
* @param {boolean} [fTempBreak]
|
|
* @param {boolean} [fQuiet]
|
|
* @return {boolean} true if found, false if not
|
|
*/
|
|
Debugger.prototype.findBreakpoint = function(aBreak, dbgAddr, fRemove, fTempBreak, fQuiet)
|
|
{
|
|
var fFound = false;
|
|
var addr = this.mapBreakpoint(this.getAddr(dbgAddr));
|
|
for (var i = 1; i < aBreak.length; i++) {
|
|
var dbgAddrBreak = aBreak[i];
|
|
if (addr !== X86.ADDR_INVALID && addr == this.mapBreakpoint(this.getAddr(dbgAddrBreak)) ||
|
|
addr === X86.ADDR_INVALID && dbgAddr.sel == dbgAddrBreak.sel && dbgAddr.off == dbgAddrBreak.off) {
|
|
if (!fTempBreak || dbgAddrBreak.fTempBreak) {
|
|
fFound = true;
|
|
if (fRemove) {
|
|
if (!dbgAddrBreak.fTempBreak && !fQuiet) {
|
|
this.printBreakpoint(aBreak, i, "cleared");
|
|
}
|
|
aBreak.splice(i, 1);
|
|
if (aBreak != this.aBreakExec) {
|
|
this.cpu.removeMemBreak(addr, aBreak == this.aBreakWrite, dbgAddrBreak.type == Debugger.ADDRTYPE.PHYSICAL);
|
|
}
|
|
/*
|
|
* We'll mirror the logic in addBreakpoint() and leave the history buffer alone if this
|
|
* was a temporary breakpoint.
|
|
*/
|
|
if (!dbgAddrBreak.fTempBreak) {
|
|
this.historyInit();
|
|
}
|
|
break;
|
|
}
|
|
if (!fQuiet) this.printBreakpoint(aBreak, i, "exists");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return fFound;
|
|
};
|
|
|
|
/**
|
|
* listBreakpoints(aBreak)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {Array} aBreak
|
|
* @return {number} of breakpoints listed, 0 if none
|
|
*/
|
|
Debugger.prototype.listBreakpoints = function(aBreak)
|
|
{
|
|
for (var i = 1; i < aBreak.length; i++) {
|
|
this.printBreakpoint(aBreak, i);
|
|
}
|
|
return aBreak.length - 1;
|
|
};
|
|
|
|
/**
|
|
* printBreakpoint(aBreak, i, sAction)
|
|
*
|
|
* TODO: We may need to start printing linear addresses also (if any), because segmented address can be ambiguous.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {Array} aBreak
|
|
* @param {number} i
|
|
* @param {string} [sAction]
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {DbgAddr} dbgAddr of new temp breakpoint
|
|
*/
|
|
Debugger.prototype.setTempBreakpoint = function(dbgAddr)
|
|
{
|
|
this.addBreakpoint(this.aBreakExec, dbgAddr, true);
|
|
};
|
|
|
|
/**
|
|
* clearTempBreakpoint(addr)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number|undefined} [addr] clear all temp breakpoints if no address specified
|
|
*/
|
|
Debugger.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.fTempBreak) {
|
|
if (!this.findBreakpoint(this.aBreakExec, dbgAddrBreak, true, true)) break;
|
|
i = 0;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* mapBreakpoint(addr)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number} addr
|
|
* @return {number}
|
|
*/
|
|
Debugger.prototype.mapBreakpoint = function(addr)
|
|
{
|
|
/*
|
|
* Map addresses in the top 64Kb at the top of the address space (assuming either a 16Mb or 4Gb
|
|
* address space) to the top of the 1Mb range.
|
|
*
|
|
* The fact that those two 64Kb regions are aliases of each other on an 80286 is a pain in the BUTT,
|
|
* because any CS-based breakpoint you set immediately after a CPU reset will have a physical address
|
|
* in the top 16Mb, yet after the first inter-segment JMP, you will be running in the first 1Mb.
|
|
*/
|
|
if (addr !== X86.ADDR_INVALID) {
|
|
var mask = (this.maskAddr & ~0xffff);
|
|
if ((addr & mask) == mask) addr &= 0x000fffff;
|
|
}
|
|
return addr;
|
|
};
|
|
|
|
/**
|
|
* checkBreakpoint(addr, nb, aBreak, fTempBreak)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number} addr
|
|
* @param {number} nb (# of bytes)
|
|
* @param {Array} aBreak
|
|
* @param {boolean} [fTempBreak]
|
|
* @return {boolean} true if breakpoint has been hit, false if not
|
|
*/
|
|
Debugger.prototype.checkBreakpoint = function(addr, nb, aBreak, fTempBreak)
|
|
{
|
|
/*
|
|
* 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++) {
|
|
|
|
addr = this.mapBreakpoint(addr);
|
|
|
|
/*
|
|
* As discussed in opINT3(), I decided to check for INT3 instructions here: we'll tell the CPU to
|
|
* stop on INT3 whenever both the INT and HALT message bits are set; a simple "g" command allows you
|
|
* to continue.
|
|
*/
|
|
if (this.messageEnabled(Messages.INT | Messages.HALT)) {
|
|
if (this.cpu.probeAddr(addr) == X86.OPCODE.INT3) {
|
|
fBreak = true;
|
|
}
|
|
}
|
|
|
|
for (var i = 1; !fBreak && i < aBreak.length; i++) {
|
|
|
|
var dbgAddrBreak = aBreak[i];
|
|
|
|
if (fTempBreak && !dbgAddrBreak.fTempBreak) continue;
|
|
|
|
/*
|
|
* We need to zap the linear address field of the breakpoint address before
|
|
* calling getAddr(), to force it to recalculate the linear address every time,
|
|
* unless this is a breakpoint on a linear address (as indicated by a null sel).
|
|
*/
|
|
if (dbgAddrBreak.sel != null) dbgAddrBreak.addr = null;
|
|
|
|
/*
|
|
* 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.mapBreakpoint(this.getAddr(dbgAddrBreak));
|
|
for (var n = 0; n < nb; n++) {
|
|
if (addr + n == addrBreak) {
|
|
var a;
|
|
fBreak = true;
|
|
if (dbgAddrBreak.fTempBreak) {
|
|
this.findBreakpoint(aBreak, dbgAddrBreak, true, true);
|
|
fTempBreak = 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 (!fTempBreak) this.printBreakpoint(aBreak, i, "hit");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.nSuppressBreaks--;
|
|
|
|
return fBreak;
|
|
};
|
|
|
|
/**
|
|
* getInstruction(dbgAddr, sComment, nSequence)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {DbgAddr} 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)
|
|
*/
|
|
Debugger.prototype.getInstruction = function(dbgAddr, sComment, nSequence)
|
|
{
|
|
var dbgAddrIns = this.newAddr(dbgAddr.off, dbgAddr.sel, dbgAddr.addr, dbgAddr.type);
|
|
|
|
var bOpcode = this.getByte(dbgAddr, 1);
|
|
|
|
/*
|
|
* Incorporate OPERAND and ADDRESS size prefixes into the current instruction.
|
|
*
|
|
* And the verdict is in: redundant OPERAND and ADDRESS prefixes must be ignored;
|
|
* see opOS() and opAS() for details. We limit the amount of redundancy to something
|
|
* reasonable (ie, 4).
|
|
*/
|
|
var cMaxOverrides = 4, cOverrides = 0;
|
|
var fDataPrefix = false, fAddrPrefix = false;
|
|
|
|
while ((bOpcode == X86.OPCODE.OS || bOpcode == X86.OPCODE.AS) && cMaxOverrides--) {
|
|
if (bOpcode == X86.OPCODE.OS) {
|
|
if (!fDataPrefix) {
|
|
dbgAddr.fData32 = !dbgAddr.fData32;
|
|
fDataPrefix = true;
|
|
}
|
|
cOverrides++;
|
|
} else {
|
|
if (!fAddrPrefix) {
|
|
dbgAddr.fAddr32 = !dbgAddr.fAddr32;
|
|
fAddrPrefix = true;
|
|
}
|
|
cOverrides++;
|
|
}
|
|
bOpcode = this.getByte(dbgAddr, 1);
|
|
}
|
|
|
|
var bModRM = -1;
|
|
var asOpcodes = Debugger.INS_NAMES;
|
|
var aOpDesc = this.aaOpDescs[bOpcode];
|
|
var iIns = aOpDesc[0];
|
|
|
|
if (iIns == Debugger.INS.OP0F) {
|
|
var b = this.getByte(dbgAddr, 1);
|
|
aOpDesc = Debugger.aaOp0FDescs[b] || Debugger.aOpDescUndefined;
|
|
bOpcode |= (b << 8);
|
|
iIns = aOpDesc[0];
|
|
}
|
|
|
|
if (iIns == Debugger.INS.ESC) {
|
|
bModRM = this.getByte(dbgAddr, 1);
|
|
var aOpFPUDesc = this.getFPUInstruction(bOpcode, bModRM);
|
|
if (aOpFPUDesc) {
|
|
asOpcodes = Debugger.FINS_NAMES;
|
|
aOpDesc = aOpFPUDesc;
|
|
iIns = aOpDesc[0];
|
|
}
|
|
}
|
|
|
|
if (iIns >= asOpcodes.length) {
|
|
bModRM = this.getByte(dbgAddr, 1);
|
|
aOpDesc = Debugger.aaGrpDescs[iIns - asOpcodes.length][(bModRM >> 3) & 0x7];
|
|
iIns = aOpDesc[0];
|
|
}
|
|
|
|
var sOpcode = asOpcodes[iIns];
|
|
var cOperands = aOpDesc.length - 1;
|
|
var sOperands = "";
|
|
|
|
if (dbgAddr.fData32) {
|
|
if (iIns == Debugger.INS.CBW) {
|
|
sOpcode = "CWDE"; // sign-extend AX into EAX, instead of AL into AX
|
|
}
|
|
else if (iIns == Debugger.INS.CWD) {
|
|
sOpcode = "CDQ"; // sign-extend EAX into EDX:EAX, instead of AX into DX:AX
|
|
}
|
|
else if (iIns >= Debugger.INS.POPA && iIns <= Debugger.INS.PUSHA) {
|
|
sOpcode += 'D'; // transform POPA/POPF/PUSHF/PUSHA to POPAD/POPFD/PUSHFD/PUSHAD as appropriate
|
|
}
|
|
}
|
|
if (this.isStringIns(bOpcode)) {
|
|
cOperands = 0; // suppress operands for string instructions, and add 'D' suffix as appropriate
|
|
if (dbgAddr.fData32 && sOpcode.slice(-1) == 'W') sOpcode = sOpcode.slice(0, -1) + 'D';
|
|
}
|
|
|
|
var typeCPU = null;
|
|
var fComplete = true;
|
|
|
|
for (var iOperand = 1; iOperand <= cOperands; iOperand++) {
|
|
|
|
var disp, off, cch;
|
|
var sOperand = "";
|
|
var type = aOpDesc[iOperand];
|
|
if (type === undefined) continue;
|
|
|
|
if (typeCPU == null) typeCPU = type >> Debugger.TYPE_CPU_SHIFT;
|
|
|
|
if (iIns == Debugger.INS.LOADALL) {
|
|
if (typeCPU == Debugger.CPU_80286) {
|
|
sOperands = "[%800]";
|
|
} else if (typeCPU == Debugger.CPU_80386) {
|
|
sOperands = "ES:[" + (dbgAddr.fAddr32? 'E':'') + "DI]";
|
|
}
|
|
}
|
|
|
|
var typeSize = type & Debugger.TYPE_SIZE;
|
|
if (typeSize == Debugger.TYPE_NONE) {
|
|
continue;
|
|
}
|
|
if (typeSize == Debugger.TYPE_PREFIX) {
|
|
fComplete = false;
|
|
continue;
|
|
}
|
|
var typeMode = type & Debugger.TYPE_MODE;
|
|
if (typeMode >= Debugger.TYPE_MODRM) {
|
|
if (bModRM < 0) {
|
|
bModRM = this.getByte(dbgAddr, 1);
|
|
}
|
|
if (typeMode < Debugger.TYPE_MODREG) {
|
|
/*
|
|
* This test also encompasses TYPE_MODMEM, which is basically the inverse of the case
|
|
* below (ie, only Mod values *other* than 11 are allowed); however, I believe that in
|
|
* some cases that's merely a convention, and that if you try to execute an instruction
|
|
* like "LEA AX,BX", it will actually do something (on some if not all processors), so
|
|
* there's probably some diagnostic value in allowing those cases to be disassembled.
|
|
*/
|
|
sOperand = this.getModRMOperand(sOpcode, bModRM, type, cOperands, dbgAddr);
|
|
}
|
|
else if (typeMode == Debugger.TYPE_MODREG) {
|
|
/*
|
|
* TYPE_MODREG instructions assume that Mod is 11 (only certain early 80486 steppings
|
|
* actually *required* that Mod contain 11) and always treat RM as a register (which we
|
|
* could also simulate by setting Mod to 11 and letting getModRMOperand() do its thing).
|
|
*/
|
|
sOperand = this.getRegOperand(bModRM & 0x7, type, dbgAddr);
|
|
}
|
|
else {
|
|
/*
|
|
* All remaining cases are register-based (eg, TYPE_REG); getRegOperand() will figure out which.
|
|
*/
|
|
sOperand = this.getRegOperand((bModRM >> 3) & 0x7, type, dbgAddr);
|
|
}
|
|
}
|
|
else if (typeMode == Debugger.TYPE_ONE) {
|
|
sOperand = '1';
|
|
}
|
|
else if (typeMode == Debugger.TYPE_IMM) {
|
|
sOperand = this.getImmOperand(type, dbgAddr);
|
|
}
|
|
else if (typeMode == Debugger.TYPE_IMMOFF) {
|
|
if (!dbgAddr.fAddr32) {
|
|
cch = 4;
|
|
off = this.getShort(dbgAddr, 2);
|
|
} else {
|
|
cch = 8;
|
|
off = this.getLong(dbgAddr, 4);
|
|
}
|
|
sOperand = '[' + str.toHex(off, cch) + ']';
|
|
}
|
|
else if (typeMode == Debugger.TYPE_IMMREL) {
|
|
if (typeSize == Debugger.TYPE_BYTE) {
|
|
disp = ((this.getByte(dbgAddr, 1) << 24) >> 24);
|
|
}
|
|
else {
|
|
disp = this.getWord(dbgAddr, true);
|
|
}
|
|
off = (dbgAddr.off + disp) & (dbgAddr.fData32? -1 : 0xffff);
|
|
sOperand = str.toHex(off, dbgAddr.fData32? 8: 4);
|
|
var aSymbol = this.findSymbol(this.newAddr(off, dbgAddr.sel));
|
|
if (aSymbol[0]) sOperand += " (" + aSymbol[0] + ")";
|
|
}
|
|
else if (typeMode == Debugger.TYPE_IMPREG) {
|
|
if (typeSize == Debugger.TYPE_ST) {
|
|
sOperand = "ST";
|
|
} else if (typeSize == Debugger.TYPE_STREG) {
|
|
sOperand = "ST(" + (bModRM & 0x7) + ")";
|
|
} else {
|
|
sOperand = this.getRegOperand((type & Debugger.TYPE_IREG) >> 8, type, dbgAddr);
|
|
}
|
|
}
|
|
else if (typeMode == Debugger.TYPE_IMPSEG) {
|
|
sOperand = this.getRegOperand((type & Debugger.TYPE_IREG) >> 8, Debugger.TYPE_SEGREG, dbgAddr);
|
|
}
|
|
else if (typeMode == Debugger.TYPE_DSSI) {
|
|
sOperand = "DS:[SI]";
|
|
}
|
|
else if (typeMode == Debugger.TYPE_ESDI) {
|
|
sOperand = "ES:[DI]";
|
|
}
|
|
if (!sOperand || !sOperand.length) {
|
|
sOperands = "INVALID";
|
|
break;
|
|
}
|
|
if (sOperands.length > 0) sOperands += ',';
|
|
sOperands += (sOperand || "???");
|
|
}
|
|
|
|
var sBytes = "";
|
|
var sLine = this.toHexAddr(dbgAddrIns) + ' ';
|
|
if (dbgAddrIns.addr !== X86.ADDR_INVALID && dbgAddr.addr !== X86.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, dbgAddrIns.fAddr32? 24 : 16);
|
|
sLine += str.pad(sOpcode, 8);
|
|
if (sOperands) sLine += ' ' + sOperands;
|
|
|
|
if (this.cpu.model < Debugger.CPUS[typeCPU]) {
|
|
sComment = Debugger.CPUS[typeCPU] + " CPU only";
|
|
}
|
|
|
|
if (sComment && fComplete) {
|
|
sLine = str.pad(sLine, dbgAddrIns.fAddr32? 74 : 56) + ';' + 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);
|
|
}
|
|
}
|
|
|
|
this.initAddrSize(dbgAddr, fComplete, cOverrides);
|
|
return sLine;
|
|
};
|
|
|
|
/**
|
|
* getFPUInstruction(bOpcode, bModRM)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number} bOpcode
|
|
* @param {number} bModRM
|
|
* @return {Array|null} (FPU instruction group, or null if none)
|
|
*/
|
|
Debugger.prototype.getFPUInstruction = function(bOpcode, bModRM)
|
|
{
|
|
var aOpDesc = null;
|
|
|
|
var mod = (bModRM >> 6) & 0x3;
|
|
var reg = (bModRM >> 3) & 0x7;
|
|
var r_m = (bModRM & 0x7);
|
|
|
|
/*
|
|
* Similar to how opFPU() decodes FPU instructions, we combine mod and reg into one
|
|
* decodable value: put mod in the high nibble and reg in the low nibble, after first
|
|
* collapsing all mod values < 3 to zero.
|
|
*/
|
|
var modReg = (mod < 3? 0 : 0x30) + reg;
|
|
|
|
/*
|
|
* All values >= 0x34 imply mod == 3 and reg >= 4, so now we shift reg into the high
|
|
* nibble and r_m into the low, yielding values >= 0x40.
|
|
*/
|
|
if ((bOpcode == X86.OPCODE.ESC1 || bOpcode == X86.OPCODE.ESC3) && modReg >= 0x34) {
|
|
modReg = (reg << 4) | r_m;
|
|
}
|
|
|
|
var aaOpDesc = Debugger.aaaOpFPUDescs[bOpcode];
|
|
if (aaOpDesc) aOpDesc = aaOpDesc[modReg];
|
|
|
|
return aOpDesc;
|
|
};
|
|
|
|
/**
|
|
* getImmOperand(type, dbgAddr)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number} type
|
|
* @param {DbgAddr} dbgAddr
|
|
* @return {string} operand
|
|
*/
|
|
Debugger.prototype.getImmOperand = function(type, dbgAddr)
|
|
{
|
|
var sOperand = ' ';
|
|
var typeSize = type & Debugger.TYPE_SIZE;
|
|
|
|
switch (typeSize) {
|
|
case Debugger.TYPE_BYTE:
|
|
/*
|
|
* There's the occasional immediate byte we don't need to display (eg, the 0x0A
|
|
* following an AAM or AAD instruction), so we suppress the byte if it lacks a TYPE_IN
|
|
* or TYPE_OUT designation (and TYPE_BOTH, as the name implies, includes both).
|
|
*/
|
|
if (type & Debugger.TYPE_BOTH) {
|
|
sOperand = str.toHex(this.getByte(dbgAddr, 1), 2);
|
|
}
|
|
break;
|
|
case Debugger.TYPE_SBYTE:
|
|
sOperand = str.toHex((this.getByte(dbgAddr, 1) << 24) >> 24, dbgAddr.fData32? 8: 4);
|
|
break;
|
|
case Debugger.TYPE_WORD:
|
|
if (dbgAddr.fData32) {
|
|
sOperand = str.toHex(this.getLong(dbgAddr, 4));
|
|
break;
|
|
}
|
|
/* falls through */
|
|
case Debugger.TYPE_SHORT:
|
|
sOperand = str.toHex(this.getShort(dbgAddr, 2), 4);
|
|
break;
|
|
case Debugger.TYPE_FARP:
|
|
dbgAddr = this.newAddr(this.getWord(dbgAddr, true), this.getShort(dbgAddr, 2), null, dbgAddr.type, dbgAddr.fData32, dbgAddr.fAddr32);
|
|
sOperand = this.toHexAddr(dbgAddr);
|
|
var aSymbol = this.findSymbol(dbgAddr);
|
|
if (aSymbol[0]) sOperand += " (" + aSymbol[0] + ")";
|
|
break;
|
|
default:
|
|
sOperand = "imm(" + str.toHexWord(type) + ')';
|
|
break;
|
|
}
|
|
return sOperand;
|
|
};
|
|
|
|
/**
|
|
* getRegOperand(bReg, type, dbgAddr)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number} bReg
|
|
* @param {number} type
|
|
* @param {DbgAddr} dbgAddr
|
|
* @return {string} operand
|
|
*/
|
|
Debugger.prototype.getRegOperand = function(bReg, type, dbgAddr)
|
|
{
|
|
var typeMode = type & Debugger.TYPE_MODE;
|
|
if (typeMode == Debugger.TYPE_SEGREG) {
|
|
if (bReg > Debugger.REG_GS ||
|
|
bReg >= Debugger.REG_FS && this.cpu.model < X86.MODEL_80386) return "??";
|
|
bReg += Debugger.REG_SEG;
|
|
}
|
|
else if (typeMode == Debugger.TYPE_CTLREG) {
|
|
bReg += Debugger.REG_CR0;
|
|
}
|
|
else if (typeMode == Debugger.TYPE_DBGREG) {
|
|
bReg += Debugger.REG_DR0;
|
|
}
|
|
else if (typeMode == Debugger.TYPE_TSTREG) {
|
|
bReg += Debugger.REG_TR0;
|
|
}
|
|
else {
|
|
var typeSize = type & Debugger.TYPE_SIZE;
|
|
if (typeSize >= Debugger.TYPE_SHORT) {
|
|
if (bReg < Debugger.REG_AX) {
|
|
bReg += Debugger.REG_AX - Debugger.REG_AL;
|
|
}
|
|
if (typeSize == Debugger.TYPE_LONG || typeSize == Debugger.TYPE_WORD && dbgAddr.fData32) {
|
|
bReg += Debugger.REG_EAX - Debugger.REG_AX;
|
|
}
|
|
}
|
|
}
|
|
return Debugger.REGS[bReg];
|
|
};
|
|
|
|
/**
|
|
* getSIBOperand(bMod, dbgAddr)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number} bMod
|
|
* @param {DbgAddr} dbgAddr
|
|
* @return {string} operand
|
|
*/
|
|
Debugger.prototype.getSIBOperand = function(bMod, dbgAddr)
|
|
{
|
|
var bSIB = this.getByte(dbgAddr, 1);
|
|
var bScale = bSIB >> 6;
|
|
var bIndex = (bSIB >> 3) & 0x7;
|
|
var bBase = bSIB & 0x7;
|
|
var sOperand = "";
|
|
/*
|
|
* Unless bMod is zero AND bBase is 5, there's always a base register.
|
|
*/
|
|
if (bMod || bBase != 5) {
|
|
sOperand = Debugger.RMS[bBase + 8];
|
|
}
|
|
if (bIndex != 4) {
|
|
if (sOperand) sOperand += '+';
|
|
sOperand += Debugger.RMS[bIndex + 8];
|
|
if (bScale) sOperand += '*' + (0x1 << bScale);
|
|
}
|
|
/*
|
|
* If bMod is zero AND bBase is 5, there's a 32-bit displacement instead of a base register.
|
|
*/
|
|
if (!bMod && bBase == 5) {
|
|
if (sOperand) sOperand += '+';
|
|
sOperand += str.toHex(this.getLong(dbgAddr, 4));
|
|
}
|
|
return sOperand;
|
|
};
|
|
|
|
/**
|
|
* getModRMOperand(sOpcode, bModRM, type, cOperands, dbgAddr)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {string} sOpcode
|
|
* @param {number} bModRM
|
|
* @param {number} type
|
|
* @param {number} cOperands (if 1, memory operands are prefixed with the size; otherwise, size can be inferred)
|
|
* @param {DbgAddr} dbgAddr
|
|
* @return {string} operand
|
|
*/
|
|
Debugger.prototype.getModRMOperand = function(sOpcode, bModRM, type, cOperands, dbgAddr)
|
|
{
|
|
var sOperand = "";
|
|
var bMod = bModRM >> 6;
|
|
var bRM = bModRM & 0x7;
|
|
if (bMod < 3) {
|
|
var disp;
|
|
var fInteger = (sOpcode.indexOf("FI") == 0);
|
|
if (!bMod && (!dbgAddr.fAddr32 && bRM == 6 || dbgAddr.fAddr32 && bRM == 5)) {
|
|
bMod = 2;
|
|
} else {
|
|
if (dbgAddr.fAddr32) {
|
|
if (bRM != 4) {
|
|
bRM += 8;
|
|
} else {
|
|
sOperand = this.getSIBOperand(bMod, dbgAddr);
|
|
}
|
|
}
|
|
if (!sOperand) sOperand = Debugger.RMS[bRM];
|
|
}
|
|
if (bMod == 1) {
|
|
disp = this.getByte(dbgAddr, 1);
|
|
if (!(disp & 0x80)) {
|
|
sOperand += '+' + str.toHex(disp, 2);
|
|
}
|
|
else {
|
|
disp = ((disp << 24) >> 24);
|
|
sOperand += '-' + str.toHex(-disp, 2);
|
|
}
|
|
}
|
|
else if (bMod == 2) {
|
|
if (sOperand) sOperand += '+';
|
|
if (!dbgAddr.fAddr32) {
|
|
disp = this.getShort(dbgAddr, 2);
|
|
sOperand += str.toHex(disp, 4);
|
|
} else {
|
|
disp = this.getLong(dbgAddr, 4);
|
|
sOperand += str.toHex(disp);
|
|
}
|
|
}
|
|
sOperand = '[' + sOperand + ']';
|
|
if (cOperands == 1) {
|
|
var sPrefix = "";
|
|
type &= Debugger.TYPE_SIZE;
|
|
if (type == Debugger.TYPE_WORD) {
|
|
type = (dbgAddr.fData32? Debugger.TYPE_LONG : Debugger.TYPE_SHORT);
|
|
}
|
|
switch(type) {
|
|
case Debugger.TYPE_FARP:
|
|
sPrefix = "FAR";
|
|
break;
|
|
case Debugger.TYPE_BYTE:
|
|
sPrefix = "BYTE";
|
|
break;
|
|
case Debugger.TYPE_SHORT:
|
|
if (fInteger) {
|
|
sPrefix = "INT16";
|
|
break;
|
|
}
|
|
/* falls through */
|
|
sPrefix = "WORD";
|
|
break;
|
|
case Debugger.TYPE_LONG:
|
|
sPrefix = "DWORD";
|
|
break;
|
|
case Debugger.TYPE_SINT:
|
|
if (fInteger) {
|
|
sPrefix = "INT32";
|
|
break;
|
|
}
|
|
/* falls through */
|
|
case Debugger.TYPE_SREAL:
|
|
sPrefix = "REAL32";
|
|
break;
|
|
case Debugger.TYPE_LINT:
|
|
if (fInteger) {
|
|
sPrefix = "INT64";
|
|
break;
|
|
}
|
|
/* falls through */
|
|
case Debugger.TYPE_LREAL:
|
|
sPrefix = "REAL64";
|
|
break;
|
|
case Debugger.TYPE_TREAL:
|
|
sPrefix = "REAL80";
|
|
break;
|
|
case Debugger.TYPE_BCD80:
|
|
sPrefix = "BCD80";
|
|
break;
|
|
}
|
|
if (sPrefix) sOperand = sPrefix + ' ' + sOperand;
|
|
}
|
|
}
|
|
else {
|
|
sOperand = this.getRegOperand(bRM, type, dbgAddr);
|
|
}
|
|
return sOperand;
|
|
};
|
|
|
|
/**
|
|
* parseInstruction(sOp, sOperand, addr)
|
|
*
|
|
* TODO: Unimplemented. See parseInstruction() in modules/c1pjs/lib/debugger.js for a working implementation.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {string} sOp
|
|
* @param {string|undefined} sOperand
|
|
* @param {DbgAddr} 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
|
|
*/
|
|
Debugger.prototype.parseInstruction = function(sOp, sOperand, dbgAddr)
|
|
{
|
|
var aOpBytes = [];
|
|
this.println("not supported yet");
|
|
return aOpBytes;
|
|
};
|
|
|
|
/**
|
|
* getFlagOutput(sFlag)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {string} sFlag
|
|
* @return {string} value of flag
|
|
*/
|
|
Debugger.prototype.getFlagOutput = function(sFlag)
|
|
{
|
|
var b;
|
|
switch (sFlag) {
|
|
case 'V':
|
|
b = this.cpu.getOF();
|
|
break;
|
|
case 'D':
|
|
b = this.cpu.getDF();
|
|
break;
|
|
case 'I':
|
|
b = this.cpu.getIF();
|
|
break;
|
|
case 'T':
|
|
b = this.cpu.getTF();
|
|
break;
|
|
case 'S':
|
|
b = this.cpu.getSF();
|
|
break;
|
|
case 'Z':
|
|
b = this.cpu.getZF();
|
|
break;
|
|
case 'A':
|
|
b = this.cpu.getAF();
|
|
break;
|
|
case 'P':
|
|
b = this.cpu.getPF();
|
|
break;
|
|
case 'C':
|
|
b = this.cpu.getCF();
|
|
break;
|
|
default:
|
|
b = 0;
|
|
break;
|
|
}
|
|
return sFlag + (b? '1' : '0') + ' ';
|
|
};
|
|
|
|
/**
|
|
* getLimitString(l)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number} l
|
|
* @return {string}
|
|
*/
|
|
Debugger.prototype.getLimitString = function(l)
|
|
{
|
|
return str.toHex(l, (l & ~0xffff)? 8 : 4);
|
|
};
|
|
|
|
/**
|
|
* getRegOutput(iReg)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number} iReg
|
|
* @return {string}
|
|
*/
|
|
Debugger.prototype.getRegOutput = function(iReg)
|
|
{
|
|
if (iReg >= Debugger.REG_AX && iReg <= Debugger.REG_DI && this.cchReg > 4) iReg += Debugger.REG_EAX - Debugger.REG_AX;
|
|
var sReg = Debugger.REGS[iReg];
|
|
if (iReg == Debugger.REG_CR0 && this.cpu.model == X86.MODEL_80286) sReg = "MS";
|
|
return sReg + '=' + this.getRegString(iReg) + ' ';
|
|
};
|
|
|
|
/**
|
|
* getSegOutput(seg, fProt)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {X86Seg} seg
|
|
* @param {boolean} [fProt]
|
|
* @return {string}
|
|
*/
|
|
Debugger.prototype.getSegOutput = function(seg, fProt)
|
|
{
|
|
return seg.sName + '=' + str.toHex(seg.sel, 4) + (fProt? '[' + str.toHex(seg.base, this.cchAddr) + ',' + this.getLimitString(seg.limit) + ']' : "");
|
|
};
|
|
|
|
/**
|
|
* getDTROutput(sName, sel, addr, addrLimit)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {string} sName
|
|
* @param {number|null} sel
|
|
* @param {number} addr
|
|
* @param {number} addrLimit
|
|
* @return {string}
|
|
*/
|
|
Debugger.prototype.getDTROutput = function(sName, sel, addr, addrLimit)
|
|
{
|
|
return sName + '=' + (sel != null? str.toHex(sel, 4) : "") + '[' + str.toHex(addr, this.cchAddr) + ',' + str.toHex(addrLimit - addr, 4) + ']';
|
|
};
|
|
|
|
/**
|
|
* getRegDump(fProt)
|
|
*
|
|
* Sample 8086 and 80286 real-mode register dump:
|
|
*
|
|
* AX=0000 BX=0000 CX=0000 DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
|
|
* SS=0000 DS=0000 ES=0000 PS=0002 V0 D0 I0 T0 S0 Z0 A0 P0 C0
|
|
* F000:FFF0 EA5BE000F0 JMP F000:E05B
|
|
*
|
|
* Sample 80386 real-mode register dump:
|
|
*
|
|
* EAX=00000000 EBX=00000000 ECX=00000000 EDX=00000000
|
|
* ESP=00000000 EBP=00000000 ESI=00000000 EDI=00000000
|
|
* SS=0000 DS=0000 ES=0000 FS=0000 GS=0000 PS=00000002 V0 D0 I0 T0 S0 Z0 A0 P0 C0
|
|
* F000:FFF0 EA05F900F0 JMP F000:F905
|
|
*
|
|
* Sample 80286 protected-mode register dump:
|
|
*
|
|
* AX=0000 BX=0000 CX=0000 DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
|
|
* SS=0000[000000,FFFF] DS=0000[000000,FFFF] ES=0000[000000,FFFF] A20=ON
|
|
* CS=F000[FF0000,FFFF] LD=0000[000000,FFFF] GD=[000000,FFFF] ID=[000000,03FF]
|
|
* TR=0000 MS=FFF0 PS=0002 V0 D0 I0 T0 S0 Z0 A0 P0 C0
|
|
* F000:FFF0 EA5BE000F0 JMP F000:E05B
|
|
*
|
|
* Sample 80386 protected-mode register dump:
|
|
*
|
|
* EAX=00000000 EBX=00000000 ECX=00000000 EDX=00000000
|
|
* ESP=00000000 EBP=00000000 ESI=00000000 EDI=00000000
|
|
* SS=0000[00000000,FFFF] DS=0000[00000000,FFFF] ES=0000[00000000,FFFF]
|
|
* CS=F000[FFFF0000,FFFF] FS=0000[00000000,FFFF] GS=0000[00000000,FFFF]
|
|
* LD=0000[00000000,FFFF] GD=[00000000,FFFF] ID=[00000000,03FF] TR=0000 A20=ON
|
|
* CR0=00000010 CR2=00000000 CR3=00000000 PS=00000002 V0 D0 I0 T0 S0 Z0 A0 P0 C0
|
|
* F000:0000FFF0 EA05F900F0 JMP F000:0000F905
|
|
*
|
|
* This no longer includes CS in real-mode (or EIP in any mode), because that information can be obtained from the
|
|
* first line of disassembly, which an "r" or "rp" command will also display.
|
|
*
|
|
* Note that even when the processor is in real mode, you can always use the "rp" command to force a protected-mode
|
|
* dump, in case you need to verify any selector base or limit values, since those also affect real-mode operation.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {boolean} [fProt]
|
|
* @return {string}
|
|
*/
|
|
Debugger.prototype.getRegDump = function(fProt)
|
|
{
|
|
var s;
|
|
if (fProt === undefined) fProt = this.getCPUMode();
|
|
|
|
s = this.getRegOutput(Debugger.REG_AX) +
|
|
this.getRegOutput(Debugger.REG_BX) +
|
|
this.getRegOutput(Debugger.REG_CX) +
|
|
this.getRegOutput(Debugger.REG_DX) + (this.cchReg > 4? '\n' : '') +
|
|
this.getRegOutput(Debugger.REG_SP) +
|
|
this.getRegOutput(Debugger.REG_BP) +
|
|
this.getRegOutput(Debugger.REG_SI) +
|
|
this.getRegOutput(Debugger.REG_DI) + '\n' +
|
|
this.getSegOutput(this.cpu.segSS, fProt) + ' ' +
|
|
this.getSegOutput(this.cpu.segDS, fProt) + ' ' +
|
|
this.getSegOutput(this.cpu.segES, fProt) + ' ';
|
|
|
|
if (fProt) {
|
|
var sTR = "TR=" + str.toHex(this.cpu.segTSS.sel, 4);
|
|
var sA20 = "A20=" + (this.bus.getA20()? "ON " : "OFF ");
|
|
if (this.cpu.model < X86.MODEL_80386) {
|
|
sTR = '\n' + sTR;
|
|
s += sA20; sA20 = '';
|
|
}
|
|
s += '\n' + this.getSegOutput(this.cpu.segCS, fProt) + ' ';
|
|
if (I386 && this.cpu.model >= X86.MODEL_80386) {
|
|
sA20 += '\n';
|
|
s += this.getSegOutput(this.cpu.segFS, fProt) + ' ' +
|
|
this.getSegOutput(this.cpu.segGS, fProt) + '\n';
|
|
}
|
|
s += this.getDTROutput("LD", this.cpu.segLDT.sel, this.cpu.segLDT.base, this.cpu.segLDT.base + this.cpu.segLDT.limit) + ' ' +
|
|
this.getDTROutput("GD", null, this.cpu.addrGDT, this.cpu.addrGDTLimit) + ' ' +
|
|
this.getDTROutput("ID", null, this.cpu.addrIDT, this.cpu.addrIDTLimit) + ' ';
|
|
s += sTR + ' ' + sA20;
|
|
s += this.getRegOutput(Debugger.REG_CR0);
|
|
if (I386 && this.cpu.model >= X86.MODEL_80386) {
|
|
s += this.getRegOutput(Debugger.REG_CR2) + this.getRegOutput(Debugger.REG_CR3);
|
|
}
|
|
} else {
|
|
if (I386 && this.cpu.model >= X86.MODEL_80386) {
|
|
s += this.getSegOutput(this.cpu.segFS, fProt) + ' ' +
|
|
this.getSegOutput(this.cpu.segGS, fProt) + ' ';
|
|
}
|
|
}
|
|
|
|
s += this.getRegOutput(Debugger.REG_PS) +
|
|
this.getFlagOutput('V') + this.getFlagOutput('D') + this.getFlagOutput('I') + this.getFlagOutput('T') +
|
|
this.getFlagOutput('S') + this.getFlagOutput('Z') + this.getFlagOutput('A') + this.getFlagOutput('P') + this.getFlagOutput('C');
|
|
|
|
return s;
|
|
};
|
|
|
|
Debugger.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 {Debugger}
|
|
* @param {Array.<number>} aVals
|
|
* @param {Array.<string>} aOps
|
|
* @param {number} [cOps] (default is all)
|
|
* @return {boolean} true if successful, false if error
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @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
|
|
*/
|
|
Debugger.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(Debugger.aBinOpPrecedence[sOp] != null);
|
|
if (aOps.length && Debugger.aBinOpPrecedence[sOp] < Debugger.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 {Debugger}
|
|
* @param {string} s
|
|
* @return {string}
|
|
*/
|
|
Debugger.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), dbgAddr.fData32? 8 : 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 {Debugger}
|
|
* @param {string} s
|
|
* @return {string}
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @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
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {string|null} sVar
|
|
* @param {number|undefined} value
|
|
* @return {boolean} true if value defined, false if not
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {string} [sVar]
|
|
* @return {boolean} true if all value(s) defined, false if not
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {string} sVar
|
|
*/
|
|
Debugger.prototype.delVariable = function(sVar)
|
|
{
|
|
delete this.aVariables[sVar];
|
|
};
|
|
|
|
/**
|
|
* getVariable(sVar)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {string} sVar
|
|
* @return {number|undefined}
|
|
*/
|
|
Debugger.prototype.getVariable = function(sVar)
|
|
{
|
|
return this.aVariables[sVar];
|
|
};
|
|
|
|
/**
|
|
* setVariable(sVar, value)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {string} sVar
|
|
* @param {number} value
|
|
*/
|
|
Debugger.prototype.setVariable = function(sVar, value)
|
|
{
|
|
this.aVariables[sVar] = value;
|
|
};
|
|
|
|
/**
|
|
* comparePairs(p1, p2)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number|string|Array|Object} p1
|
|
* @param {number|string|Array|Object} p2
|
|
* @return {number}
|
|
*/
|
|
Debugger.prototype.comparePairs = function(p1, p2)
|
|
{
|
|
return p1[0] > p2[0]? 1 : p1[0] < p2[0]? -1 : 0;
|
|
};
|
|
|
|
/**
|
|
* addSymbols(sModule, nSegment, sel, off, 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:
|
|
*
|
|
* [sel, off, 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 {Debugger}
|
|
* @param {string|null} sModule
|
|
* @param {number} nSegment (zero if undefined)
|
|
* @param {number} sel (the default segment/selector for all symbols in this group)
|
|
* @param {number} off (from the base of the given selector)
|
|
* @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)
|
|
*/
|
|
Debugger.prototype.addSymbols = function(sModule, nSegment, sel, off, 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 selSymbol = symbol['s'];
|
|
var sAnnotation = symbol['a'];
|
|
if (offSymbol !== undefined) {
|
|
if (selSymbol !== undefined) {
|
|
dbgAddr.off = offSymbol;
|
|
dbgAddr.sel = selSymbol;
|
|
dbgAddr.addr = null;
|
|
/*
|
|
* getAddr() computes the corresponding physical address and saves it in dbgAddr.addr.
|
|
*/
|
|
this.getAddr(dbgAddr);
|
|
/*
|
|
* The physical address for any symbol located in the top 64Kb of the machine's address space
|
|
* should be relocated to the top 64Kb of the first 1Mb, so that we're immune from any changes
|
|
* to the A20 line.
|
|
*/
|
|
if ((dbgAddr.addr & ~0xffff) == (this.bus.nBusLimit & ~0xffff)) {
|
|
dbgAddr.addr &= 0x000fffff;
|
|
}
|
|
symbol['p'] = dbgAddr.addr;
|
|
}
|
|
usr.binaryInsert(aOffsets, [offSymbol >>> 0, sSymbol], this.comparePairs);
|
|
}
|
|
if (sAnnotation) symbol['a'] = sAnnotation.replace(/''/g, "\"");
|
|
}
|
|
var symbolTable = {
|
|
sModule: sModule,
|
|
nSegment: nSegment,
|
|
sel: sel,
|
|
off: off,
|
|
addr: addr,
|
|
len: len,
|
|
aSymbols: aSymbols,
|
|
aOffsets: aOffsets
|
|
};
|
|
this.aSymbolTable.push(symbolTable);
|
|
};
|
|
|
|
/**
|
|
* removeSymbols(sModule, nSegment)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {string|null} sModule
|
|
* @param {number} [nSegment] (segment # if sModule set, selector if sModule clear)
|
|
* @return {string|null} name of the module removed, or null if no module was found
|
|
*/
|
|
Debugger.prototype.removeSymbols = function(sModule, nSegment)
|
|
{
|
|
var sModuleRemoved = null;
|
|
for (var iTable = 0; iTable < this.aSymbolTable.length; iTable++) {
|
|
var symbolTable = this.aSymbolTable[iTable];
|
|
if (sModule && symbolTable.sModule != sModule) continue;
|
|
if (sModule && nSegment == symbolTable.nSegment || !sModule && nSegment == symbolTable.sel) {
|
|
sModuleRemoved = symbolTable.sModule;
|
|
this.aSymbolTable.splice(iTable, 1);
|
|
break;
|
|
}
|
|
}
|
|
return sModuleRemoved;
|
|
};
|
|
|
|
/**
|
|
* dumpSymbols()
|
|
*
|
|
* TODO: Add "numerical" and "alphabetical" dump options. This is simply dumping them in whatever
|
|
* order they appeared in the original MAP file.
|
|
*
|
|
* @this {Debugger}
|
|
*/
|
|
Debugger.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 selSymbol = symbol['s'];
|
|
if (selSymbol === undefined) selSymbol = symbolTable.sel;
|
|
var sSymbolOrig = symbolTable.aSymbols[sSymbol]['l'];
|
|
if (sSymbolOrig) sSymbol = sSymbolOrig;
|
|
this.println(this.toHexOffset(offSymbol, selSymbol) + ' ' + 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 {Debugger}
|
|
* @param {DbgAddr} dbgAddr
|
|
* @param {boolean} [fNearest]
|
|
* @return {Array} where [0] == symbol name, [1] == symbol value, [2] == any annotation, and [3] == any associated comment
|
|
*/
|
|
Debugger.prototype.findSymbol = function(dbgAddr, fNearest)
|
|
{
|
|
var aSymbol = [];
|
|
var offSymbol = dbgAddr.off >>> 0;
|
|
var addrSymbol = this.getAddr(dbgAddr) >>> 0;
|
|
for (var iTable = 0; iTable < this.aSymbolTable.length; iTable++) {
|
|
var symbolTable = this.aSymbolTable[iTable];
|
|
var sel = symbolTable.sel;
|
|
var off = symbolTable.off >>> 0;
|
|
var addr = symbolTable.addr;
|
|
if (addr != null) addr >>>= 0;
|
|
var len = symbolTable.len;
|
|
if (sel == 0x30) sel = 0x28; // TODO: Remove this hack once we're able to differentiate Windows 95 ring 0 code and data
|
|
if (sel == dbgAddr.sel && offSymbol >= off && offSymbol < off + len || addr != null && addrSymbol >= addr && addrSymbol < addr + len) {
|
|
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;
|
|
}
|
|
}
|
|
if (!aSymbol.length) {
|
|
var sSymbol = this.bus.getSymbol(addrSymbol, true);
|
|
if (sSymbol) {
|
|
aSymbol.push(sSymbol);
|
|
aSymbol.push(addrSymbol);
|
|
}
|
|
}
|
|
return aSymbol;
|
|
};
|
|
|
|
/**
|
|
* findSymbolAddr(sSymbol)
|
|
*
|
|
* Search aSymbolTable for sSymbol, and if found, return a dbgAddr (same as parseAddr())
|
|
*
|
|
* @this {Debugger}
|
|
* @param {string} sSymbol
|
|
* @return {DbgAddr|undefined}
|
|
*/
|
|
Debugger.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.
|
|
*/
|
|
var selSymbol = symbol['s'];
|
|
if (selSymbol === undefined) selSymbol = symbolTable.sel;
|
|
dbgAddr = this.newAddr(offSymbol, selSymbol, symbol['p']);
|
|
}
|
|
/*
|
|
* 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
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
*/
|
|
Debugger.prototype.doHelp = function()
|
|
{
|
|
var s = "commands:";
|
|
for (var sCommand in Debugger.COMMANDS) {
|
|
s += '\n' + str.pad(sCommand, 7) + Debugger.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 {Debugger}
|
|
* @param {Array.<string>} asArgs is the complete argument array, beginning with the "a" command in asArgs[0]
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {string} sCmd
|
|
* @param {string|undefined} [sAddr]
|
|
* @param {string} [sOptions] (the rest of the breakpoint command-line)
|
|
*/
|
|
Debugger.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 = {};
|
|
if (sAddr != '*') {
|
|
dbgAddr = this.parseAddr(sAddr, true, true);
|
|
if (!dbgAddr) return;
|
|
}
|
|
|
|
sAddr = (dbgAddr.off == null? sAddr : str.toHexWord(dbgAddr.off));
|
|
|
|
if (sParm == 'c') {
|
|
if (dbgAddr.off == 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.off)? "enabled" : "cleared") + ": port " + sAddr + " (input)");
|
|
return;
|
|
}
|
|
|
|
if (sParm == 'o') {
|
|
this.println("breakpoint " + (this.bus.addPortOutputBreak(dbgAddr.off)? "enabled" : "cleared") + ": port " + sAddr + " (output)");
|
|
return;
|
|
}
|
|
|
|
if (dbgAddr.off == 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 {Debugger}
|
|
* @param {string} [sCmd] (eg, "cls" or "clear")
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {Array.<string>} asArgs (formerly sCmd, [sAddr], [sLen] and [sBytes])
|
|
*/
|
|
Debugger.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 Debugger.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");
|
|
this.println("\tdi [#] dump descriptor info for IDT #");
|
|
this.println("\tds [#] dump descriptor info for selector #");
|
|
if (BACKTRACK) {
|
|
this.println("\tdt [a] dump backtrack info for address a");
|
|
}
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Transform a "ds" command into a "d desc" command (simply as shorthand); ditto for "dg" and "dl",
|
|
* only because that's the syntax that WDEB386 used. I'm uncertain what WDEB386 would do with an LDT
|
|
* selector passed to "dg" or a GDT selector passed to "dl" (because I'm too lazy to check right now),
|
|
* but that seems nonsensical.
|
|
*/
|
|
if (sCmd == "ds" || sCmd == "dg" || sCmd == "dl") {
|
|
sCmd = "d";
|
|
asArgs = [sCmd, "desc", sAddr];
|
|
}
|
|
|
|
/*
|
|
* Handle the "dp" (aka "d page") commands here.
|
|
*/
|
|
if (sCmd == "d" && sAddr == "page") {
|
|
sCmd = "dp";
|
|
asArgs.shift();
|
|
}
|
|
if (sCmd == "dp") {
|
|
asArgs.shift();
|
|
this.dumpPage(asArgs);
|
|
return;
|
|
}
|
|
|
|
if (sCmd == "d") {
|
|
/*
|
|
* Transform a "d disk" command into a "l json" command (TODO: Register a dumper for "disk" instead?)
|
|
*/
|
|
if (sAddr == "disk") {
|
|
asArgs[0] = "l";
|
|
asArgs[1] = "json";
|
|
this.doLoad(asArgs);
|
|
return;
|
|
}
|
|
for (m in Debugger.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;
|
|
}
|
|
|
|
if (sCmd == "di") {
|
|
asArgs.shift();
|
|
this.dumpIDT(asArgs);
|
|
return;
|
|
}
|
|
|
|
if (sCmd == "dt") {
|
|
asArgs.shift();
|
|
var sInfo = this.dumpBackTrack(asArgs);
|
|
this.println(sInfo);
|
|
return;
|
|
}
|
|
|
|
var dbgAddr = this.parseAddr(sAddr);
|
|
if (!dbgAddr || dbgAddr.sel == null && dbgAddr.addr == null) 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 {Debugger}
|
|
* @param {Array.<string>} asArgs
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {string|undefined} sParm
|
|
*/
|
|
Debugger.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];
|
|
});
|
|
for (i = 0; i < aaSortedOpcodeCounts.length; i++) {
|
|
var bOpcode = aaSortedOpcodeCounts[i][0];
|
|
var cFreq = aaSortedOpcodeCounts[i][1];
|
|
if (cFreq) {
|
|
this.println((Debugger.INS_NAMES[this.aaOpDescs[bOpcode][0]] + " ").substr(0, 5) + " (" + str.toHexByte(bOpcode) + "): " + cFreq + " times");
|
|
cData++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!cData) {
|
|
this.println("no frequency data available");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* doHalt(fQuiet)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {boolean} [fQuiet]
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {string} sCmd
|
|
* @param {boolean} [fQuiet]
|
|
* @return {boolean} true if expression is non-zero, false if zero (or undefined due to a parse error)
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {Array.<string>} asArgs
|
|
* @return {boolean} true only if the instruction info command ("n") is supported
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {string|undefined} sPort
|
|
*/
|
|
Debugger.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(sInt)
|
|
*
|
|
* Displays information about the given software interrupt (assuming that said interrupt is in progress).
|
|
*
|
|
* These messages also reset the system variable $ops (by updating cOpcodesStart), to make it easier to see
|
|
* how many opcodes were executed since these interrupts "started".
|
|
*
|
|
* @this {Debugger}
|
|
* @param {string|undefined} sInt
|
|
* @return {boolean} true if successful, false if not
|
|
*/
|
|
Debugger.prototype.doInt = function(sInt)
|
|
{
|
|
switch(this.parseValue(sInt)) {
|
|
case 0x13:
|
|
this.messageInt(Interrupts.DISK, this.cpu.regLIP, true);
|
|
this.cOpcodesStart = this.cOpcodes;
|
|
return true;
|
|
case 0x21:
|
|
this.messageInt(Interrupts.DOS, this.cpu.regLIP, true);
|
|
this.cOpcodesStart = this.cOpcodes;
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* doVar(sCmd)
|
|
*
|
|
* The command must be of the form "{variable} = [{expression}]", where expression may contain constants,
|
|
* operators, registers, symbols, other variables, or nothing at all; in the latter case, the variable, if
|
|
* any, is deleted.
|
|
*
|
|
* Other supported shorthand: "var" with no parameters prints the values of all variables, and "var {variable}"
|
|
* prints the value of the specified variable.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {string} sCmd
|
|
* @return {boolean} true if valid "var" assignment, false if not
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {string} sAddr
|
|
* @param {boolean} [fPrint]
|
|
* @return {string|null}
|
|
*/
|
|
Debugger.prototype.doList = function(sAddr, fPrint)
|
|
{
|
|
var sSymbol = null;
|
|
|
|
var dbgAddr = this.parseAddr(sAddr, true);
|
|
if (dbgAddr) {
|
|
|
|
var addr = this.getAddr(dbgAddr);
|
|
if (MAXDEBUG && fPrint) {
|
|
this.println(this.toHexAddr(dbgAddr) + " (%" + str.toHex(addr, this.cchAddr) + ')');
|
|
}
|
|
|
|
var aSymbol = this.findSymbol(dbgAddr, true);
|
|
if (aSymbol.length) {
|
|
var nDelta, sDelta, s;
|
|
if (aSymbol[0]) {
|
|
sDelta = "";
|
|
nDelta = dbgAddr.off - aSymbol[1];
|
|
if (nDelta) sDelta = " + " + str.toHexWord(nDelta);
|
|
s = aSymbol[0] + " (" + this.toHexOffset(aSymbol[1], dbgAddr.sel) + ')' + sDelta;
|
|
if (fPrint) this.println(s);
|
|
sSymbol = s;
|
|
}
|
|
if (aSymbol.length > 4 && aSymbol[4]) {
|
|
sDelta = "";
|
|
nDelta = aSymbol[5] - dbgAddr.off;
|
|
if (nDelta) sDelta = " - " + str.toHexWord(nDelta);
|
|
s = aSymbol[4] + " (" + this.toHexOffset(aSymbol[5], dbgAddr.sel) + ')' + sDelta;
|
|
if (fPrint) this.println(s);
|
|
if (!sSymbol) sSymbol = s;
|
|
}
|
|
} else {
|
|
if (fPrint) this.println("no symbols");
|
|
}
|
|
}
|
|
return sSymbol;
|
|
};
|
|
|
|
/**
|
|
* doLoad(asArgs)
|
|
*
|
|
* The format of this command mirrors the DOS DEBUG "L" command:
|
|
*
|
|
* l [address] [drive #] [sector #] [# sectors]
|
|
*
|
|
* The only optional parameter is the last, which defaults to 1 sector if not specified.
|
|
*
|
|
* As a quick-and-dirty way of getting the current contents of a disk image as a JSON dump
|
|
* (which you can then save as .json disk image file), I also support this command:
|
|
*
|
|
* l json [drive #]
|
|
*
|
|
* which is aliased to this command:
|
|
*
|
|
* d disk [drive #]
|
|
*
|
|
* @this {Debugger}
|
|
* @param {Array.<string>} asArgs
|
|
*/
|
|
Debugger.prototype.doLoad = function(asArgs)
|
|
{
|
|
if (!asArgs[1] || asArgs[1] == '?') {
|
|
this.println("load commands:");
|
|
this.println("\tl [address] [drive #] [sector #] [# sectors]");
|
|
return;
|
|
}
|
|
|
|
var fJSON = (asArgs[1] == "json");
|
|
var iDrive, iSector = 0, nSectors = 0;
|
|
|
|
var dbgAddr = (fJSON? {} : this.parseAddr(asArgs[1]));
|
|
if (!dbgAddr) return;
|
|
|
|
iDrive = this.parseValue(asArgs[2], "drive #");
|
|
if (iDrive === undefined) return;
|
|
if (!fJSON) {
|
|
iSector = this.parseValue(asArgs[3], "sector #");
|
|
if (iSector === undefined) return;
|
|
nSectors = this.parseValue(asArgs[4], "# of sectors");
|
|
if (nSectors === undefined) nSectors = 1;
|
|
}
|
|
|
|
/*
|
|
* We choose the disk controller very simplistically: FDC for drives 0 or 1, and HDC for drives 2
|
|
* and up, unless no HDC is present, in which case we assume FDC for all drive numbers.
|
|
*
|
|
* Both controllers must obviously support the same interfaces; ie, copyDrive(), seekDrive(),
|
|
* and readData(). We also rely on the disk property to determine whether the drive is "loaded".
|
|
*
|
|
* In the case of the HDC, if the drive is valid, then by definition it is also "loaded", since an HDC
|
|
* drive and its disk are inseparable; it's certainly possible that the disk object may be empty at
|
|
* this point (ie, if the disk is uninitialized and unformatted), but that will only affect whether the
|
|
* read succeeds or not.
|
|
*/
|
|
var dc = this.fdc;
|
|
if (iDrive >= 2 && this.hdc) {
|
|
iDrive -= 2;
|
|
dc = this.hdc;
|
|
}
|
|
if (dc) {
|
|
var drive = dc.copyDrive(iDrive);
|
|
if (drive) {
|
|
if (drive.disk) {
|
|
if (fJSON) {
|
|
/*
|
|
* This is an interim solution to dumping disk images in JSON. It has many problems, the
|
|
* "biggest" being that the large disk images really need to be compressed first, because they
|
|
* get "inflated" with use. See the dump() method in the Disk component for more details.
|
|
*/
|
|
this.doClear();
|
|
this.println(drive.disk.toJSON());
|
|
return;
|
|
}
|
|
if (dc.seekDrive(drive, iSector, nSectors)) {
|
|
var cb = 0;
|
|
var fAbort = false;
|
|
var sAddr = this.toHexAddr(dbgAddr);
|
|
while (!fAbort && drive.nBytes-- > 0) {
|
|
(function(dbg, dbgAddrCur) {
|
|
dc.readData(drive, function(b, fAsync) {
|
|
if (b < 0) {
|
|
dbg.println("out of data at address " + dbg.toHexAddr(dbgAddrCur));
|
|
fAbort = true;
|
|
return;
|
|
}
|
|
dbg.setByte(dbgAddrCur, b, 1, true);
|
|
cb++;
|
|
});
|
|
}(this, dbgAddr));
|
|
}
|
|
/*
|
|
* Call updateCPU() now, since we forced setByte() to defer all updates
|
|
*/
|
|
this.cpu.updateCPU(true);
|
|
this.println(cb + " bytes read at " + sAddr);
|
|
} else {
|
|
this.println("sector " + iSector + " request out of range");
|
|
}
|
|
} else {
|
|
this.println("drive " + iDrive + " not loaded");
|
|
}
|
|
} else {
|
|
this.println("invalid drive: " + iDrive);
|
|
}
|
|
} else {
|
|
this.println("disk controller not present");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* doMessages(asArgs)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {Array.<string>} asArgs
|
|
*/
|
|
Debugger.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) & ~(Messages.HALT | Messages.KEYS | Messages.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 Debugger.MESSAGES) {
|
|
if (sCategory == m) {
|
|
bitsMessage = Debugger.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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Display those message categories that match the current criteria (on or off)
|
|
*/
|
|
var n = 0;
|
|
var sCategories = "";
|
|
for (m in Debugger.MESSAGES) {
|
|
if (!sCategory || sCategory == m) {
|
|
var bitMessage = Debugger.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 Messages.INT was turned on
|
|
};
|
|
|
|
/**
|
|
* doMouse(sAction, sDelta)
|
|
*
|
|
* When using the "click" action, specify 0 for Mouse.BUTTON.LEFT or 2 for Mouse.BUTTON.RIGHT.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {string} sAction
|
|
* @param {string} sDelta
|
|
*/
|
|
Debugger.prototype.doMouse = function(sAction, sDelta)
|
|
{
|
|
if (this.mouse) {
|
|
var sign = 1;
|
|
if (sDelta.charAt(0) == '-') {
|
|
sign = -1;
|
|
sDelta = sDelta.substr(1);
|
|
}
|
|
var n = this.parseValue(sDelta, sAction);
|
|
if (n === undefined) return;
|
|
n = (n * sign)|0;
|
|
switch(sAction) {
|
|
case "x":
|
|
this.mouse.moveMouse(n, 0);
|
|
break;
|
|
case "y":
|
|
this.mouse.moveMouse(0, n);
|
|
break;
|
|
case "click":
|
|
this.mouse.clickMouse(n, true);
|
|
this.mouse.clickMouse(n, false);
|
|
break;
|
|
default:
|
|
this.println("unknown action: " + sAction);
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
this.println("no mouse");
|
|
};
|
|
|
|
/**
|
|
* doExecOptions(asArgs)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {Array.<string>} asArgs
|
|
*/
|
|
Debugger.prototype.doExecOptions = function(asArgs)
|
|
{
|
|
if (!asArgs[1] || asArgs[1] == '?') {
|
|
this.println("execution options:");
|
|
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 #");
|
|
return;
|
|
}
|
|
switch (asArgs[1]) {
|
|
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"));
|
|
break;
|
|
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)");
|
|
break;
|
|
default:
|
|
this.println("unknown option: " + asArgs[1]);
|
|
break;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* doOutput(sPort, sByte)
|
|
*
|
|
* Simulate a 1-byte port output operation.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {string|undefined} sPort
|
|
* @param {string|undefined} sByte (string representation of 1 byte)
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {Array.<string>} [asArgs]
|
|
* @param {boolean} [fInstruction] (true to include the current instruction; default is true)
|
|
*/
|
|
Debugger.prototype.doRegisters = function(asArgs, fInstruction)
|
|
{
|
|
if (asArgs && asArgs[1] == '?') {
|
|
this.println("register commands:");
|
|
this.println("\tr\tdump registers");
|
|
if (this.fpu) this.println("\trfp\tdump floating-point registers");
|
|
this.println("\trp\tdump all registers");
|
|
this.println("\trx [#]\tset flag or register x to [#]");
|
|
return;
|
|
}
|
|
|
|
var fProt;
|
|
if (fInstruction == null) fInstruction = true;
|
|
|
|
if (asArgs != null && asArgs.length > 1) {
|
|
var sReg = asArgs[1];
|
|
if (this.fpu && sReg == "fp") {
|
|
this.doFPURegisters(asArgs);
|
|
return;
|
|
}
|
|
if (sReg == 'p') {
|
|
fProt = (this.cpu.model >= X86.MODEL_80286);
|
|
}
|
|
else {
|
|
// fInstruction = false;
|
|
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();
|
|
if (sRegMatch.charAt(0) == 'E' && this.cchReg <= 4) {
|
|
sRegMatch = null;
|
|
}
|
|
switch (sRegMatch) {
|
|
case "AL":
|
|
this.cpu.regEAX = (this.cpu.regEAX & ~0xff) | (w & 0xff);
|
|
break;
|
|
case "AH":
|
|
this.cpu.regEAX = (this.cpu.regEAX & ~0xff00) | ((w << 8) & 0xff);
|
|
break;
|
|
case "AX":
|
|
this.cpu.regEAX = (this.cpu.regEAX & ~0xffff) | (w & 0xffff);
|
|
break;
|
|
case "BL":
|
|
this.cpu.regEBX = (this.cpu.regEBX & ~0xff) | (w & 0xff);
|
|
break;
|
|
case "BH":
|
|
this.cpu.regEBX = (this.cpu.regEBX & ~0xff00) | ((w << 8) & 0xff);
|
|
break;
|
|
case "BX":
|
|
this.cpu.regEBX = (this.cpu.regEBX & ~0xffff) | (w & 0xffff);
|
|
break;
|
|
case "CL":
|
|
this.cpu.regECX = (this.cpu.regECX & ~0xff) | (w & 0xff);
|
|
break;
|
|
case "CH":
|
|
this.cpu.regECX = (this.cpu.regECX & ~0xff00) | ((w << 8) & 0xff);
|
|
break;
|
|
case "CX":
|
|
this.cpu.regECX = (this.cpu.regECX & ~0xffff) | (w & 0xffff);
|
|
break;
|
|
case "DL":
|
|
this.cpu.regEDX = (this.cpu.regEDX & ~0xff) | (w & 0xff);
|
|
break;
|
|
case "DH":
|
|
this.cpu.regEDX = (this.cpu.regEDX & ~0xff00) | ((w << 8) & 0xff);
|
|
break;
|
|
case "DX":
|
|
this.cpu.regEDX = (this.cpu.regEDX & ~0xffff) | (w & 0xffff);
|
|
break;
|
|
case "SP":
|
|
this.cpu.setSP((this.cpu.getSP() & ~0xffff) | (w & 0xffff));
|
|
break;
|
|
case "BP":
|
|
this.cpu.regEBP = (this.cpu.regEBP & ~0xffff) | (w & 0xffff);
|
|
break;
|
|
case "SI":
|
|
this.cpu.regESI = (this.cpu.regESI & ~0xffff) | (w & 0xffff);
|
|
break;
|
|
case "DI":
|
|
this.cpu.regEDI = (this.cpu.regEDI & ~0xffff) | (w & 0xffff);
|
|
break;
|
|
/*
|
|
* DANGER: For any of the segment loads below, by going through the normal CPU
|
|
* segment load procedure, you run the risk of generating a fault in the machine
|
|
* if you're not careful. So, um, be careful.
|
|
*/
|
|
case "DS":
|
|
this.cpu.setDS(w);
|
|
break;
|
|
case "ES":
|
|
this.cpu.setES(w);
|
|
break;
|
|
case "SS":
|
|
this.cpu.setSS(w);
|
|
break;
|
|
case "CS":
|
|
// fInstruction = true;
|
|
this.cpu.setCS(w);
|
|
this.dbgAddrNextCode = this.newAddr(this.cpu.getIP(), this.cpu.getCS());
|
|
break;
|
|
case "IP":
|
|
case "EIP":
|
|
// fInstruction = true;
|
|
this.cpu.setIP(w);
|
|
this.dbgAddrNextCode = this.newAddr(this.cpu.getIP(), this.cpu.getCS());
|
|
break;
|
|
/*
|
|
* I used to alias "PC" (Program Counter) to "IP" (Instruction Pointer), because in PC-DOS 1.00
|
|
* through 2.10, DEBUG.COM did the same thing. Then I discovered that, starting with PC-DOS 3.00,
|
|
* DEBUG.COM changed "PC" to refer to the 16-bit flags register (Program or Processor Control?)
|
|
* I've elected to go for PC-DOS 3.00+ compatibility, since that will be more widely known.
|
|
*
|
|
* PCx86 prefers "PS" (Processor Status) for accessing the FLAGS register in its 16-bit (or 32-bit)
|
|
* entirety. Individual flag bits can also be accessed as 1-bit registers, using the names shown
|
|
* below ("C", "P", "A", "Z", etc.)
|
|
*/
|
|
case "PC":
|
|
case "PS":
|
|
this.cpu.setPS(w);
|
|
break;
|
|
case 'C':
|
|
if (w) this.cpu.setCF(); else this.cpu.clearCF();
|
|
break;
|
|
case 'P':
|
|
if (w) this.cpu.setPF(); else this.cpu.clearPF();
|
|
break;
|
|
case 'A':
|
|
if (w) this.cpu.setAF(); else this.cpu.clearAF();
|
|
break;
|
|
case 'Z':
|
|
if (w) this.cpu.setZF(); else this.cpu.clearZF();
|
|
break;
|
|
case 'S':
|
|
if (w) this.cpu.setSF(); else this.cpu.clearSF();
|
|
break;
|
|
case 'I':
|
|
if (w) this.cpu.setIF(); else this.cpu.clearIF();
|
|
break;
|
|
case 'D':
|
|
if (w) this.cpu.setDF(); else this.cpu.clearDF();
|
|
break;
|
|
case 'V':
|
|
if (w) this.cpu.setOF(); else this.cpu.clearOF();
|
|
break;
|
|
default:
|
|
var fUnknown = true;
|
|
if (this.cpu.model >= X86.MODEL_80286) {
|
|
fUnknown = false;
|
|
switch(sRegMatch){
|
|
case "MS":
|
|
this.cpu.setMSW(w);
|
|
break;
|
|
case "TR":
|
|
/*
|
|
* DANGER: Like any of the segment loads above, by going through the normal CPU
|
|
* segment load procedure, you run the risk of generating a fault in the machine
|
|
* if you're not careful. So, um, be careful.
|
|
*/
|
|
if (this.cpu.segTSS.load(w) === X86.ADDR_INVALID) {
|
|
fValid = false;
|
|
}
|
|
break;
|
|
/*
|
|
* TODO: Add support for GDTR (addr and limit), IDTR (addr and limit), and perhaps
|
|
* even the ability to edit descriptor information associated with each segment register.
|
|
*/
|
|
default:
|
|
fUnknown = true;
|
|
if (I386 && this.cpu.model >= X86.MODEL_80386) {
|
|
fUnknown = false;
|
|
switch(sRegMatch){
|
|
case "EAX":
|
|
this.cpu.regEAX = w;
|
|
break;
|
|
case "EBX":
|
|
this.cpu.regEBX = w;
|
|
break;
|
|
case "ECX":
|
|
this.cpu.regECX = w;
|
|
break;
|
|
case "EDX":
|
|
this.cpu.regEDX = w;
|
|
break;
|
|
case "ESP":
|
|
this.cpu.setSP(w);
|
|
break;
|
|
case "EBP":
|
|
this.cpu.regEBP = w;
|
|
break;
|
|
case "ESI":
|
|
this.cpu.regESI = w;
|
|
break;
|
|
case "EDI":
|
|
this.cpu.regEDI = w;
|
|
break;
|
|
/*
|
|
* DANGER: For any of the segment loads below, by going through the normal CPU
|
|
* segment load procedure, you run the risk of generating a fault in the machine
|
|
* if you're not careful. So, um, be careful.
|
|
*/
|
|
case "FS":
|
|
this.cpu.setFS(w);
|
|
break;
|
|
case "GS":
|
|
this.cpu.setGS(w);
|
|
break;
|
|
case "CR0":
|
|
this.cpu.regCR0 = w;
|
|
X86.helpLoadCR0.call(this.cpu, w);
|
|
break;
|
|
case "CR2":
|
|
this.cpu.regCR2 = w;
|
|
break;
|
|
case "CR3":
|
|
this.cpu.regCR3 = w;
|
|
X86.helpLoadCR3.call(this.cpu, w);
|
|
break;
|
|
/*
|
|
* TODO: Add support for DR0-DR7 and TR6-TR7.
|
|
*/
|
|
default:
|
|
fUnknown = true;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (fUnknown) {
|
|
this.println("unknown register: " + sReg);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if (!fValid) {
|
|
this.println("invalid value: " + sValue);
|
|
return;
|
|
}
|
|
this.cpu.updateCPU();
|
|
this.println("updated registers:");
|
|
}
|
|
}
|
|
|
|
this.println(this.getRegDump(fProt));
|
|
|
|
if (fInstruction) {
|
|
this.dbgAddrNextCode = this.newAddr(this.cpu.getIP(), this.cpu.getCS());
|
|
this.doUnassemble(this.toHexAddr(this.dbgAddrNextCode));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* doFPURegisters(asArgs)
|
|
*
|
|
* NOTE: If we're called, the existence of an FPU has already been verified.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {Array.<string>} [asArgs]
|
|
*/
|
|
Debugger.prototype.doFPURegisters = function(asArgs)
|
|
{
|
|
var fpu = this.fpu;
|
|
this.assert(fpu);
|
|
var wStatus = fpu.getStatus(), wControl = fpu.getControl();
|
|
for (var i = 0; i < 8; i++) {
|
|
var a = fpu.readFPUStack(i);
|
|
if (!a) break;
|
|
var sValue = str.pad(a[2].toFixed(15), 24, true);
|
|
this.println("ST" + i + ": " + sValue + " " + str.toHex(a[4]) + "," + str.toHex(a[3]) + " [" + a[0] + ":" + Debugger.FPU_TAGS[a[1]] + "]");
|
|
// this.println(" REG" + a[0] + " " + str.toBin(a[7], 16) + str.toBin(a[6]) + str.toBin(a[5]));
|
|
}
|
|
this.println(" B3SSS210ESPUOZDI xxxIRRPPIxPUOZDI");
|
|
this.println("SW: " + str.toBin(wStatus, 16) + " (" + str.toHexWord(wStatus) + ") CW: " + str.toBin(wControl, 16) + " (" + str.toHexWord(wControl) + ")");
|
|
};
|
|
|
|
/**
|
|
* doRun(sCmd, sAddr, sOptions, fQuiet)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {string} sCmd
|
|
* @param {string|undefined} [sAddr]
|
|
* @param {string} [sOptions] (the rest of the breakpoint command-line)
|
|
* @param {boolean} [fQuiet]
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {string} sCmd
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {string} [sCmd] "p" or "pr"
|
|
*/
|
|
Debugger.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 fPrefix;
|
|
var fRepeat = false;
|
|
var dbgAddr = this.newAddr(this.cpu.getIP(), this.cpu.getCS());
|
|
do {
|
|
fPrefix = false;
|
|
var bOpcode = this.getByte(dbgAddr);
|
|
switch (bOpcode) {
|
|
case X86.OPCODE.ES:
|
|
case X86.OPCODE.CS:
|
|
case X86.OPCODE.SS:
|
|
case X86.OPCODE.DS:
|
|
case X86.OPCODE.FS: // I386 only
|
|
case X86.OPCODE.GS: // I386 only
|
|
case X86.OPCODE.OS: // I386 only
|
|
case X86.OPCODE.AS: // I386 only
|
|
case X86.OPCODE.LOCK:
|
|
this.incAddr(dbgAddr, 1);
|
|
fPrefix = true;
|
|
break;
|
|
case X86.OPCODE.INT3:
|
|
case X86.OPCODE.INTO:
|
|
this.nStep = nStep;
|
|
this.incAddr(dbgAddr, 1);
|
|
break;
|
|
case X86.OPCODE.INTN:
|
|
case X86.OPCODE.LOOPNZ:
|
|
case X86.OPCODE.LOOPZ:
|
|
case X86.OPCODE.LOOP:
|
|
this.nStep = nStep;
|
|
this.incAddr(dbgAddr, dbgAddr.fData32? 4 : 2);
|
|
break;
|
|
case X86.OPCODE.CALL:
|
|
if (fCallStep) {
|
|
this.nStep = nStep;
|
|
this.incAddr(dbgAddr, dbgAddr.fData32? 5 : 3);
|
|
}
|
|
break;
|
|
case X86.OPCODE.CALLF:
|
|
if (fCallStep) {
|
|
this.nStep = nStep;
|
|
this.incAddr(dbgAddr, dbgAddr.fData32? 7 : 5);
|
|
}
|
|
break;
|
|
case X86.OPCODE.GRP4W:
|
|
if (fCallStep) {
|
|
var w = this.getWord(dbgAddr) & X86.OPCODE.CALLMASK;
|
|
if (w == X86.OPCODE.CALLW || w == X86.OPCODE.CALLFDW) {
|
|
this.nStep = nStep;
|
|
this.getInstruction(dbgAddr); // advance dbgAddr past this variable-length CALL
|
|
}
|
|
}
|
|
break;
|
|
case X86.OPCODE.REPZ:
|
|
case X86.OPCODE.REPNZ:
|
|
this.incAddr(dbgAddr, 1);
|
|
fRepeat = fPrefix = true;
|
|
break;
|
|
case X86.OPCODE.INSB:
|
|
case X86.OPCODE.INSW:
|
|
case X86.OPCODE.OUTSB:
|
|
case X86.OPCODE.OUTSW:
|
|
case X86.OPCODE.MOVSB:
|
|
case X86.OPCODE.MOVSW:
|
|
case X86.OPCODE.CMPSB:
|
|
case X86.OPCODE.CMPSW:
|
|
case X86.OPCODE.STOSB:
|
|
case X86.OPCODE.STOSW:
|
|
case X86.OPCODE.LODSB:
|
|
case X86.OPCODE.LODSW:
|
|
case X86.OPCODE.SCASB:
|
|
case X86.OPCODE.SCASW:
|
|
if (fRepeat) {
|
|
this.nStep = nStep;
|
|
this.incAddr(dbgAddr, 1);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} while (fPrefix);
|
|
|
|
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, fFar)
|
|
*
|
|
* Given a possible return address (typically from the stack), look for a matching CALL (or INT) that
|
|
* immediately precedes that address.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {DbgAddr} dbgAddr
|
|
* @param {boolean} [fFar]
|
|
* @return {string|null} CALL instruction at or near dbgAddr, or null if none
|
|
*/
|
|
Debugger.prototype.getCall = function(dbgAddr, fFar)
|
|
{
|
|
var sCall = null;
|
|
var off = dbgAddr.off;
|
|
var offOrig = off;
|
|
for (var n = 1; n <= 6 && !!off; n++) {
|
|
if (n > 2) {
|
|
dbgAddr.off = off;
|
|
dbgAddr.addr = null;
|
|
var s = this.getInstruction(dbgAddr);
|
|
if (s.indexOf("CALL") >= 0 || fFar && s.indexOf("INT") >= 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 (off + (j - i - 1)/2 == offOrig) {
|
|
sCall = s;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
off--;
|
|
}
|
|
dbgAddr.off = offOrig;
|
|
return sCall;
|
|
};
|
|
|
|
/**
|
|
* doStackTrace(sCmd, sAddr)
|
|
*
|
|
* Use "k" for a normal stack trace and "ks" for a stack trace with symbolic info.
|
|
*
|
|
* @this {Debugger}
|
|
* @param {string} [sCmd]
|
|
* @param {string} [sAddr] (not used yet)
|
|
*/
|
|
Debugger.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 selCode = this.cpu.segCS.sel;
|
|
var dbgAddrCall = this.newAddr();
|
|
var dbgAddrStack = this.newAddr(this.cpu.getSP(), this.cpu.getSS());
|
|
this.println("stack trace for " + this.toHexAddr(dbgAddrStack));
|
|
|
|
while (cFrames < nFrames) {
|
|
var sCall = null, sCallPrev = null, cTests = 256;
|
|
while ((dbgAddrStack.off >>> 0) < (this.cpu.regLSPLimit >>> 0)) {
|
|
dbgAddrCall.off = 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;
|
|
dbgAddrCall.sel = selCode;
|
|
sCall = this.getCall(dbgAddrCall);
|
|
if (sCall) break;
|
|
dbgAddrCall.sel = this.getWord(dbgAddrStack);
|
|
sCall = this.getCall(dbgAddrCall, true);
|
|
if (sCall) {
|
|
selCode = this.getWord(dbgAddrStack, true);
|
|
/*
|
|
* It's not strictly necessary that we skip over the flags word that's pushed as part of any INT
|
|
* instruction, but it reduces the risk of misinterpreting it as a return address on the next iteration.
|
|
*/
|
|
if (sCall.indexOf("INT") > 0) this.getWord(dbgAddrStack, true);
|
|
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 {Debugger}
|
|
* @param {string} [sCmd] ("t", "tc", or "tr")
|
|
* @param {string} [sCount] # of instructions to step
|
|
*/
|
|
Debugger.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);
|
|
}
|
|
);
|
|
};
|
|
|
|
/**
|
|
* initAddrSize(dbgAddr, fComplete, cOverrides)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {DbgAddr} dbgAddr
|
|
* @param {boolean} fComplete
|
|
* @param {number} [cOverrides]
|
|
*/
|
|
Debugger.prototype.initAddrSize = function(dbgAddr, fComplete, cOverrides)
|
|
{
|
|
/*
|
|
* We use dbgAddr.fComplete to record whether or not the caller (ie, getInstruction())
|
|
* processed a complete instruction.
|
|
*/
|
|
dbgAddr.fComplete = fComplete;
|
|
/*
|
|
* For proper disassembly of instructions preceded by an OPERAND (0x66) size prefix, we set
|
|
* dbgAddr.fData32 to true whenever the operand size is 32-bit; similarly, for an ADDRESS (0x67)
|
|
* size prefix, we set dbgAddr.fAddr32 to true whenever the address size is 32-bit.
|
|
*
|
|
* Initially (and every time we've processed a complete instruction), both fields must be
|
|
* set to their original value.
|
|
*/
|
|
if (fComplete) {
|
|
if (dbgAddr.fData32Orig != null) dbgAddr.fData32 = dbgAddr.fData32Orig;
|
|
if (dbgAddr.fAddr32Orig != null) dbgAddr.fAddr32 = dbgAddr.fAddr32Orig;
|
|
dbgAddr.fData32Orig = dbgAddr.fData32;
|
|
dbgAddr.fAddr32Orig = dbgAddr.fAddr32;
|
|
}
|
|
/*
|
|
* Use cOverrides to record whether we previously processed any OPERAND or ADDRESS overrides.
|
|
*/
|
|
dbgAddr.cOverrides = cOverrides || 0;
|
|
};
|
|
|
|
/**
|
|
* isStringIns(bOpcode)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {number} bOpcode
|
|
* @return {boolean} true if string instruction, false if not
|
|
*/
|
|
Debugger.prototype.isStringIns = function(bOpcode)
|
|
{
|
|
return (bOpcode >= X86.OPCODE.MOVSB && bOpcode <= X86.OPCODE.CMPSW || bOpcode >= X86.OPCODE.STOSB && bOpcode <= X86.OPCODE.SCASW);
|
|
};
|
|
|
|
/**
|
|
* doUnassemble(sAddr, sAddrEnd, n)
|
|
*
|
|
* @this {Debugger}
|
|
* @param {string} [sAddr]
|
|
* @param {string} [sAddrEnd]
|
|
* @param {number} [n]
|
|
*/
|
|
Debugger.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.off < dbgAddr.off) return;
|
|
|
|
cb = dbgAddrEnd.off - dbgAddr.off;
|
|
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;
|
|
this.initAddrSize(dbgAddr, true);
|
|
|
|
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);
|
|
|
|
/*
|
|
* If getInstruction() reported that it did not process a complete instruction (via dbgAddr.fComplete),
|
|
* then bump the instruction count by one, so that we display one more line (and hopefully the complete
|
|
* instruction).
|
|
*/
|
|
if (!dbgAddr.fComplete && !n) n++;
|
|
|
|
this.println(sInstruction);
|
|
this.dbgAddrNextCode = dbgAddr;
|
|
cb -= dbgAddr.addr - addr;
|
|
cLines++;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* parseCommand(sCmd, fSave, chSep)
|
|
*
|
|
* @this {Debugger}
|
|
* @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>}
|
|
*/
|
|
Debugger.prototype.parseCommand = function(sCmd, fSave, chSep)
|
|
{
|
|
if (fSave) {
|
|
if (!sCmd) {
|
|
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 {Debugger}
|
|
* @param {Array.<string>} asArgs
|
|
* @return {Array.<string>}
|
|
*/
|
|
Debugger.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 {Debugger}
|
|
* @param {string} sCmd
|
|
* @param {boolean} [fQuiet]
|
|
* @return {boolean} true if command processed, false if unrecognized
|
|
*/
|
|
Debugger.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 = ">> ";
|
|
if (this.cpu.regCR0 & X86.CR0.MSW.PE) {
|
|
sPrompt = (this.cpu.regPS & X86.PS.VM)? "-- " : "## ";
|
|
}
|
|
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 (!PCX86.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;
|
|
}
|
|
this.doLoad(asArgs);
|
|
break;
|
|
case 'm':
|
|
if (asArgs[0] == "mouse") {
|
|
this.doMouse(asArgs[1], asArgs[2]);
|
|
break;
|
|
}
|
|
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 '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((PCX86.APPNAME || "PCx86") + " version " + (XMLVERSION || PCX86.APPVERSION) + " (" + this.cpu.model + (PCX86.COMPILED? ",RELEASE" : (PCX86.DEBUG? ",DEBUG" : ",NODEBUG")) + (PCX86.PREFETCH? ",PREFETCH" : ",NOPREFETCH") + (PCX86.TYPEDARRAYS? ",TYPEDARRAYS" : (PCX86.BYTEARRAYS? ",BYTEARRAYS" : ",LONGARRAYS")) + (PCX86.BACKTRACK? ",BACKTRACK" : ",NOBACKTRACK") + ')');
|
|
this.println(web.getUserAgent());
|
|
break;
|
|
case 'x':
|
|
this.doExecOptions(asArgs);
|
|
break;
|
|
case '?':
|
|
if (asArgs[1]) {
|
|
this.doPrint(sCmd.substr(1));
|
|
break;
|
|
}
|
|
this.doHelp();
|
|
break;
|
|
case 'n':
|
|
if (!PCX86.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 {Debugger}
|
|
* @param {string} sCmds
|
|
* @param {boolean} [fSave]
|
|
* @return {boolean} true if all commands processed, false if not
|
|
*/
|
|
Debugger.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;
|
|
};
|
|
|
|
/**
|
|
* Debugger.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.
|
|
*/
|
|
Debugger.init = function()
|
|
{
|
|
var aeDbg = Component.getElementsByClass(document, PCX86.APPCLASS, "debugger");
|
|
for (var iDbg = 0; iDbg < aeDbg.length; iDbg++) {
|
|
var eDbg = aeDbg[iDbg];
|
|
var parmsDbg = Component.getComponentParms(eDbg);
|
|
var dbg = new Debugger(parmsDbg);
|
|
Component.bindComponentControls(dbg, eDbg, PCX86.APPCLASS);
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Initialize every Debugger module on the page (as IF there's ever going to be more than one ;-))
|
|
*/
|
|
web.onInit(Debugger.init);
|
|
|
|
} // endif DEBUGGER
|
|
|
|
if (NODE) module.exports = Debugger;
|