Debugging improvements, including more support for physical vs. linear addresses
This commit is contained in:
parent
5994e91110
commit
4ef741b7e5
5 changed files with 336 additions and 212 deletions
|
|
@ -56,7 +56,7 @@ if (DEBUGGER) {
|
|||
* 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.ADDR.* values
|
||||
* 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
|
||||
* 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
|
||||
*
|
||||
* along with ONE of:
|
||||
*
|
||||
* CODE or DATA
|
||||
*
|
||||
* REAL and V86 addresses are specifed with a '&' prefix, PROT addresses with a '#' prefix,
|
||||
* REAL and V86 addresses are specified with a '&' prefix, PROT addresses with a '#' prefix,
|
||||
* LINEAR addresses with '%', and PHYSICAL addresses with '%%'.
|
||||
*/
|
||||
Debugger.ADDR = {
|
||||
Debugger.ADDRTYPE = {
|
||||
NONE: 0x00,
|
||||
REAL: 0x01,
|
||||
PROT: 0x02,
|
||||
V86: 0x03,
|
||||
LINEAR: 0x04,
|
||||
PHYSICAL: 0x05,
|
||||
CODE: 0x10,
|
||||
DATA: 0x20
|
||||
PHYSICAL: 0x05
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
@ -1298,7 +1292,7 @@ if (DEBUGGER) {
|
|||
* @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]
|
||||
* @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)
|
||||
{
|
||||
|
|
@ -1306,8 +1300,8 @@ if (DEBUGGER) {
|
|||
var seg = this.getSegment(sel);
|
||||
var len = seg? seg.limit + 1 : 0;
|
||||
var sSection = (fCode? "_CODE" : "_DATA") + str.toHex(nSegment, 2);
|
||||
if (fPrint) {
|
||||
this.println(sModule + ' ' + (fCode? "code" : "data") + '(' + str.toHex(nSegment, 4) + ")=#" + str.toHex(sel, 4) + " len " + str.toHex(len));
|
||||
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);
|
||||
|
|
@ -1320,16 +1314,16 @@ if (DEBUGGER) {
|
|||
*
|
||||
* @this {Debugger}
|
||||
* @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)
|
||||
{
|
||||
var sModuleRemoved = this.removeSymbols(null, sel);
|
||||
if (fPrint) {
|
||||
if (fPrint && this.messageEnabled(Messages.MEM)) {
|
||||
if (sModuleRemoved) {
|
||||
this.println(sModuleRemoved + " #" + str.toHex(sel, 4) + " removed");
|
||||
this.message(sModuleRemoved + " #" + str.toHex(sel, 4) + " removed");
|
||||
} 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}
|
||||
* @param {DbgAddr} dbgAddr (address of D386_Device_Params)
|
||||
* @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)
|
||||
{
|
||||
|
|
@ -1368,11 +1362,11 @@ if (DEBUGGER) {
|
|||
sParent += '!';
|
||||
}
|
||||
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.
|
||||
*/
|
||||
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
|
||||
|
|
@ -1389,17 +1383,17 @@ if (DEBUGGER) {
|
|||
* @this {Debugger}
|
||||
* @param {number} nSegment (logical segment number)
|
||||
* @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)
|
||||
{
|
||||
var sModule = this.getSZ(dbgAddr).toUpperCase();
|
||||
var sModuleRemoved = this.removeSymbols(sModule, nSegment);
|
||||
if (fPrint) {
|
||||
if (fPrint && this.messageEnabled(Messages.MEM)) {
|
||||
if (sModuleRemoved) {
|
||||
this.println(sModule + ' ' + str.toHex(nSegment, 4) + " removed");
|
||||
this.message(sModule + ' ' + str.toHex(nSegment, 4) + " removed");
|
||||
} 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).
|
||||
*
|
||||
* 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, because Windows assumes that the former
|
||||
* is installed whenever it detects the latter.
|
||||
* 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.
|
||||
*
|
||||
* Which is also 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 interrupt handler exists inside the machine.
|
||||
* 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
|
||||
|
|
@ -1478,6 +1472,7 @@ if (DEBUGGER) {
|
|||
*/
|
||||
Debugger.prototype.intWindowsDebugger = function(addr)
|
||||
{
|
||||
var dbgAddr;
|
||||
var cpu = this.cpu;
|
||||
var AX = cpu.regEAX & 0xffff;
|
||||
var BX = cpu.regEBX & 0xffff;
|
||||
|
|
@ -1550,8 +1545,11 @@ if (DEBUGGER) {
|
|||
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
|
||||
*/
|
||||
|
|
@ -1563,19 +1561,25 @@ if (DEBUGGER) {
|
|||
break;
|
||||
|
||||
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;
|
||||
|
||||
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
|
||||
* 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
|
||||
* this notification comes through.
|
||||
* Regardless, whenever we receive this notification, we'll allocate a temporary breakpoint
|
||||
* 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;
|
||||
|
||||
case Interrupts.WINDBG.GETSYMBOL: // 0x008D
|
||||
|
|
@ -1646,15 +1650,15 @@ if (DEBUGGER) {
|
|||
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 (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.
|
||||
* 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) {
|
||||
|
|
@ -1689,9 +1693,13 @@ if (DEBUGGER) {
|
|||
|
||||
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];
|
||||
cpu.regEDI = a[0]; // ES:EDI receives the "call break" address
|
||||
cpu.setES(a[1]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1805,8 +1813,8 @@ if (DEBUGGER) {
|
|||
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;
|
||||
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;
|
||||
}
|
||||
|
|
@ -1922,25 +1930,25 @@ if (DEBUGGER) {
|
|||
};
|
||||
|
||||
/**
|
||||
* getCurrentMode()
|
||||
* getCPUMode()
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @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);
|
||||
};
|
||||
|
||||
/**
|
||||
* getCurrentType()
|
||||
* getAddressType()
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @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}
|
||||
* @param {number|null|undefined} sel
|
||||
* @param {number} [type] (defaults to getCurrentType())
|
||||
* @param {number} [type] (defaults to getAddressType())
|
||||
* @return {X86Seg|null} seg
|
||||
*/
|
||||
Debugger.prototype.getSegment = function(sel, type)
|
||||
{
|
||||
var typeDefault = this.getCurrentType();
|
||||
var typeDefault = this.getAddressType();
|
||||
|
||||
if (!type) type = typeDefault;
|
||||
|
||||
|
|
@ -1976,10 +1984,10 @@ if (DEBUGGER) {
|
|||
* 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.ADDR.PROT || !this.segDebugger) return null;
|
||||
if (this.nSuppressBreaks && type == Debugger.ADDRTYPE.PROT || !this.segDebugger) return null;
|
||||
}
|
||||
var seg = this.segDebugger;
|
||||
if (type != Debugger.ADDR.PROT) {
|
||||
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
|
||||
|
|
@ -2042,7 +2050,7 @@ if (DEBUGGER) {
|
|||
var b = 0xff;
|
||||
var addr = this.getAddr(dbgAddr, false, 1);
|
||||
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);
|
||||
}
|
||||
return b;
|
||||
|
|
@ -2077,7 +2085,7 @@ if (DEBUGGER) {
|
|||
var w = 0xffff;
|
||||
var addr = this.getAddr(dbgAddr, false, 2);
|
||||
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);
|
||||
}
|
||||
return w;
|
||||
|
|
@ -2096,7 +2104,7 @@ if (DEBUGGER) {
|
|||
var l = -1;
|
||||
var addr = this.getAddr(dbgAddr, false, 4);
|
||||
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);
|
||||
}
|
||||
return l;
|
||||
|
|
@ -2166,18 +2174,25 @@ if (DEBUGGER) {
|
|||
*/
|
||||
Debugger.prototype.getAddrPrefix = function(dbgAddr)
|
||||
{
|
||||
var ch = '';
|
||||
var ch;
|
||||
|
||||
switch (dbgAddr.type) {
|
||||
case Debugger.ADDR.REAL:
|
||||
case Debugger.ADDR.V86:
|
||||
case Debugger.ADDRTYPE.REAL:
|
||||
case Debugger.ADDRTYPE.V86:
|
||||
ch = '&';
|
||||
break;
|
||||
case Debugger.ADDR.PROT:
|
||||
case Debugger.ADDRTYPE.PROT:
|
||||
ch = '#';
|
||||
break;
|
||||
case Debugger.ADDR.LINEAR:
|
||||
case Debugger.ADDRTYPE.LINEAR:
|
||||
ch = '%';
|
||||
break;
|
||||
case Debugger.ADDRTYPE.PHYSICAL:
|
||||
ch = '%%';
|
||||
break;
|
||||
default:
|
||||
ch = dbgAddr.sel? '' : '%';
|
||||
break;
|
||||
}
|
||||
return ch;
|
||||
};
|
||||
|
|
@ -2202,7 +2217,7 @@ if (DEBUGGER) {
|
|||
dbgAddr.off = off || 0;
|
||||
dbgAddr.sel = sel;
|
||||
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.fAddr32 = (fAddr32 != null)? fAddr32 : (this.cpu && this.cpu.segCS.sizeAddr == 4);
|
||||
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
|
||||
* 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}
|
||||
* @param {string|undefined} sAddr
|
||||
* @param {number|undefined} [type] is either CODE or DATA, in case sAddr doesn't specify a segment
|
||||
* @param {boolean} [fNoChecks] (eg, true when setting breakpoints that may not be valid now, but will be later)
|
||||
* @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, type, fNoChecks, fPrint)
|
||||
Debugger.prototype.parseAddr = function(sAddr, fCode, fNoChecks, fPrint)
|
||||
{
|
||||
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;
|
||||
|
||||
type = fNoChecks? Debugger.ADDR.NONE : dbgAddrNext.type;
|
||||
|
||||
if (sAddr !== undefined) {
|
||||
|
||||
sAddr = this.parseReference(sAddr);
|
||||
|
|
@ -2320,25 +2334,30 @@ if (DEBUGGER) {
|
|||
|
||||
switch(ch) {
|
||||
case '&':
|
||||
type = Debugger.ADDR.REAL;
|
||||
type = Debugger.ADDRTYPE.REAL;
|
||||
break;
|
||||
case '#':
|
||||
type = Debugger.ADDR.PROT;
|
||||
type = Debugger.ADDRTYPE.PROT;
|
||||
break;
|
||||
case '%':
|
||||
type = Debugger.ADDR.LINEAR;
|
||||
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.ADDR.NONE;
|
||||
if (iColon >= 0) type = Debugger.ADDRTYPE.NONE;
|
||||
ch = '';
|
||||
break;
|
||||
}
|
||||
|
||||
if (ch) {
|
||||
sAddr = sAddr.substr(1);
|
||||
iColon--;
|
||||
sAddr = sAddr.substr(ch.length);
|
||||
iColon -= ch.length;
|
||||
}
|
||||
|
||||
dbgAddr = this.findSymbolAddr(sAddr);
|
||||
|
|
@ -2436,7 +2455,7 @@ if (DEBUGGER) {
|
|||
Debugger.prototype.hexAddr = function(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)
|
||||
{
|
||||
var i = 0, n = aBlocks.length;
|
||||
var addr = 0, i = 0, n = aBlocks.length;
|
||||
|
||||
if (sAddr) {
|
||||
var addr = this.getAddr(this.parseAddr(sAddr));
|
||||
addr = this.getAddr(this.parseAddr(sAddr));
|
||||
if (addr == X86.ADDR_INVALID) {
|
||||
this.println("invalid address: " + sAddr);
|
||||
return;
|
||||
|
|
@ -2518,12 +2537,26 @@ if (DEBUGGER) {
|
|||
n = 1;
|
||||
}
|
||||
|
||||
this.println("blkid " + (fLinear? "linear " : "physical") + " blkaddr used size type");
|
||||
this.println("-------- --------- -------- ------ ------ ----");
|
||||
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 {
|
||||
|
|
@ -2535,11 +2568,12 @@ if (DEBUGGER) {
|
|||
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);
|
||||
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++;
|
||||
}
|
||||
};
|
||||
|
|
@ -2581,7 +2615,7 @@ if (DEBUGGER) {
|
|||
var sInfo = "no information";
|
||||
if (BACKTRACK) {
|
||||
var sAddr = asArgs[0];
|
||||
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR.CODE, true, false);
|
||||
var dbgAddr = this.parseAddr(sAddr, true, true, false);
|
||||
if (dbgAddr) {
|
||||
var addr = this.getAddr(dbgAddr);
|
||||
sInfo = '%' + str.toHex(addr) + ": " + (this.bus.getSymbol(addr, true) || sInfo);
|
||||
|
|
@ -2604,6 +2638,75 @@ if (DEBUGGER) {
|
|||
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.
|
||||
*/
|
||||
|
|
@ -2645,7 +2748,7 @@ if (DEBUGGER) {
|
|||
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));
|
||||
if (!seg) return;
|
||||
|
||||
|
|
@ -2875,7 +2978,7 @@ if (DEBUGGER) {
|
|||
this.println("invalid task selector: " + sSel);
|
||||
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));
|
||||
|
|
@ -2889,9 +2992,9 @@ if (DEBUGGER) {
|
|||
for (var sField in aTSSFields) {
|
||||
off = aTSSFields[sField];
|
||||
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) {
|
||||
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';
|
||||
sDump += str.toHexWord(off) + ' ' + str.pad(sField + ':', 11) + str.toHex(v, cch);
|
||||
|
|
@ -2904,7 +3007,7 @@ if (DEBUGGER) {
|
|||
*/
|
||||
while (off < seg.offMax && iPort < 0x3ff) {
|
||||
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);
|
||||
iPort += 16;
|
||||
off += 2;
|
||||
|
|
@ -3219,16 +3322,16 @@ if (DEBUGGER) {
|
|||
{
|
||||
/*
|
||||
* 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);
|
||||
|
||||
/*
|
||||
* 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 b, sChar, sAddr, dbgAddr, sReplace;
|
||||
while ((i = s.indexOf('%', i)) >= 0) {
|
||||
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);
|
||||
|
|
@ -3290,7 +3393,7 @@ if (DEBUGGER) {
|
|||
Debugger.prototype.message = function(sMessage, 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;
|
||||
|
|
@ -3736,6 +3839,7 @@ if (DEBUGGER) {
|
|||
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();
|
||||
};
|
||||
|
||||
|
|
@ -3759,6 +3863,7 @@ if (DEBUGGER) {
|
|||
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;
|
||||
};
|
||||
|
||||
|
|
@ -4000,23 +4105,19 @@ if (DEBUGGER) {
|
|||
*/
|
||||
Debugger.prototype.clearBreakpoints = function()
|
||||
{
|
||||
var i;
|
||||
var i, dbgAddr;
|
||||
this.aBreakExec = ["bp"];
|
||||
/*
|
||||
* TODO: Each read breakpoint needs to keep track of whether it's linear or physical.
|
||||
*/
|
||||
if (this.aBreakRead !== undefined) {
|
||||
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"];
|
||||
/*
|
||||
* TODO: Each write breakpoint needs to keep track of whether it's linear or physical.
|
||||
*/
|
||||
if (this.aBreakWrite !== undefined) {
|
||||
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"];
|
||||
|
|
@ -4106,10 +4207,7 @@ if (DEBUGGER) {
|
|||
this.println("invalid address: " + this.hexAddr(dbgAddr));
|
||||
fSuccess = false;
|
||||
} else {
|
||||
/*
|
||||
* TODO: Add some UI that allows a physical address (fLinear is currently hard-coded to true)
|
||||
*/
|
||||
this.cpu.addMemBreak(addr, aBreak == this.aBreakWrite, true);
|
||||
this.cpu.addMemBreak(addr, aBreak == this.aBreakWrite, dbgAddr.type != Debugger.ADDRTYPE.PHYSICAL);
|
||||
/*
|
||||
* Force memory breakpoints to use their linear address, by zapping the selector.
|
||||
*/
|
||||
|
|
@ -4168,10 +4266,7 @@ if (DEBUGGER) {
|
|||
}
|
||||
aBreak.splice(i, 1);
|
||||
if (aBreak != this.aBreakExec) {
|
||||
/*
|
||||
* 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.cpu.removeMemBreak(addr, aBreak == this.aBreakWrite, dbgAddrBreak.type != Debugger.ADDRTYPE.PHYSICAL);
|
||||
}
|
||||
this.historyInit();
|
||||
break;
|
||||
|
|
@ -4952,7 +5047,7 @@ if (DEBUGGER) {
|
|||
Debugger.prototype.getRegDump = function(fProt)
|
||||
{
|
||||
var s;
|
||||
if (fProt === undefined) fProt = this.getCurrentMode();
|
||||
if (fProt === undefined) fProt = this.getCPUMode();
|
||||
|
||||
s = this.getRegOutput(Debugger.REG_AX) +
|
||||
this.getRegOutput(Debugger.REG_BX) +
|
||||
|
|
@ -5689,7 +5784,7 @@ if (DEBUGGER) {
|
|||
*/
|
||||
Debugger.prototype.doAssemble = function(asArgs)
|
||||
{
|
||||
var dbgAddr = this.parseAddr(asArgs[1], Debugger.ADDR.CODE);
|
||||
var dbgAddr = this.parseAddr(asArgs[1], true);
|
||||
if (!dbgAddr) return;
|
||||
|
||||
this.dbgAddrAssemble = dbgAddr;
|
||||
|
|
@ -5768,7 +5863,7 @@ if (DEBUGGER) {
|
|||
}
|
||||
var dbgAddr = {};
|
||||
if (sAddr != '*') {
|
||||
dbgAddr = this.parseAddr(sAddr, Debugger.ADDR.CODE, true);
|
||||
dbgAddr = this.parseAddr(sAddr, true, true);
|
||||
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") {
|
||||
sCmd = 'd';
|
||||
if (sCmd == "ds" || sCmd == "dg" || sCmd == "dl") {
|
||||
sCmd = "d";
|
||||
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") {
|
||||
asArgs[0] = "l";
|
||||
|
|
@ -5973,7 +6084,7 @@ if (DEBUGGER) {
|
|||
return;
|
||||
}
|
||||
|
||||
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR.DATA);
|
||||
var dbgAddr = this.parseAddr(sAddr);
|
||||
if (!dbgAddr || dbgAddr.sel == null && dbgAddr.addr == null) return;
|
||||
|
||||
var cb = 0; // 0 is not a default; it triggers the appropriate default below
|
||||
|
|
@ -6038,7 +6149,7 @@ if (DEBUGGER) {
|
|||
return;
|
||||
}
|
||||
|
||||
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR.DATA);
|
||||
var dbgAddr = this.parseAddr(sAddr);
|
||||
if (!dbgAddr) return;
|
||||
|
||||
for (var i = 2; i < asArgs.length; i++) {
|
||||
|
|
@ -6298,7 +6409,7 @@ if (DEBUGGER) {
|
|||
{
|
||||
var sSymbol = null;
|
||||
|
||||
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR.CODE);
|
||||
var dbgAddr = this.parseAddr(sAddr, true);
|
||||
if (dbgAddr) {
|
||||
|
||||
var addr = this.getAddr(dbgAddr);
|
||||
|
|
@ -6364,7 +6475,7 @@ if (DEBUGGER) {
|
|||
var fJSON = (asArgs[1] == "json");
|
||||
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;
|
||||
|
||||
iDrive = this.parseValue(asArgs[2], "drive #");
|
||||
|
|
@ -6922,7 +7033,7 @@ if (DEBUGGER) {
|
|||
Debugger.prototype.doRun = function(sAddr, sOptions, fQuiet)
|
||||
{
|
||||
if (sAddr !== undefined) {
|
||||
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR.CODE);
|
||||
var dbgAddr = this.parseAddr(sAddr, true);
|
||||
if (!dbgAddr) return;
|
||||
this.parseAddrOptions(dbgAddr, sOptions);
|
||||
this.setTempBreakpoint(dbgAddr);
|
||||
|
|
@ -7245,7 +7356,7 @@ if (DEBUGGER) {
|
|||
*/
|
||||
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 (n === undefined) n = 1;
|
||||
|
|
@ -7253,7 +7364,7 @@ if (DEBUGGER) {
|
|||
var cb = 0x100;
|
||||
if (sAddrEnd !== undefined) {
|
||||
|
||||
var dbgAddrEnd = this.parseAddr(sAddrEnd, Debugger.ADDR.CODE);
|
||||
var dbgAddrEnd = this.parseAddr(sAddrEnd, true);
|
||||
if (!dbgAddrEnd || dbgAddrEnd.off < dbgAddr.off) return;
|
||||
|
||||
cb = dbgAddrEnd.off - dbgAddr.off;
|
||||
|
|
|
|||
|
|
@ -73,11 +73,13 @@ var Interrupts = {
|
|||
KRNLVARS: 0x005A, // DS_Kernel_Vars
|
||||
RELSEG: 0x005C, // DS_ReleaseSeg (same as DS_FreeSeg but "restores any breakpoints first")
|
||||
LOADHIGH: 0x005D, // D386_LoadCodeDataHigh
|
||||
EXITCALL: 0x0062, // DS_EXITCALL
|
||||
LOADDLL: 0x0064, // DS_LOADDLL
|
||||
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
|
||||
CHECKFAULT: 0x007F, // DS_CheckFault (BX==fault #, CX==fault type; return AX=0 to handle fault normally)
|
||||
TRAPFAULT: 0x0083, // DS_TrapFault (BX==fault #, CX==faulting CS, EDX==faulting EIP, ESI==fault error, EDI==fault flags)
|
||||
CHECKFAULT: 0x007F, // DS_CheckFault (BX==fault #, CX==FAULTTYPE bits; return AX=0 to handle fault normally, 1 to issue TRAPFAULT)
|
||||
FAULTTYPE: {
|
||||
V86: 0x0001,
|
||||
PM: 0x0002,
|
||||
|
|
@ -85,6 +87,7 @@ var Interrupts = {
|
|||
FIRST: 0x0008,
|
||||
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)
|
||||
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)
|
||||
|
|
@ -110,7 +113,7 @@ if (DEBUGGER) {
|
|||
*
|
||||
* 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 $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.
|
||||
|
|
@ -120,15 +123,15 @@ if (DEBUGGER) {
|
|||
Interrupts.FUNCS[Interrupts.DISK] = {
|
||||
0x00: "disk reset",
|
||||
0x01: "get status",
|
||||
0x02: "read drive %DL (%CH:%DH:%CL,%AL) into %ES:%BX",
|
||||
0x03: "write drive %DL (%CH:%DH:%CL,%AL) from %ES:%BX",
|
||||
0x04: "verify drive %DL (%CH:%DH:%CL,%AL)",
|
||||
0x05: "format drive %DL using %ES:%BX",
|
||||
0x08: "read drive %DL parameters into %ES:%DI",
|
||||
0x15: "get drive %DL DASD type",
|
||||
0x16: "get drive %DL change line status",
|
||||
0x17: "set drive %DL DASD type",
|
||||
0x18: "set drive %DL media type"
|
||||
0x02: "read drive @DL (@CH:@DH:@CL,@AL) into @ES:@BX",
|
||||
0x03: "write drive @DL (@CH:@DH:@CL,@AL) from @ES:@BX",
|
||||
0x04: "verify drive @DL (@CH:@DH:@CL,@AL)",
|
||||
0x05: "format drive @DL using @ES:@BX",
|
||||
0x08: "read drive @DL parameters into @ES:@DI",
|
||||
0x15: "get drive @DL DASD type",
|
||||
0x16: "get drive @DL change line status",
|
||||
0x17: "set drive @DL DASD type",
|
||||
0x18: "set drive @DL media type"
|
||||
/*
|
||||
* 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
|
||||
|
|
@ -161,11 +164,11 @@ if (DEBUGGER) {
|
|||
0x80: "open device",
|
||||
0x81: "close device",
|
||||
0x82: "program termination",
|
||||
0x83: "wait %CX:%DXus for event",
|
||||
0x83: "wait @CX:@DXus for event",
|
||||
0x84: "joystick support",
|
||||
0x85: "SYSREQ pressed",
|
||||
0x86: "wait %CX:%DXus",
|
||||
0x87: "move block (%CX words)",
|
||||
0x86: "wait @CX:@DXus",
|
||||
0x87: "move block (@CX words)",
|
||||
0x88: "get extended memory size",
|
||||
0x89: "processor to virtual mode",
|
||||
0x90: "device busy loop",
|
||||
|
|
@ -174,97 +177,97 @@ if (DEBUGGER) {
|
|||
Interrupts.FUNCS[Interrupts.DOS] = {
|
||||
0x00: "terminate program",
|
||||
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
|
||||
0x04: "write character #%DL to stdaux", // eg, COM1
|
||||
0x05: "write character #%DL to stdprn", // eg, LPT1
|
||||
0x06: "direct console output (input if %DL=FF)",
|
||||
0x04: "write character #@DL to stdaux", // eg, COM1
|
||||
0x05: "write character #@DL to stdprn", // eg, LPT1
|
||||
0x06: "direct console output (input if @DL=FF)",
|
||||
0x07: "direct console input 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
|
||||
0x0B: "get stdin status",
|
||||
0x0C: "flush buffer and read stdin", // AL is a function # (0x01, 0x06, 0x07, 0x08, or 0x0A)
|
||||
0x0D: "disk reset",
|
||||
0x0E: "select default drive %DL", // returns # of available drives in AL
|
||||
0x0F: "open file using FCB ^%DS:%DX", // DS:DX -> unopened File Control Block
|
||||
0x10: "close file using FCB ^%DS:%DX",
|
||||
0x11: "find first matching file using FCB ^%DS:%DX",
|
||||
0x12: "find next matching file using FCB ^%DS:%DX",
|
||||
0x13: "delete file using FCB ^%DS:%DX",
|
||||
0x14: "sequential read from file using FCB ^%DS:%DX",
|
||||
0x15: "sequential write to file using FCB ^%DS:%DX",
|
||||
0x16: "create or truncate file using FCB ^%DS:%DX",
|
||||
0x17: "rename file using FCB ^%DS:%DX",
|
||||
0x0E: "select default drive @DL", // returns # of available drives in AL
|
||||
0x0F: "open file using FCB ^@DS:@DX", // DS:DX -> unopened File Control Block
|
||||
0x10: "close file using FCB ^@DS:@DX",
|
||||
0x11: "find first matching file using FCB ^@DS:@DX",
|
||||
0x12: "find next matching file using FCB ^@DS:@DX",
|
||||
0x13: "delete file using FCB ^@DS:@DX",
|
||||
0x14: "sequential read from file using FCB ^@DS:@DX",
|
||||
0x15: "sequential write to file using FCB ^@DS:@DX",
|
||||
0x16: "create or truncate file using FCB ^@DS:@DX",
|
||||
0x17: "rename file using FCB ^@DS:@DX",
|
||||
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",
|
||||
0x1C: "get allocation information for specific drive %DL",
|
||||
0x1C: "get allocation information for specific drive @DL",
|
||||
0x1F: "get drive parameter block for default drive",
|
||||
0x21: "read random record from file using FCB ^%DS:%DX",
|
||||
0x22: "write random record to file using FCB ^%DS:%DX",
|
||||
0x23: "get file size using FCB ^%DS:%DX",
|
||||
0x24: "set random record number for FCB ^%DS:%DX",
|
||||
0x25: "set address %DS:%DX of interrupt vector %AL",
|
||||
0x26: "create new PSP at segment %DX",
|
||||
0x27: "random block read from 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",
|
||||
0x21: "read random record from file using FCB ^@DS:@DX",
|
||||
0x22: "write random record to file using FCB ^@DS:@DX",
|
||||
0x23: "get file size using FCB ^@DS:@DX",
|
||||
0x24: "set random record number for FCB ^@DS:@DX",
|
||||
0x25: "set address @DS:@DX of interrupt vector @AL",
|
||||
0x26: "create new PSP at segment @DX",
|
||||
0x27: "random block read from 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",
|
||||
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)",
|
||||
0x2D: "set system time (hour=%CH, min=%CL, sec=%DH, 100ths=%DL)",
|
||||
0x2E: "set verify flag %AL",
|
||||
0x2D: "set system time (hour=@CH, min=@CL, sec=@DH, 100ths=@DL)",
|
||||
0x2E: "set verify flag @AL",
|
||||
0x2F: "get disk transfer area (DTA=ES:BX)", // DOS 2.00+
|
||||
0x30: "get DOS version (AL=major, AH=minor)",
|
||||
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",
|
||||
0x34: "get address (ES:BX) of InDOS flag",
|
||||
0x35: "get address (ES:BX) of interrupt vector %AL",
|
||||
0x36: "get free disk space of drive %DL",
|
||||
0x37: "get(0)/set(1) switch character %DL (%AL)",
|
||||
0x35: "get address (ES:BX) of interrupt vector @AL",
|
||||
0x36: "get free disk space of drive @DL",
|
||||
0x37: "get(0)/set(1) switch character @DL (@AL)",
|
||||
0x38: "get country-specific information",
|
||||
0x39: "create subdirectory $%DS:%DX",
|
||||
0x3A: "remove subdirectory $%DS:%DX",
|
||||
0x3B: "set current directory $%DS:%DX",
|
||||
0x3C: "create or truncate file $%DS:%DX with attributes %CX",
|
||||
0x3D: "open file $%DS:%DX with mode %AL",
|
||||
0x3E: "close file %BX",
|
||||
0x3F: "read %CX bytes from file %BX into buffer %DS:%DX",
|
||||
0x40: "write %CX bytes to file %BX from buffer %DS:%DX",
|
||||
0x41: "delete file $%DS:%DX",
|
||||
0x42: "set position %CX:%DX of file %BX relative to %AL",
|
||||
0x43: "get(0)/set(1) attributes %CX of file $%DS:%DX (%AL)",
|
||||
0x39: "create subdirectory $@DS:@DX",
|
||||
0x3A: "remove subdirectory $@DS:@DX",
|
||||
0x3B: "set current directory $@DS:@DX",
|
||||
0x3C: "create or truncate file $@DS:@DX with attributes @CX",
|
||||
0x3D: "open file $@DS:@DX with mode @AL",
|
||||
0x3E: "close file @BX",
|
||||
0x3F: "read @CX bytes from file @BX into buffer @DS:@DX",
|
||||
0x40: "write @CX bytes to file @BX from buffer @DS:@DX",
|
||||
0x41: "delete file $@DS:@DX",
|
||||
0x42: "set position @CX:@DX of file @BX relative to @AL",
|
||||
0x43: "get(0)/set(1) attributes @CX of file $@DS:@DX (@AL)",
|
||||
0x44: "get device information (IOCTL)",
|
||||
0x45: "duplicate file handle %BX",
|
||||
0x46: "force file handle %CX to duplicate file handle %BX",
|
||||
0x47: "get current directory (DS:SI) for drive %DL",
|
||||
0x48: "allocate memory segment with %BX paragraphs",
|
||||
0x49: "free memory segment %ES",
|
||||
0x4A: "resize memory segment %ES to %BX paragraphs",
|
||||
0x4B: "load program $%DS:%DX using parameter block %ES:%BX",
|
||||
0x4C: "terminate with return code %AL",
|
||||
0x45: "duplicate file handle @BX",
|
||||
0x46: "force file handle @CX to duplicate file handle @BX",
|
||||
0x47: "get current directory (DS:SI) for drive @DL",
|
||||
0x48: "allocate memory segment with @BX paragraphs",
|
||||
0x49: "free memory segment @ES",
|
||||
0x4A: "resize memory segment @ES to @BX paragraphs",
|
||||
0x4B: "load program $@DS:@DX using parameter block @ES:@BX",
|
||||
0x4C: "terminate with 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",
|
||||
0x50: "set current PSP %BX",
|
||||
0x50: "set current PSP @BX",
|
||||
0x51: "get current PSP (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)",
|
||||
0x55: "create child PSP at segment %DX",
|
||||
0x56: "rename file $%DS:%DX to $%ES:%DI",
|
||||
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+
|
||||
0x55: "create child PSP at segment @DX",
|
||||
0x56: "rename file $@DS:@DX to $@ES:@DI",
|
||||
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+
|
||||
0x59: "get extended error information", // 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)
|
||||
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)
|
||||
0x60: "get fully-qualified filename from $%DS:%SI", // DOS 3.00+ (undocumented)
|
||||
0x63: "get lead byte table (%AL)", // DOS 2.25 and 3.20+
|
||||
0x6C: "extended open file $%DS:%SI" // DOS 4.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)
|
||||
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)
|
||||
0x60: "get fully-qualified filename from $@DS:@SI", // DOS 3.00+ (undocumented)
|
||||
0x63: "get lead byte table (@AL)", // DOS 2.25 and 3.20+
|
||||
0x6C: "extended open file $@DS:@SI" // DOS 4.00+
|
||||
};
|
||||
Interrupts.FUNCS[Interrupts.WINDBG.VECTOR] = {
|
||||
0x004F: "check debugger loaded" // WINDBG.IS_LOADED returns WINDBG.LOADED (0xF386) if debugger loaded
|
||||
|
|
|
|||
|
|
@ -619,7 +619,7 @@ Memory.prototype = {
|
|||
* @return {number}
|
||||
*/
|
||||
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);
|
||||
}
|
||||
return 0xff;
|
||||
|
|
@ -633,7 +633,7 @@ Memory.prototype = {
|
|||
* @param {number} 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);
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -686,6 +686,8 @@ X86CPU.prototype.setAddressMask = function(nBusMask)
|
|||
* 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.
|
||||
*
|
||||
* For now, this is simply a DEBUGGER-only interface.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} addr
|
||||
* @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
|
||||
* 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}
|
||||
* @param {number} addr
|
||||
* @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();
|
||||
} 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++) {
|
||||
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
|
||||
* 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}
|
||||
* @param {number} addr is a linear address
|
||||
* @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
|
||||
*/
|
||||
X86CPU.prototype.probeAddr = function(addr, size)
|
||||
X86CPU.prototype.probeAddr = function(addr, size, fLinear)
|
||||
{
|
||||
var block = this.aMemBlocks[(addr & this.nMemMask) >>> this.nBlockShift];
|
||||
if (block) {
|
||||
if (block.type == Memory.TYPE.UNPAGED) {
|
||||
block = this.mapPageBlock(addr, false, true);
|
||||
}
|
||||
var aBlocks = fLinear === false? this.aBusBlocks : this.aMemBlocks;
|
||||
var block = aBlocks[(addr & this.nMemMask) >>> this.nBlockShift];
|
||||
if (block && block.type == Memory.TYPE.UNPAGED) {
|
||||
block = this.mapPageBlock(addr, false, true);
|
||||
}
|
||||
if (block) {
|
||||
var off = addr & this.nBlockLimit;
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ X86Seg.CALLBREAK_SEL = 0x0001;
|
|||
* 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
|
||||
* "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}
|
||||
* @param {function()} fn
|
||||
|
|
|
|||
Loading…
Reference in a new issue