Debugging improvements, including more support for physical vs. linear addresses

This commit is contained in:
Jeff Parsons 2015-09-15 23:10:22 -07:00
commit 4ef741b7e5
5 changed files with 336 additions and 212 deletions

View file

@ -56,7 +56,7 @@ if (DEBUGGER) {
* off offset, if any * off offset, if any
* sel selector, if any (if null, addr should be set to a linear address) * 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) * addr linear address, if any (if null, addr will be recomputed from sel:off)
* type one of the Debugger.ADDR.* values * type one of the Debugger.ADDRTYPE values
* fData32 true if 32-bit operand size in effect * fData32 true if 32-bit operand size in effect
* fAddr32 true if 32-bit address size in effect * fAddr32 true if 32-bit address size in effect
* cOverrides non-zero if any overrides were processed with this address * cOverrides non-zero if any overrides were processed with this address
@ -306,26 +306,20 @@ if (DEBUGGER) {
}; };
/* /*
* Supported address types; the type field in a DbgAddr object may contain ONE of: * Supported address types; the type field in a DbgAddr object may be one of:
* *
* NONE, REAL, PROT, V86, LINEAR or PHYSICAL * NONE, REAL, PROT, V86, LINEAR or PHYSICAL
* *
* along with ONE of: * REAL and V86 addresses are specified with a '&' prefix, PROT addresses with a '#' prefix,
*
* CODE or DATA
*
* REAL and V86 addresses are specifed with a '&' prefix, PROT addresses with a '#' prefix,
* LINEAR addresses with '%', and PHYSICAL addresses with '%%'. * LINEAR addresses with '%', and PHYSICAL addresses with '%%'.
*/ */
Debugger.ADDR = { Debugger.ADDRTYPE = {
NONE: 0x00, NONE: 0x00,
REAL: 0x01, REAL: 0x01,
PROT: 0x02, PROT: 0x02,
V86: 0x03, V86: 0x03,
LINEAR: 0x04, LINEAR: 0x04,
PHYSICAL: 0x05, PHYSICAL: 0x05
CODE: 0x10,
DATA: 0x20
}; };
/* /*
@ -1298,7 +1292,7 @@ if (DEBUGGER) {
* @param {number} nSegment (logical segment number) * @param {number} nSegment (logical segment number)
* @param {number} sel (current selector) * @param {number} sel (current selector)
* @param {boolean} fCode (true if code segment, false if data segment) * @param {boolean} fCode (true if code segment, false if data segment)
* @param {boolean} [fPrint] * @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) Debugger.prototype.addSegmentInfo = function(dbgAddr, nSegment, sel, fCode, fPrint)
{ {
@ -1306,8 +1300,8 @@ if (DEBUGGER) {
var seg = this.getSegment(sel); var seg = this.getSegment(sel);
var len = seg? seg.limit + 1 : 0; var len = seg? seg.limit + 1 : 0;
var sSection = (fCode? "_CODE" : "_DATA") + str.toHex(nSegment, 2); var sSection = (fCode? "_CODE" : "_DATA") + str.toHex(nSegment, 2);
if (fPrint) { if (fPrint && this.messageEnabled(Messages.MEM)) {
this.println(sModule + ' ' + (fCode? "code" : "data") + '(' + str.toHex(nSegment, 4) + ")=#" + str.toHex(sel, 4) + " len " + str.toHex(len)); this.message(sModule + ' ' + (fCode? "code" : "data") + '(' + str.toHex(nSegment, 4) + ")=#" + str.toHex(sel, 4) + " len " + str.toHex(len));
} }
var off = 0; var off = 0;
var aSymbols = this.findModuleInfo(sModule, nSegment); var aSymbols = this.findModuleInfo(sModule, nSegment);
@ -1320,16 +1314,16 @@ if (DEBUGGER) {
* *
* @this {Debugger} * @this {Debugger}
* @param {number} sel * @param {number} sel
* @param {boolean} [fPrint] * @param {boolean} [fPrint] (false means we're merely monitoring OR we don't really care about these notifications)
*/ */
Debugger.prototype.removeSegmentInfo = function(sel, fPrint) Debugger.prototype.removeSegmentInfo = function(sel, fPrint)
{ {
var sModuleRemoved = this.removeSymbols(null, sel); var sModuleRemoved = this.removeSymbols(null, sel);
if (fPrint) { if (fPrint && this.messageEnabled(Messages.MEM)) {
if (sModuleRemoved) { if (sModuleRemoved) {
this.println(sModuleRemoved + " #" + str.toHex(sel, 4) + " removed"); this.message(sModuleRemoved + " #" + str.toHex(sel, 4) + " removed");
} else { } else {
this.println("unable to remove module for segment #" + str.toHex(sel, 4)); this.message("unable to remove module for segment #" + str.toHex(sel, 4));
} }
} }
}; };
@ -1349,7 +1343,7 @@ if (DEBUGGER) {
* @this {Debugger} * @this {Debugger}
* @param {DbgAddr} dbgAddr (address of D386_Device_Params) * @param {DbgAddr} dbgAddr (address of D386_Device_Params)
* @param {boolean} fCode (true if code section, false if data section) * @param {boolean} fCode (true if code section, false if data section)
* @param {boolean} [fPrint] * @param {boolean} [fPrint] (false means we're merely monitoring, so let WDEB386 print its own notifications)
*/ */
Debugger.prototype.addSectionInfo = function(dbgAddr, fCode, fPrint) Debugger.prototype.addSectionInfo = function(dbgAddr, fCode, fPrint)
{ {
@ -1368,11 +1362,11 @@ if (DEBUGGER) {
sParent += '!'; sParent += '!';
} }
var sSection = (fCode? "_CODE" : "_DATA") + str.toHex(nSegment, 2); var sSection = (fCode? "_CODE" : "_DATA") + str.toHex(nSegment, 2);
if (fPrint) { if (fPrint && this.messageEnabled(Messages.MEM)) {
/* /*
* Mimics WDEB386 output, except that WDEB386 only displays a linear address, omitting the selector. * Mimics WDEB386 output, except that WDEB386 only displays a linear address, omitting the selector.
*/ */
this.println(sParent + sModule + ' ' + (fCode? "code" : "data") + '(' + str.toHex(nSegment, 4) + ")=" + str.toHex(sel, 4) + ':' + str.toHex(off) + " len " + str.toHex(len)); 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 * TODO: Add support for 32-bit symbols; findModuleInfo() relies on Disk.getModuleInfo(), and the Disk
@ -1389,17 +1383,17 @@ if (DEBUGGER) {
* @this {Debugger} * @this {Debugger}
* @param {number} nSegment (logical segment number) * @param {number} nSegment (logical segment number)
* @param {DbgAddr} dbgAddr (address of module) * @param {DbgAddr} dbgAddr (address of module)
* @param {boolean} [fPrint] * @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) Debugger.prototype.removeSectionInfo = function(nSegment, dbgAddr, fPrint)
{ {
var sModule = this.getSZ(dbgAddr).toUpperCase(); var sModule = this.getSZ(dbgAddr).toUpperCase();
var sModuleRemoved = this.removeSymbols(sModule, nSegment); var sModuleRemoved = this.removeSymbols(sModule, nSegment);
if (fPrint) { if (fPrint && this.messageEnabled(Messages.MEM)) {
if (sModuleRemoved) { if (sModuleRemoved) {
this.println(sModule + ' ' + str.toHex(nSegment, 4) + " removed"); this.message(sModule + ' ' + str.toHex(nSegment, 4) + " removed");
} else { } else {
this.println("unable to remove " + sModule + " for section " + str.toHex(nSegment, 4)); this.message("unable to remove " + sModule + " for section " + str.toHex(nSegment, 4));
} }
} }
}; };
@ -1464,13 +1458,13 @@ if (DEBUGGER) {
* This intercepts calls to the Windows Debugger protected-mode interface (INT 0x41). * 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 * 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 * Interrupts.WINDBGRM.ENABLED is true, because if the latter decides to respond to requests,
* requests, then we must start responding, too, because Windows assumes that the former * then we must start responding, too. Windows assumes that if INT 0x68 support is present,
* is installed whenever it detects the latter. * then INT 0x41 support must be present as well.
* *
* Which is also why intWindowsDebuggerRM() will also set this.fWinDbg to true: we MUST * That is why intWindowsDebuggerRM() will also set this.fWinDbg to true: we MUST return false
* return false for all INT 0x41 requests, so that all requests are consumed, since there's * for all INT 0x41 requests, so that all requests are consumed, since there's no guarantee
* no guarantee that a valid interrupt handler exists inside the machine. * that a valid INT 0x41 handler will exist inside the machine.
* *
* @this {Debugger} * @this {Debugger}
* @param {number} addr * @param {number} addr
@ -1478,6 +1472,7 @@ if (DEBUGGER) {
*/ */
Debugger.prototype.intWindowsDebugger = function(addr) Debugger.prototype.intWindowsDebugger = function(addr)
{ {
var dbgAddr;
var cpu = this.cpu; var cpu = this.cpu;
var AX = cpu.regEAX & 0xffff; var AX = cpu.regEAX & 0xffff;
var BX = cpu.regEBX & 0xffff; var BX = cpu.regEBX & 0xffff;
@ -1550,8 +1545,11 @@ if (DEBUGGER) {
break; break;
case Interrupts.WINDBG.RELSEG: // 0x005C case Interrupts.WINDBG.RELSEG: // 0x005C
case Interrupts.WINDBG.EXITCALL: // 0x0062
case Interrupts.WINDBG.LOADDLL: // 0x0064 case Interrupts.WINDBG.LOADDLL: // 0x0064
case Interrupts.WINDBG.DELMODULE: // 0x0065 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 * TODO: Figure out what to do with these notifications, if anything
*/ */
@ -1563,19 +1561,25 @@ if (DEBUGGER) {
break; break;
case Interrupts.WINDBG.CHECKFAULT: // 0x007F case Interrupts.WINDBG.CHECKFAULT: // 0x007F
if (this.fWinDbg) cpu.regEAX = (cpu.regEAX & ~0xffff)|0; // AX == 0 means handle fault normally /*
* TODO: We need some UI to control our response to CHECKFAULT notifications. For now, it's hard-coded.
*/
if (DEBUG) this.println("CHECKFAULT: fault=" + str.toHexWord(BX) + " type=" + str.toHexWord(CX));
if (this.fWinDbg) cpu.regEAX = (cpu.regEAX & ~0xffff)|1; // AX == 0 means handle fault normally, 1 means issue TRAPFAULT
break; break;
case Interrupts.WINDBG.TRAPFAULT: // 0x0083 case Interrupts.WINDBG.TRAPFAULT: // 0x0083
/* /*
* Ordinarily (I think), since we're responding with AX=0 to all CHECKFAULT notifications, * Ordinarily (I think), if we respond with AX=0 to all CHECKFAULT notifications, then
* all TRAPFAULT notifications should be withheld; however, one exception may be if the user * all TRAPFAULT notifications should be withheld; however, one exception may be if the user
* is presented with a fault dialog containing a "Debug" button, and the user clicks it.... * is presented with a fault dialog containing a "Debug" button, and the user clicks it....
* *
* So for now, we'll allocate a temporary breakpoint at the reported fault address whenever * Regardless, whenever we receive this notification, we'll allocate a temporary breakpoint
* this notification comes through. * at the reported fault address.
*/ */
this.addBreakpoint(this.aBreakExec, this.newAddr(cpu.regEDX, CX), true); dbgAddr = this.newAddr(cpu.regEDX, CX);
this.println("TRAPFAULT: fault=" + str.toHexWord(BX) + " error=" + str.toHexLong(cpu.regESI) + " addr=" + this.hexAddr(dbgAddr));
this.addBreakpoint(this.aBreakExec, dbgAddr, true);
break; break;
case Interrupts.WINDBG.GETSYMBOL: // 0x008D case Interrupts.WINDBG.GETSYMBOL: // 0x008D
@ -1646,15 +1650,15 @@ if (DEBUGGER) {
return true; return true;
} }
/* /*
* Ditto for WDEB386 itself, which presumably wants to avoid loading on top of another copy. * Ditto for WDEB386 itself, which presumably wants to avoid loading on top of itself.
*/ */
if (cpu.getLong((cpu.segCS.sel << 4) + 0x5F) == 0x42454457) { if (cpu.getLong((cpu.segCS.sel << 4) + 0x5F) == 0x42454457) {
if (DEBUG) this.println("Ignoring INT 0x68 from WDEB386.EXE"); if (DEBUG) this.println("Ignoring INT 0x68 from WDEB386.EXE");
return true; return true;
} }
/* /*
* We're only going to respond to this function if no one else did, in which case, * We're only going to respond to this function if no one else did, in which case, we'll set
* we'll set fWinDbgRM to true and handle additional notifications. * fWinDbgRM to true and handle additional notifications.
*/ */
cpu.addIntReturn(addr, function(dbg) { cpu.addIntReturn(addr, function(dbg) {
return function onInt68Return(nLevel) { return function onInt68Return(nLevel) {
@ -1689,9 +1693,13 @@ if (DEBUGGER) {
case Interrupts.WINDBGRM.PREP_PMODE: // 0x44 case Interrupts.WINDBGRM.PREP_PMODE: // 0x44
if (this.fWinDbgRM) { 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)); var a = cpu.segCS.addCallBreak(this.callWindowsDebuggerPMInit.bind(this));
if (a) { if (a) {
cpu.regEDI = a[0]; cpu.regEDI = a[0]; // ES:EDI receives the "call break" address
cpu.setES(a[1]); cpu.setES(a[1]);
} }
} }
@ -1805,8 +1813,8 @@ if (DEBUGGER) {
var AL = cpu.regEAX & 0xff; var AL = cpu.regEAX & 0xff;
if (MAXDEBUG) this.println("INT 0x68 callback: " + str.toHexByte(AL)); if (MAXDEBUG) this.println("INT 0x68 callback: " + str.toHexByte(AL));
if (AL == 5) { if (AL == 5) {
cpu.regECX = cpu.regESI = 0; // our in-machine debugger footprint is zero cpu.regECX = cpu.regESI = 0; // our in-machine debugger footprint is zero
cpu.regEAX = (cpu.regEAX & ~0xff) | 0x01; cpu.regEAX = (cpu.regEAX & ~0xff) | 0x01; // TODO: Returning a "don't call" response sounds good, but what does it REALLY mean?
} }
return false; return false;
} }
@ -1922,25 +1930,25 @@ if (DEBUGGER) {
}; };
/** /**
* getCurrentMode() * getCPUMode()
* *
* @this {Debugger} * @this {Debugger}
* @return {boolean} (true if protected mode, false if not) * @return {boolean} (true if protected mode, false if not)
*/ */
Debugger.prototype.getCurrentMode = function() Debugger.prototype.getCPUMode = function()
{ {
return this.cpu && !!(this.cpu.regCR0 & X86.CR0.MSW.PE) && !(this.cpu.regPS & X86.PS.VM); return this.cpu && !!(this.cpu.regCR0 & X86.CR0.MSW.PE) && !(this.cpu.regPS & X86.PS.VM);
}; };
/** /**
* getCurrentType() * getAddressType()
* *
* @this {Debugger} * @this {Debugger}
* @return {number} * @return {number}
*/ */
Debugger.prototype.getCurrentType = function() Debugger.prototype.getAddressType = function()
{ {
return this.getCurrentMode()? Debugger.ADDR.PROT : Debugger.ADDR.REAL; return this.getCPUMode()? Debugger.ADDRTYPE.PROT : Debugger.ADDRTYPE.REAL;
}; };
/** /**
@ -1954,12 +1962,12 @@ if (DEBUGGER) {
* *
* @this {Debugger} * @this {Debugger}
* @param {number|null|undefined} sel * @param {number|null|undefined} sel
* @param {number} [type] (defaults to getCurrentType()) * @param {number} [type] (defaults to getAddressType())
* @return {X86Seg|null} seg * @return {X86Seg|null} seg
*/ */
Debugger.prototype.getSegment = function(sel, type) Debugger.prototype.getSegment = function(sel, type)
{ {
var typeDefault = this.getCurrentType(); var typeDefault = this.getAddressType();
if (!type) type = typeDefault; if (!type) type = typeDefault;
@ -1976,10 +1984,10 @@ if (DEBUGGER) {
* Even if nSuppressBreaks is set, we'll allow the call in real-mode, * Even if nSuppressBreaks is set, we'll allow the call in real-mode,
* because a loadReal() request using segDebugger should generally be safe. * because a loadReal() request using segDebugger should generally be safe.
*/ */
if (this.nSuppressBreaks && type == Debugger.ADDR.PROT || !this.segDebugger) return null; if (this.nSuppressBreaks && type == Debugger.ADDRTYPE.PROT || !this.segDebugger) return null;
} }
var seg = this.segDebugger; var seg = this.segDebugger;
if (type != Debugger.ADDR.PROT) { if (type != Debugger.ADDRTYPE.PROT) {
seg.loadReal(sel); seg.loadReal(sel);
seg.limit = 0xffff; // although an ACTUAL real-mode segment load would not modify the limit, 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 seg.offMax = 0x10000; // proper segDebugger operation requires that we update the limit ourselves
@ -2042,7 +2050,7 @@ if (DEBUGGER) {
var b = 0xff; var b = 0xff;
var addr = this.getAddr(dbgAddr, false, 1); var addr = this.getAddr(dbgAddr, false, 1);
if (addr !== X86.ADDR_INVALID) { if (addr !== X86.ADDR_INVALID) {
b = this.cpu.probeAddr(addr) | 0; b = this.cpu.probeAddr(addr, 1, dbgAddr.type != Debugger.ADDRTYPE.PHYSICAL) | 0;
if (inc) this.incAddr(dbgAddr, inc); if (inc) this.incAddr(dbgAddr, inc);
} }
return b; return b;
@ -2077,7 +2085,7 @@ if (DEBUGGER) {
var w = 0xffff; var w = 0xffff;
var addr = this.getAddr(dbgAddr, false, 2); var addr = this.getAddr(dbgAddr, false, 2);
if (addr !== X86.ADDR_INVALID) { if (addr !== X86.ADDR_INVALID) {
w = this.cpu.probeAddr(addr) | (this.cpu.probeAddr(addr + 1) << 8); w = this.cpu.probeAddr(addr, 2, dbgAddr.type != Debugger.ADDRTYPE.PHYSICAL);
if (inc) this.incAddr(dbgAddr, inc); if (inc) this.incAddr(dbgAddr, inc);
} }
return w; return w;
@ -2096,7 +2104,7 @@ if (DEBUGGER) {
var l = -1; var l = -1;
var addr = this.getAddr(dbgAddr, false, 4); var addr = this.getAddr(dbgAddr, false, 4);
if (addr !== X86.ADDR_INVALID) { if (addr !== X86.ADDR_INVALID) {
l = this.cpu.probeAddr(addr) | (this.cpu.probeAddr(addr + 1) << 8) | (this.cpu.probeAddr(addr + 2) << 16) | (this.cpu.probeAddr(addr + 3) << 24); l = this.cpu.probeAddr(addr, 4, dbgAddr.type != Debugger.ADDRTYPE.PHYSICAL);
if (inc) this.incAddr(dbgAddr, inc); if (inc) this.incAddr(dbgAddr, inc);
} }
return l; return l;
@ -2166,18 +2174,25 @@ if (DEBUGGER) {
*/ */
Debugger.prototype.getAddrPrefix = function(dbgAddr) Debugger.prototype.getAddrPrefix = function(dbgAddr)
{ {
var ch = ''; var ch;
switch (dbgAddr.type) { switch (dbgAddr.type) {
case Debugger.ADDR.REAL: case Debugger.ADDRTYPE.REAL:
case Debugger.ADDR.V86: case Debugger.ADDRTYPE.V86:
ch = '&'; ch = '&';
break; break;
case Debugger.ADDR.PROT: case Debugger.ADDRTYPE.PROT:
ch = '#'; ch = '#';
break; break;
case Debugger.ADDR.LINEAR: case Debugger.ADDRTYPE.LINEAR:
ch = '%'; ch = '%';
break; break;
case Debugger.ADDRTYPE.PHYSICAL:
ch = '%%';
break;
default:
ch = dbgAddr.sel? '' : '%';
break;
} }
return ch; return ch;
}; };
@ -2202,7 +2217,7 @@ if (DEBUGGER) {
dbgAddr.off = off || 0; dbgAddr.off = off || 0;
dbgAddr.sel = sel; dbgAddr.sel = sel;
dbgAddr.addr = addr; dbgAddr.addr = addr;
dbgAddr.type = type || this.getCurrentType(); dbgAddr.type = type || this.getAddressType();
dbgAddr.fData32 = (fData32 != null)? fData32 : (this.cpu && this.cpu.segCS.sizeData == 4); 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.fAddr32 = (fAddr32 != null)? fAddr32 : (this.cpu && this.cpu.segCS.sizeAddr == 4);
dbgAddr.fTempBreak = false; dbgAddr.fTempBreak = false;
@ -2274,7 +2289,7 @@ if (DEBUGGER) {
}; };
/** /**
* parseAddr(sAddr, type, fNoChecks, fPrint) * parseAddr(sAddr, fCode, fNoChecks, fPrint)
* *
* As discussed above, dbgAddr variables contain one or more of: off, sel, and addr. They represent * 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 * a segmented address (sel:off) when sel is defined or a linear address (addr) when sel is undefined
@ -2297,20 +2312,19 @@ if (DEBUGGER) {
* *
* @this {Debugger} * @this {Debugger}
* @param {string|undefined} sAddr * @param {string|undefined} sAddr
* @param {number|undefined} [type] is either CODE or DATA, in case sAddr doesn't specify a segment * @param {boolean} [fCode] (true if target is code, false if target is data)
* @param {boolean} [fNoChecks] (eg, true when setting breakpoints that may not be valid now, but will be later) * @param {boolean} [fNoChecks] (true when setting breakpoints that may not be valid now, but will be later)
* @param {boolean} [fPrint] * @param {boolean} [fPrint]
* @return {DbgAddr|null|undefined} * @return {DbgAddr|null|undefined}
*/ */
Debugger.prototype.parseAddr = function(sAddr, type, fNoChecks, fPrint) Debugger.prototype.parseAddr = function(sAddr, fCode, fNoChecks, fPrint)
{ {
var dbgAddr; var dbgAddr;
var dbgAddrNext = (type === Debugger.ADDR.CODE? this.dbgAddrNextCode : this.dbgAddrNextData); 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; var off = dbgAddrNext.off, sel = dbgAddrNext.sel, addr = dbgAddrNext.addr;
type = fNoChecks? Debugger.ADDR.NONE : dbgAddrNext.type;
if (sAddr !== undefined) { if (sAddr !== undefined) {
sAddr = this.parseReference(sAddr); sAddr = this.parseReference(sAddr);
@ -2320,25 +2334,30 @@ if (DEBUGGER) {
switch(ch) { switch(ch) {
case '&': case '&':
type = Debugger.ADDR.REAL; type = Debugger.ADDRTYPE.REAL;
break; break;
case '#': case '#':
type = Debugger.ADDR.PROT; type = Debugger.ADDRTYPE.PROT;
break; break;
case '%': case '%':
type = Debugger.ADDR.LINEAR; type = Debugger.ADDRTYPE.LINEAR;
ch = sAddr.charAt(1);
if (ch == '%') {
type = Debugger.ADDRTYPE.PHYSICAL;
ch += ch;
}
off = addr = 0; off = addr = 0;
sel = null; // we still have code that relies on this crutch, instead of the type field sel = null; // we still have code that relies on this crutch, instead of the type field
break; break;
default: default:
if (iColon >= 0) type = Debugger.ADDR.NONE; if (iColon >= 0) type = Debugger.ADDRTYPE.NONE;
ch = ''; ch = '';
break; break;
} }
if (ch) { if (ch) {
sAddr = sAddr.substr(1); sAddr = sAddr.substr(ch.length);
iColon--; iColon -= ch.length;
} }
dbgAddr = this.findSymbolAddr(sAddr); dbgAddr = this.findSymbolAddr(sAddr);
@ -2436,7 +2455,7 @@ if (DEBUGGER) {
Debugger.prototype.hexAddr = function(dbgAddr) Debugger.prototype.hexAddr = function(dbgAddr)
{ {
var ch = this.getAddrPrefix(dbgAddr); var ch = this.getAddrPrefix(dbgAddr);
return dbgAddr.sel == null? ('%' + str.toHex(dbgAddr.addr)) : (ch + this.hexOffset(dbgAddr.off, dbgAddr.sel, dbgAddr.fAddr32)); return dbgAddr.sel == null? (ch + str.toHex(dbgAddr.addr)) : (ch + this.hexOffset(dbgAddr.off, dbgAddr.sel, dbgAddr.fAddr32));
}; };
/** /**
@ -2506,10 +2525,10 @@ if (DEBUGGER) {
*/ */
Debugger.prototype.dumpBlocks = function(aBlocks, sAddr, fLinear) Debugger.prototype.dumpBlocks = function(aBlocks, sAddr, fLinear)
{ {
var i = 0, n = aBlocks.length; var addr = 0, i = 0, n = aBlocks.length;
if (sAddr) { if (sAddr) {
var addr = this.getAddr(this.parseAddr(sAddr)); addr = this.getAddr(this.parseAddr(sAddr));
if (addr == X86.ADDR_INVALID) { if (addr == X86.ADDR_INVALID) {
this.println("invalid address: " + sAddr); this.println("invalid address: " + sAddr);
return; return;
@ -2518,12 +2537,26 @@ if (DEBUGGER) {
n = 1; n = 1;
} }
this.println("blkid " + (fLinear? "linear " : "physical") + " blkaddr used size type"); this.println("blockid " + (fLinear? "linear " : "physical") + " blockaddr used size type");
this.println("-------- --------- -------- ------ ------ ----"); this.println("-------- --------- ---------- ------ ------ ----");
var typePrev = -1, cPrev = 0; var typePrev = -1, cPrev = 0;
while (n--) { while (n--) {
var block = aBlocks[i]; 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 (block.type == typePrev) {
if (!cPrev++) this.println("..."); if (!cPrev++) this.println("...");
} else { } else {
@ -2535,11 +2568,12 @@ if (DEBUGGER) {
sType += " -> " + Memory.TYPE.NAMES[block.type]; sType += " -> " + Memory.TYPE.NAMES[block.type];
} }
if (block) { 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); 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; if (typePrev != Memory.TYPE.NONE && typePrev != Memory.TYPE.UNPAGED) typePrev = -1;
cPrev = 0; cPrev = 0;
} }
addr += this.cpu.nBlockSize;
i++; i++;
} }
}; };
@ -2581,7 +2615,7 @@ if (DEBUGGER) {
var sInfo = "no information"; var sInfo = "no information";
if (BACKTRACK) { if (BACKTRACK) {
var sAddr = asArgs[0]; var sAddr = asArgs[0];
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR.CODE, true, false); var dbgAddr = this.parseAddr(sAddr, true, true, false);
if (dbgAddr) { if (dbgAddr) {
var addr = this.getAddr(dbgAddr); var addr = this.getAddr(dbgAddr);
sInfo = '%' + str.toHex(addr) + ": " + (this.bus.getSymbol(addr, true) || sInfo); sInfo = '%' + str.toHex(addr) + ": " + (this.bus.getSymbol(addr, true) || sInfo);
@ -2604,6 +2638,75 @@ if (DEBUGGER) {
return sInfo; return sInfo;
}; };
/**
* 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;
};
/**
* 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;
}
/*
* Here begins the code that is remarkably similar to mapPageBlock(), with fSuppress set.
*/
var bus = this.bus;
var offPDE = (addr & X86.LADDR.PDE.MASK) >>> X86.LADDR.PDE.SHIFT;
var addrPDE = this.cpu.regCR3 + offPDE;
var blockPDE = bus.aMemBlocks[(addrPDE & bus.nBusMask) >>> bus.nBlockShift];
var lPDE = blockPDE.readLong(offPDE);
var offPTE = (addr & X86.LADDR.PTE.MASK) >>> X86.LADDR.PTE.SHIFT;
var addrPTE = (lPDE & X86.PTE.FRAME) + offPTE;
var blockPTE = bus.aMemBlocks[(addrPTE & bus.nBusMask) >>> bus.nBlockShift];
var lPTE = blockPTE.readLong(offPTE);
var addrPhys = (lPTE & X86.PTE.FRAME) + (addr & X86.LADDR.OFFSET);
var blockPhys = bus.aMemBlocks[(addrPhys & bus.nBusMask) >>> bus.nBlockShift];
/*
* And here ends the code that is remarkably similar to mapPageBlock(), with fSuppress set.
*/
this.println("linear PDE addr PDE PTE addr PTE physical" );
this.println("--------- ---------- -------- ---------- -------- ----------");
var s = '%' + str.toHex(addr);
s += " %%" + this.getPageEntry(addrPDE, lPDE);
s += " %%" + this.getPageEntry(addrPTE, lPTE, true);
s += " %%" + str.toHex(addrPhys);
this.println(s);
};
/* /*
* Table of system (non-segment) descriptors, including indicators of which ones are gates. * Table of system (non-segment) descriptors, including indicators of which ones are gates.
*/ */
@ -2645,7 +2748,7 @@ if (DEBUGGER) {
return; return;
} }
var seg = this.getSegment(sel, Debugger.ADDR.PROT); 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)); this.println("dumpSel(" + str.toHexWord(seg? seg.sel : sel) + "): %" + str.toHex(seg? seg.addrDesc : null, this.cchAddr));
if (!seg) return; if (!seg) return;
@ -2875,7 +2978,7 @@ if (DEBUGGER) {
this.println("invalid task selector: " + sSel); this.println("invalid task selector: " + sSel);
return; return;
} }
seg = this.getSegment(sel, Debugger.ADDR.PROT); seg = this.getSegment(sel, Debugger.ADDRTYPE.PROT);
} }
this.println("dumpTSS(" + str.toHexWord(seg? seg.sel : sel) + "): %" + str.toHex(seg? seg.base : null, this.cchAddr)); this.println("dumpTSS(" + str.toHexWord(seg? seg.sel : sel) + "): %" + str.toHex(seg? seg.base : null, this.cchAddr));
@ -2889,9 +2992,9 @@ if (DEBUGGER) {
for (var sField in aTSSFields) { for (var sField in aTSSFields) {
off = aTSSFields[sField]; off = aTSSFields[sField];
addr = seg.base + off; addr = seg.base + off;
v = this.cpu.probeAddr(addr) | (this.cpu.probeAddr(addr + 1) << 8); v = this.cpu.probeAddr(addr, 2);
if (type == X86.DESC.ACC.TYPE.TSS386) { if (type == X86.DESC.ACC.TYPE.TSS386) {
v |= (this.cpu.probeAddr(addr + 2) << 16) | (this.cpu.probeAddr(addr + 3) << 24); v |= this.cpu.probeAddr(addr + 2, 2) << 16;
} }
if (sDump) sDump += '\n'; if (sDump) sDump += '\n';
sDump += str.toHexWord(off) + ' ' + str.pad(sField + ':', 11) + str.toHex(v, cch); sDump += str.toHexWord(off) + ' ' + str.pad(sField + ':', 11) + str.toHex(v, cch);
@ -2904,7 +3007,7 @@ if (DEBUGGER) {
*/ */
while (off < seg.offMax && iPort < 0x3ff) { while (off < seg.offMax && iPort < 0x3ff) {
addr = seg.base + off; addr = seg.base + off;
v = this.cpu.probeAddr(addr) | (this.cpu.probeAddr(addr + 1) << 8); v = this.cpu.probeAddr(addr, 2);
sDump += "\n" + str.toHexWord(off) + " ports " + str.toHexWord(iPort) + '-' + str.toHexWord(iPort+15) + ": " + str.toBinBytes(v, 2); sDump += "\n" + str.toHexWord(off) + " ports " + str.toHexWord(iPort) + '-' + str.toHexWord(iPort+15) + ": " + str.toBinBytes(v, 2);
iPort += 16; iPort += 16;
off += 2; off += 2;
@ -3219,16 +3322,16 @@ if (DEBUGGER) {
{ {
/* /*
* Replace any references first; this means that register references inside the reference * Replace any references first; this means that register references inside the reference
* do NOT need to be prefixed with '%'. * do NOT need to be prefixed with '@'.
*/ */
s = this.parseReference(s); s = this.parseReference(s);
/* /*
* Replace every %XX (or %XXX), where XX (or XXX) is a register, with the register's value. * Replace every @XX (or @XXX), where XX (or XXX) is a register, with the register's value.
*/ */
var i = 0; var i = 0;
var b, sChar, sAddr, dbgAddr, sReplace; var b, sChar, sAddr, dbgAddr, sReplace;
while ((i = s.indexOf('%', i)) >= 0) { while ((i = s.indexOf('@', i)) >= 0) {
var iReg = this.getRegIndex(s, i + 1); var iReg = this.getRegIndex(s, i + 1);
if (iReg >= 0) { if (iReg >= 0) {
s = s.substr(0, i) + this.getRegString(iReg) + s.substr(i + 1 + Debugger.REGS[iReg].length); s = s.substr(0, i) + this.getRegString(iReg) + s.substr(i + 1 + Debugger.REGS[iReg].length);
@ -3290,7 +3393,7 @@ if (DEBUGGER) {
Debugger.prototype.message = function(sMessage, fAddress) Debugger.prototype.message = function(sMessage, fAddress)
{ {
if (fAddress) { if (fAddress) {
sMessage += " @" + this.hexOffset(this.cpu.getIP(), this.cpu.getCS()) + " (%" + str.toHex(this.cpu.regLIP) + ")"; sMessage += " at " + this.hexOffset(this.cpu.getIP(), this.cpu.getCS()) + " (%" + str.toHex(this.cpu.regLIP) + ")";
} }
if (this.sMessagePrev && sMessage == this.sMessagePrev) return; if (this.sMessagePrev && sMessage == this.sMessagePrev) return;
@ -3736,6 +3839,7 @@ if (DEBUGGER) {
state.set(0, this.packAddr(this.dbgAddrNextCode)); state.set(0, this.packAddr(this.dbgAddrNextCode));
state.set(1, this.packAddr(this.dbgAddrAssemble)); state.set(1, this.packAddr(this.dbgAddrAssemble));
state.set(2, [this.aPrevCmds, this.fAssemble, this.bitsMessage]); state.set(2, [this.aPrevCmds, this.fAssemble, this.bitsMessage]);
state.set(3, this.aSymbolTable);
return state.data(); return state.data();
}; };
@ -3759,6 +3863,7 @@ if (DEBUGGER) {
this.fAssemble = data[i][1]; 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 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; return true;
}; };
@ -4000,23 +4105,19 @@ if (DEBUGGER) {
*/ */
Debugger.prototype.clearBreakpoints = function() Debugger.prototype.clearBreakpoints = function()
{ {
var i; var i, dbgAddr;
this.aBreakExec = ["bp"]; this.aBreakExec = ["bp"];
/*
* TODO: Each read breakpoint needs to keep track of whether it's linear or physical.
*/
if (this.aBreakRead !== undefined) { if (this.aBreakRead !== undefined) {
for (i = 1; i < this.aBreakRead.length; i++) { for (i = 1; i < this.aBreakRead.length; i++) {
this.cpu.removeMemBreak(this.getAddr(this.aBreakRead[i]), false, true); dbgAddr = this.aBreakRead[i];
this.cpu.removeMemBreak(this.getAddr(dbgAddr), false, dbgAddr.type != Debugger.ADDRTYPE.PHYSICAL);
} }
} }
this.aBreakRead = ["br"]; this.aBreakRead = ["br"];
/*
* TODO: Each write breakpoint needs to keep track of whether it's linear or physical.
*/
if (this.aBreakWrite !== undefined) { if (this.aBreakWrite !== undefined) {
for (i = 1; i < this.aBreakWrite.length; i++) { for (i = 1; i < this.aBreakWrite.length; i++) {
this.cpu.removeMemBreak(this.getAddr(this.aBreakWrite[i]), true, true); dbgAddr = this.aBreakWrite[i];
this.cpu.removeMemBreak(this.getAddr(dbgAddr), true, dbgAddr.type != Debugger.ADDRTYPE.PHYSICAL);
} }
} }
this.aBreakWrite = ["bw"]; this.aBreakWrite = ["bw"];
@ -4106,10 +4207,7 @@ if (DEBUGGER) {
this.println("invalid address: " + this.hexAddr(dbgAddr)); this.println("invalid address: " + this.hexAddr(dbgAddr));
fSuccess = false; fSuccess = false;
} else { } else {
/* this.cpu.addMemBreak(addr, aBreak == this.aBreakWrite, dbgAddr.type != Debugger.ADDRTYPE.PHYSICAL);
* TODO: Add some UI that allows a physical address (fLinear is currently hard-coded to true)
*/
this.cpu.addMemBreak(addr, aBreak == this.aBreakWrite, true);
/* /*
* Force memory breakpoints to use their linear address, by zapping the selector. * Force memory breakpoints to use their linear address, by zapping the selector.
*/ */
@ -4168,10 +4266,7 @@ if (DEBUGGER) {
} }
aBreak.splice(i, 1); aBreak.splice(i, 1);
if (aBreak != this.aBreakExec) { if (aBreak != this.aBreakExec) {
/* this.cpu.removeMemBreak(addr, aBreak == this.aBreakWrite, dbgAddrBreak.type != Debugger.ADDRTYPE.PHYSICAL);
* TODO: Add some UI that allows a physical address (fLinear is currently hard-coded to true)
*/
this.cpu.removeMemBreak(addr, aBreak == this.aBreakWrite, true);
} }
this.historyInit(); this.historyInit();
break; break;
@ -4952,7 +5047,7 @@ if (DEBUGGER) {
Debugger.prototype.getRegDump = function(fProt) Debugger.prototype.getRegDump = function(fProt)
{ {
var s; var s;
if (fProt === undefined) fProt = this.getCurrentMode(); if (fProt === undefined) fProt = this.getCPUMode();
s = this.getRegOutput(Debugger.REG_AX) + s = this.getRegOutput(Debugger.REG_AX) +
this.getRegOutput(Debugger.REG_BX) + this.getRegOutput(Debugger.REG_BX) +
@ -5689,7 +5784,7 @@ if (DEBUGGER) {
*/ */
Debugger.prototype.doAssemble = function(asArgs) Debugger.prototype.doAssemble = function(asArgs)
{ {
var dbgAddr = this.parseAddr(asArgs[1], Debugger.ADDR.CODE); var dbgAddr = this.parseAddr(asArgs[1], true);
if (!dbgAddr) return; if (!dbgAddr) return;
this.dbgAddrAssemble = dbgAddr; this.dbgAddrAssemble = dbgAddr;
@ -5768,7 +5863,7 @@ if (DEBUGGER) {
} }
var dbgAddr = {}; var dbgAddr = {};
if (sAddr != '*') { if (sAddr != '*') {
dbgAddr = this.parseAddr(sAddr, Debugger.ADDR.CODE, true); dbgAddr = this.parseAddr(sAddr, true, true);
if (!dbgAddr) return; if (!dbgAddr) return;
} }
@ -5926,16 +6021,32 @@ if (DEBUGGER) {
} }
/* /*
* Transform a "ds" command into a "d desc" command (simply as shorthand) * 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") { if (sCmd == "ds" || sCmd == "dg" || sCmd == "dl") {
sCmd = 'd'; sCmd = "d";
asArgs = [sCmd, "desc", sAddr]; asArgs = [sCmd, "desc", sAddr];
} }
if (sCmd == 'd') { /*
* 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 (alternatively, register a dumper for "disk") * Transform a "d disk" command into a "l json" command (TODO: Register a dumper for "disk" instead?)
*/ */
if (sAddr == "disk") { if (sAddr == "disk") {
asArgs[0] = "l"; asArgs[0] = "l";
@ -5973,7 +6084,7 @@ if (DEBUGGER) {
return; return;
} }
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR.DATA); var dbgAddr = this.parseAddr(sAddr);
if (!dbgAddr || dbgAddr.sel == null && dbgAddr.addr == null) return; if (!dbgAddr || dbgAddr.sel == null && dbgAddr.addr == null) return;
var cb = 0; // 0 is not a default; it triggers the appropriate default below var cb = 0; // 0 is not a default; it triggers the appropriate default below
@ -6038,7 +6149,7 @@ if (DEBUGGER) {
return; return;
} }
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR.DATA); var dbgAddr = this.parseAddr(sAddr);
if (!dbgAddr) return; if (!dbgAddr) return;
for (var i = 2; i < asArgs.length; i++) { for (var i = 2; i < asArgs.length; i++) {
@ -6298,7 +6409,7 @@ if (DEBUGGER) {
{ {
var sSymbol = null; var sSymbol = null;
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR.CODE); var dbgAddr = this.parseAddr(sAddr, true);
if (dbgAddr) { if (dbgAddr) {
var addr = this.getAddr(dbgAddr); var addr = this.getAddr(dbgAddr);
@ -6364,7 +6475,7 @@ if (DEBUGGER) {
var fJSON = (asArgs[1] == "json"); var fJSON = (asArgs[1] == "json");
var iDrive, iSector = 0, nSectors = 0; var iDrive, iSector = 0, nSectors = 0;
var dbgAddr = (fJSON? {} : this.parseAddr(asArgs[1], Debugger.ADDR.DATA)); var dbgAddr = (fJSON? {} : this.parseAddr(asArgs[1]));
if (!dbgAddr) return; if (!dbgAddr) return;
iDrive = this.parseValue(asArgs[2], "drive #"); iDrive = this.parseValue(asArgs[2], "drive #");
@ -6922,7 +7033,7 @@ if (DEBUGGER) {
Debugger.prototype.doRun = function(sAddr, sOptions, fQuiet) Debugger.prototype.doRun = function(sAddr, sOptions, fQuiet)
{ {
if (sAddr !== undefined) { if (sAddr !== undefined) {
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR.CODE); var dbgAddr = this.parseAddr(sAddr, true);
if (!dbgAddr) return; if (!dbgAddr) return;
this.parseAddrOptions(dbgAddr, sOptions); this.parseAddrOptions(dbgAddr, sOptions);
this.setTempBreakpoint(dbgAddr); this.setTempBreakpoint(dbgAddr);
@ -7245,7 +7356,7 @@ if (DEBUGGER) {
*/ */
Debugger.prototype.doUnassemble = function(sAddr, sAddrEnd, n) Debugger.prototype.doUnassemble = function(sAddr, sAddrEnd, n)
{ {
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR.CODE); var dbgAddr = this.parseAddr(sAddr, true);
if (!dbgAddr) return; if (!dbgAddr) return;
if (n === undefined) n = 1; if (n === undefined) n = 1;
@ -7253,7 +7364,7 @@ if (DEBUGGER) {
var cb = 0x100; var cb = 0x100;
if (sAddrEnd !== undefined) { if (sAddrEnd !== undefined) {
var dbgAddrEnd = this.parseAddr(sAddrEnd, Debugger.ADDR.CODE); var dbgAddrEnd = this.parseAddr(sAddrEnd, true);
if (!dbgAddrEnd || dbgAddrEnd.off < dbgAddr.off) return; if (!dbgAddrEnd || dbgAddrEnd.off < dbgAddr.off) return;
cb = dbgAddrEnd.off - dbgAddr.off; cb = dbgAddrEnd.off - dbgAddr.off;

View file

@ -73,11 +73,13 @@ var Interrupts = {
KRNLVARS: 0x005A, // DS_Kernel_Vars KRNLVARS: 0x005A, // DS_Kernel_Vars
RELSEG: 0x005C, // DS_ReleaseSeg (same as DS_FreeSeg but "restores any breakpoints first") RELSEG: 0x005C, // DS_ReleaseSeg (same as DS_FreeSeg but "restores any breakpoints first")
LOADHIGH: 0x005D, // D386_LoadCodeDataHigh LOADHIGH: 0x005D, // D386_LoadCodeDataHigh
EXITCALL: 0x0062, // DS_EXITCALL
LOADDLL: 0x0064, // DS_LOADDLL LOADDLL: 0x0064, // DS_LOADDLL
DELMODULE: 0x0065, // DS_DELMODULE DELMODULE: 0x0065, // DS_DELMODULE
UNKNOWN66: 0x0066, // Unknown (but I suspect it isn't good)
UNKNOWN67: 0x0067, // Unknown (but I suspect it isn't good)
REGDOTCMD: 0x0070, // DS_RegisterDotCommand REGDOTCMD: 0x0070, // DS_RegisterDotCommand
CHECKFAULT: 0x007F, // DS_CheckFault (BX==fault #, CX==fault type; return AX=0 to handle fault normally) CHECKFAULT: 0x007F, // DS_CheckFault (BX==fault #, CX==FAULTTYPE bits; return AX=0 to handle fault normally, 1 to issue TRAPFAULT)
TRAPFAULT: 0x0083, // DS_TrapFault (BX==fault #, CX==faulting CS, EDX==faulting EIP, ESI==fault error, EDI==fault flags)
FAULTTYPE: { FAULTTYPE: {
V86: 0x0001, V86: 0x0001,
PM: 0x0002, PM: 0x0002,
@ -85,6 +87,7 @@ var Interrupts = {
FIRST: 0x0008, FIRST: 0x0008,
LAST: 0x0010 LAST: 0x0010
}, },
TRAPFAULT: 0x0083, // DS_TrapFault (BX==fault #, CX==faulting CS, EDX==faulting EIP, ESI==fault error, EDI==fault flags)
GETSYMBOL: 0x008D, // DS_GetSymbol (DS:ESI->symbol; return AX=0 if success, 1 if not found, 2 if memory not loaded yet) GETSYMBOL: 0x008D, // DS_GetSymbol (DS:ESI->symbol; return AX=0 if success, 1 if not found, 2 if memory not loaded yet)
LOADSEG32: 0x0150, // DS_LoadSeg_32 (SI==0 if code, 1 if data; DX:EBX->D386_Device_Params) LOADSEG32: 0x0150, // DS_LoadSeg_32 (SI==0 if code, 1 if data; DX:EBX->D386_Device_Params)
FREESEG32: 0x0152, // DS_FreeSeg_32 (BX==segment, DX:EDI->module name) FREESEG32: 0x0152, // DS_FreeSeg_32 (BX==segment, DX:EDI->module name)
@ -110,7 +113,7 @@ if (DEBUGGER) {
* *
* Replacements occur in the following order: * Replacements occur in the following order:
* *
* Replace every %XX (or %XXX), where XX (or XXX) is a register, with the register's value. * Replace every @XX (or @XXX), where XX (or XXX) is a register, with the register's value.
* Replace every #XX, where XX is a hex byte value, with the corresponding ASCII character (if printable). * Replace every #XX, where XX is a hex byte value, with the corresponding ASCII character (if printable).
* Replace every $XXXX:XXXX, where XXXX:XXXX is a segmented address, with the zero-terminated string at that address. * Replace every $XXXX:XXXX, where XXXX:XXXX is a segmented address, with the zero-terminated string at that address.
* Replace every ^XXXX:XXXX, where XXXX:XXXX is a segmented address, with the FCB filename stored at that address. * Replace every ^XXXX:XXXX, where XXXX:XXXX is a segmented address, with the FCB filename stored at that address.
@ -120,15 +123,15 @@ if (DEBUGGER) {
Interrupts.FUNCS[Interrupts.DISK] = { Interrupts.FUNCS[Interrupts.DISK] = {
0x00: "disk reset", 0x00: "disk reset",
0x01: "get status", 0x01: "get status",
0x02: "read drive %DL (%CH:%DH:%CL,%AL) into %ES:%BX", 0x02: "read drive @DL (@CH:@DH:@CL,@AL) into @ES:@BX",
0x03: "write drive %DL (%CH:%DH:%CL,%AL) from %ES:%BX", 0x03: "write drive @DL (@CH:@DH:@CL,@AL) from @ES:@BX",
0x04: "verify drive %DL (%CH:%DH:%CL,%AL)", 0x04: "verify drive @DL (@CH:@DH:@CL,@AL)",
0x05: "format drive %DL using %ES:%BX", 0x05: "format drive @DL using @ES:@BX",
0x08: "read drive %DL parameters into %ES:%DI", 0x08: "read drive @DL parameters into @ES:@DI",
0x15: "get drive %DL DASD type", 0x15: "get drive @DL DASD type",
0x16: "get drive %DL change line status", 0x16: "get drive @DL change line status",
0x17: "set drive %DL DASD type", 0x17: "set drive @DL DASD type",
0x18: "set drive %DL media type" 0x18: "set drive @DL media type"
/* /*
* Here's an additional function reference, previously in the HDC component, but moved here * Here's an additional function reference, previously in the HDC component, but moved here
* because our components are hardware emulations, not BIOS emulations, so this information is * because our components are hardware emulations, not BIOS emulations, so this information is
@ -161,11 +164,11 @@ if (DEBUGGER) {
0x80: "open device", 0x80: "open device",
0x81: "close device", 0x81: "close device",
0x82: "program termination", 0x82: "program termination",
0x83: "wait %CX:%DXus for event", 0x83: "wait @CX:@DXus for event",
0x84: "joystick support", 0x84: "joystick support",
0x85: "SYSREQ pressed", 0x85: "SYSREQ pressed",
0x86: "wait %CX:%DXus", 0x86: "wait @CX:@DXus",
0x87: "move block (%CX words)", 0x87: "move block (@CX words)",
0x88: "get extended memory size", 0x88: "get extended memory size",
0x89: "processor to virtual mode", 0x89: "processor to virtual mode",
0x90: "device busy loop", 0x90: "device busy loop",
@ -174,97 +177,97 @@ if (DEBUGGER) {
Interrupts.FUNCS[Interrupts.DOS] = { Interrupts.FUNCS[Interrupts.DOS] = {
0x00: "terminate program", 0x00: "terminate program",
0x01: "read character (AL) from stdin with echo", 0x01: "read character (AL) from stdin with echo",
0x02: "write character #%DL to stdout", 0x02: "write character #@DL to stdout",
0x03: "read character (AL) from stdaux", // eg, COM1 0x03: "read character (AL) from stdaux", // eg, COM1
0x04: "write character #%DL to stdaux", // eg, COM1 0x04: "write character #@DL to stdaux", // eg, COM1
0x05: "write character #%DL to stdprn", // eg, LPT1 0x05: "write character #@DL to stdprn", // eg, LPT1
0x06: "direct console output (input if %DL=FF)", 0x06: "direct console output (input if @DL=FF)",
0x07: "direct console input without echo", 0x07: "direct console input without echo",
0x08: "read character (AL) from stdin without echo", 0x08: "read character (AL) from stdin without echo",
0x09: "write string $%DS:%DX to stdout", 0x09: "write string $@DS:@DX to stdout",
0x0A: "buffered input (DS:DX)", // byte 0 is maximum chars, byte 1 is number of previous characters, byte 2 is number of characters read 0x0A: "buffered input (DS:DX)", // byte 0 is maximum chars, byte 1 is number of previous characters, byte 2 is number of characters read
0x0B: "get stdin status", 0x0B: "get stdin status",
0x0C: "flush buffer and read stdin", // AL is a function # (0x01, 0x06, 0x07, 0x08, or 0x0A) 0x0C: "flush buffer and read stdin", // AL is a function # (0x01, 0x06, 0x07, 0x08, or 0x0A)
0x0D: "disk reset", 0x0D: "disk reset",
0x0E: "select default drive %DL", // returns # of available drives in AL 0x0E: "select default drive @DL", // returns # of available drives in AL
0x0F: "open file using FCB ^%DS:%DX", // DS:DX -> unopened File Control Block 0x0F: "open file using FCB ^@DS:@DX", // DS:DX -> unopened File Control Block
0x10: "close file using FCB ^%DS:%DX", 0x10: "close file using FCB ^@DS:@DX",
0x11: "find first matching file using FCB ^%DS:%DX", 0x11: "find first matching file using FCB ^@DS:@DX",
0x12: "find next matching file using FCB ^%DS:%DX", 0x12: "find next matching file using FCB ^@DS:@DX",
0x13: "delete file using FCB ^%DS:%DX", 0x13: "delete file using FCB ^@DS:@DX",
0x14: "sequential read from file using FCB ^%DS:%DX", 0x14: "sequential read from file using FCB ^@DS:@DX",
0x15: "sequential write to file using FCB ^%DS:%DX", 0x15: "sequential write to file using FCB ^@DS:@DX",
0x16: "create or truncate file using FCB ^%DS:%DX", 0x16: "create or truncate file using FCB ^@DS:@DX",
0x17: "rename file using FCB ^%DS:%DX", 0x17: "rename file using FCB ^@DS:@DX",
0x19: "get current default drive (AL)", 0x19: "get current default drive (AL)",
0x1A: "set disk transfer area (DTA=%DS:%DX)", 0x1A: "set disk transfer area (DTA=@DS:@DX)",
0x1B: "get allocation information for default drive", 0x1B: "get allocation information for default drive",
0x1C: "get allocation information for specific drive %DL", 0x1C: "get allocation information for specific drive @DL",
0x1F: "get drive parameter block for default drive", 0x1F: "get drive parameter block for default drive",
0x21: "read random record from file using FCB ^%DS:%DX", 0x21: "read random record from file using FCB ^@DS:@DX",
0x22: "write random record to file using FCB ^%DS:%DX", 0x22: "write random record to file using FCB ^@DS:@DX",
0x23: "get file size using FCB ^%DS:%DX", 0x23: "get file size using FCB ^@DS:@DX",
0x24: "set random record number for FCB ^%DS:%DX", 0x24: "set random record number for FCB ^@DS:@DX",
0x25: "set address %DS:%DX of interrupt vector %AL", 0x25: "set address @DS:@DX of interrupt vector @AL",
0x26: "create new PSP at segment %DX", 0x26: "create new PSP at segment @DX",
0x27: "random block read from file using FCB ^%DS:%DX", 0x27: "random block read from file using FCB ^@DS:@DX",
0x28: "random block write to file using FCB ^%DS:%DX", 0x28: "random block write to file using FCB ^@DS:@DX",
0x29: "parse filename $%DS:%SI into FCB %ES:%DI using %AL", 0x29: "parse filename $@DS:@SI into FCB @ES:@DI using @AL",
0x2A: "get system date (year=CX, mon=DH, day=DL)", 0x2A: "get system date (year=CX, mon=DH, day=DL)",
0x2B: "set system date (year=%CX, mon=%DH, day=%DL)", 0x2B: "set system date (year=@CX, mon=@DH, day=@DL)",
0x2C: "get system time (hour=CH, min=CL, sec=DH, 100ths=DL)", 0x2C: "get system time (hour=CH, min=CL, sec=DH, 100ths=DL)",
0x2D: "set system time (hour=%CH, min=%CL, sec=%DH, 100ths=%DL)", 0x2D: "set system time (hour=@CH, min=@CL, sec=@DH, 100ths=@DL)",
0x2E: "set verify flag %AL", 0x2E: "set verify flag @AL",
0x2F: "get disk transfer area (DTA=ES:BX)", // DOS 2.00+ 0x2F: "get disk transfer area (DTA=ES:BX)", // DOS 2.00+
0x30: "get DOS version (AL=major, AH=minor)", 0x30: "get DOS version (AL=major, AH=minor)",
0x31: "terminate and stay resident", 0x31: "terminate and stay resident",
0x32: "get drive parameter block (DPB=DS:BX) for drive %DL", 0x32: "get drive parameter block (DPB=DS:BX) for drive @DL",
0x33: "extended break check", 0x33: "extended break check",
0x34: "get address (ES:BX) of InDOS flag", 0x34: "get address (ES:BX) of InDOS flag",
0x35: "get address (ES:BX) of interrupt vector %AL", 0x35: "get address (ES:BX) of interrupt vector @AL",
0x36: "get free disk space of drive %DL", 0x36: "get free disk space of drive @DL",
0x37: "get(0)/set(1) switch character %DL (%AL)", 0x37: "get(0)/set(1) switch character @DL (@AL)",
0x38: "get country-specific information", 0x38: "get country-specific information",
0x39: "create subdirectory $%DS:%DX", 0x39: "create subdirectory $@DS:@DX",
0x3A: "remove subdirectory $%DS:%DX", 0x3A: "remove subdirectory $@DS:@DX",
0x3B: "set current directory $%DS:%DX", 0x3B: "set current directory $@DS:@DX",
0x3C: "create or truncate file $%DS:%DX with attributes %CX", 0x3C: "create or truncate file $@DS:@DX with attributes @CX",
0x3D: "open file $%DS:%DX with mode %AL", 0x3D: "open file $@DS:@DX with mode @AL",
0x3E: "close file %BX", 0x3E: "close file @BX",
0x3F: "read %CX bytes from file %BX into buffer %DS:%DX", 0x3F: "read @CX bytes from file @BX into buffer @DS:@DX",
0x40: "write %CX bytes to file %BX from buffer %DS:%DX", 0x40: "write @CX bytes to file @BX from buffer @DS:@DX",
0x41: "delete file $%DS:%DX", 0x41: "delete file $@DS:@DX",
0x42: "set position %CX:%DX of file %BX relative to %AL", 0x42: "set position @CX:@DX of file @BX relative to @AL",
0x43: "get(0)/set(1) attributes %CX of file $%DS:%DX (%AL)", 0x43: "get(0)/set(1) attributes @CX of file $@DS:@DX (@AL)",
0x44: "get device information (IOCTL)", 0x44: "get device information (IOCTL)",
0x45: "duplicate file handle %BX", 0x45: "duplicate file handle @BX",
0x46: "force file handle %CX to duplicate file handle %BX", 0x46: "force file handle @CX to duplicate file handle @BX",
0x47: "get current directory (DS:SI) for drive %DL", 0x47: "get current directory (DS:SI) for drive @DL",
0x48: "allocate memory segment with %BX paragraphs", 0x48: "allocate memory segment with @BX paragraphs",
0x49: "free memory segment %ES", 0x49: "free memory segment @ES",
0x4A: "resize memory segment %ES to %BX paragraphs", 0x4A: "resize memory segment @ES to @BX paragraphs",
0x4B: "load program $%DS:%DX using parameter block %ES:%BX", 0x4B: "load program $@DS:@DX using parameter block @ES:@BX",
0x4C: "terminate with return code %AL", 0x4C: "terminate with return code @AL",
0x4D: "get return code (AL)", 0x4D: "get return code (AL)",
0x4E: "find first matching file $%DS:%DX with attributes %CX", 0x4E: "find first matching file $@DS:@DX with attributes @CX",
0x4F: "find next matching file", 0x4F: "find next matching file",
0x50: "set current PSP %BX", 0x50: "set current PSP @BX",
0x51: "get current PSP (bx)", 0x51: "get current PSP (bx)",
0x52: "get system variables (ES:BX)", 0x52: "get system variables (ES:BX)",
0x53: "translate BPB %DS:%SI to DPB (ES:BP)", 0x53: "translate BPB @DS:@SI to DPB (ES:BP)",
0x54: "get verify flag (AL)", 0x54: "get verify flag (AL)",
0x55: "create child PSP at segment %DX", 0x55: "create child PSP at segment @DX",
0x56: "rename file $%DS:%DX to $%ES:%DI", 0x56: "rename file $@DS:@DX to $@ES:@DI",
0x57: "get(0)/set(1) file %BX date %DX and time %CX (%AL)", 0x57: "get(0)/set(1) file @BX date @DX and time @CX (@AL)",
0x58: "get(0)/set(1) memory allocation strategy (%AL)", // DOS 2.11+ 0x58: "get(0)/set(1) memory allocation strategy (@AL)", // DOS 2.11+
0x59: "get extended error information", // DOS 3.00+ 0x59: "get extended error information", // DOS 3.00+
0x5A: "create temporary file $%DS:%DX with attributes %CX", // DOS 3.00+ 0x5A: "create temporary file $@DS:@DX with attributes @CX", // DOS 3.00+
0x5B: "create file $%DS:%DX with attributes %CX", // DOS 3.00+ (doesn't truncate existing files like 0x3C) 0x5B: "create file $@DS:@DX with attributes @CX", // DOS 3.00+ (doesn't truncate existing files like 0x3C)
0x5C: "lock(0)/unlock(1) file %BX region %CX:%DX length %SI:%DI (%AL)", // DOS 3.00+ 0x5C: "lock(0)/unlock(1) file @BX region @CX:@DX length @SI:@DI (@AL)", // DOS 3.00+
0x5D: "critical error information (%AL)", // DOS 3.00+ (undocumented) 0x5D: "critical error information (@AL)", // DOS 3.00+ (undocumented)
0x60: "get fully-qualified filename from $%DS:%SI", // DOS 3.00+ (undocumented) 0x60: "get fully-qualified filename from $@DS:@SI", // DOS 3.00+ (undocumented)
0x63: "get lead byte table (%AL)", // DOS 2.25 and 3.20+ 0x63: "get lead byte table (@AL)", // DOS 2.25 and 3.20+
0x6C: "extended open file $%DS:%SI" // DOS 4.00+ 0x6C: "extended open file $@DS:@SI" // DOS 4.00+
}; };
Interrupts.FUNCS[Interrupts.WINDBG.VECTOR] = { Interrupts.FUNCS[Interrupts.WINDBG.VECTOR] = {
0x004F: "check debugger loaded" // WINDBG.IS_LOADED returns WINDBG.LOADED (0xF386) if debugger loaded 0x004F: "check debugger loaded" // WINDBG.IS_LOADED returns WINDBG.LOADED (0xF386) if debugger loaded

View file

@ -619,7 +619,7 @@ Memory.prototype = {
* @return {number} * @return {number}
*/ */
readNone: function readNone(off, addr) { readNone: function readNone(off, addr) {
if (DEBUGGER && this.dbg && this.dbg.messageEnabled(Messages.MEM) /* && !off */) { if (DEBUGGER && this.dbg && this.dbg.messageEnabled(Messages.CPU | Messages.MEM) /* && !off */) {
this.dbg.message("attempt to read invalid block %" + str.toHex(this.addr), true); this.dbg.message("attempt to read invalid block %" + str.toHex(this.addr), true);
} }
return 0xff; return 0xff;
@ -633,7 +633,7 @@ Memory.prototype = {
* @param {number} addr * @param {number} addr
*/ */
writeNone: function writeNone(off, v, addr) { writeNone: function writeNone(off, v, addr) {
if (DEBUGGER && this.dbg && this.dbg.messageEnabled(Messages.MEM) /* && !off */) { if (DEBUGGER && this.dbg && this.dbg.messageEnabled(Messages.CPU | Messages.MEM) /* && !off */) {
this.dbg.message("attempt to write " + str.toHexWord(v) + " to invalid block %" + str.toHex(this.addr), true); this.dbg.message("attempt to write " + str.toHexWord(v) + " to invalid block %" + str.toHex(this.addr), true);
} }
}, },

View file

@ -686,6 +686,8 @@ X86CPU.prototype.setAddressMask = function(nBusMask)
* NOTE: addMemBreak() could be merged with addMemCheck(), but the new merged interface would * NOTE: addMemBreak() could be merged with addMemCheck(), but the new merged interface would
* have to provide one additional parameter indicating whether the Debugger or the CPU is the client. * have to provide one additional parameter indicating whether the Debugger or the CPU is the client.
* *
* For now, this is simply a DEBUGGER-only interface.
*
* @this {X86CPU} * @this {X86CPU}
* @param {number} addr * @param {number} addr
* @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint * @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint
@ -706,6 +708,8 @@ X86CPU.prototype.addMemBreak = function(addr, fWrite, fLinear)
* NOTE: removeMemBreak() could be merged with removeMemCheck(), but the new merged interface would * NOTE: removeMemBreak() could be merged with removeMemCheck(), but the new merged interface would
* have to provide one additional parameter indicating whether the Debugger or the CPU is the client. * have to provide one additional parameter indicating whether the Debugger or the CPU is the client.
* *
* For now, this is simply a DEBUGGER-only interface.
*
* @this {X86CPU} * @this {X86CPU}
* @param {number} addr * @param {number} addr
* @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint * @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint
@ -811,6 +815,12 @@ X86CPU.prototype.enablePageBlocks = function()
*/ */
this.memEmpty = new Memory(); this.memEmpty = new Memory();
} else { } else {
/*
* Our equivalent of a TLB flush. NOTE: We do not attempt to simulate an actual TLB; our
* aMemBlocks array will "cache" as many pages (ie, allow as many PAGED block) as there are
* entries in the array. I'm assuming we won't run into any system software that relies on
* a constrained TLB -- at least not from the 80386 era, which is all we're emulating.
*/
for (var i = 0; i < this.aBlocksPaged.length; i++) { for (var i = 0; i < this.aBlocksPaged.length; i++) {
this.aMemBlocks[this.aBlocksPaged[i]] = this.blockUnpaged; this.aMemBlocks[this.aBlocksPaged[i]] = this.blockUnpaged;
} }
@ -3223,7 +3233,7 @@ X86CPU.prototype.setBinding = function(sHTMLType, sBinding, control)
}; };
/** /**
* probeAddr(addr, size) * probeAddr(addr, size, fLinear)
* *
* Used by the Debugger to probe addresses without risk of triggering a page fault, and by internal * Used by the Debugger to probe addresses without risk of triggering a page fault, and by internal
* functions, like fnFaultMessage(), that also need to avoid triggering faults, since they're not part * functions, like fnFaultMessage(), that also need to avoid triggering faults, since they're not part
@ -3235,15 +3245,15 @@ X86CPU.prototype.setBinding = function(sHTMLType, sBinding, control)
* @this {X86CPU} * @this {X86CPU}
* @param {number} addr is a linear address * @param {number} addr is a linear address
* @param {number} [size] is a length (default is 1) * @param {number} [size] is a length (default is 1)
* @param {boolean} [fLinear] (true for linear probe, false for physical; linear is the default)
* @return {number|null} byte (8-bit) value at that address, or null if invalid * @return {number|null} byte (8-bit) value at that address, or null if invalid
*/ */
X86CPU.prototype.probeAddr = function(addr, size) X86CPU.prototype.probeAddr = function(addr, size, fLinear)
{ {
var block = this.aMemBlocks[(addr & this.nMemMask) >>> this.nBlockShift]; var aBlocks = fLinear === false? this.aBusBlocks : this.aMemBlocks;
if (block) { var block = aBlocks[(addr & this.nMemMask) >>> this.nBlockShift];
if (block.type == Memory.TYPE.UNPAGED) { if (block && block.type == Memory.TYPE.UNPAGED) {
block = this.mapPageBlock(addr, false, true); block = this.mapPageBlock(addr, false, true);
}
} }
if (block) { if (block) {
var off = addr & this.nBlockLimit; var off = addr & this.nBlockLimit;

View file

@ -155,7 +155,7 @@ X86Seg.CALLBREAK_SEL = 0x0001;
* whenever that address is called, and if fn() returns false, then the call is skipped. Otherwise, * whenever that address is called, and if fn() returns false, then the call is skipped. Otherwise,
* the call is performed (ie, the old CS:[E]IP is pushed on the stack, and CS:[E]IP is set to the * the call is performed (ie, the old CS:[E]IP is pushed on the stack, and CS:[E]IP is set to the
* "call break" address. Which is probably a bad idea, so your function should probably always * "call break" address. Which is probably a bad idea, so your function should probably always
* return false. Just sayin'. * return false. Just sayin'. TODO: Should probably just force all "call break" calls to be skipped.
* *
* @this {X86Seg} * @this {X86Seg}
* @param {function()} fn * @param {function()} fn