diff --git a/modules/pcjs/lib/debugger.js b/modules/pcjs/lib/debugger.js index 52a55742f..3bb26653e 100644 --- a/modules/pcjs/lib/debugger.js +++ b/modules/pcjs/lib/debugger.js @@ -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.} 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; diff --git a/modules/pcjs/lib/interrupts.js b/modules/pcjs/lib/interrupts.js index 8a07b3516..9291a3d12 100644 --- a/modules/pcjs/lib/interrupts.js +++ b/modules/pcjs/lib/interrupts.js @@ -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 diff --git a/modules/pcjs/lib/memory.js b/modules/pcjs/lib/memory.js index bd988440c..b27202cbd 100644 --- a/modules/pcjs/lib/memory.js +++ b/modules/pcjs/lib/memory.js @@ -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); } }, diff --git a/modules/pcjs/lib/x86cpu.js b/modules/pcjs/lib/x86cpu.js index 57a286faf..740d21216 100644 --- a/modules/pcjs/lib/x86cpu.js +++ b/modules/pcjs/lib/x86cpu.js @@ -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; diff --git a/modules/pcjs/lib/x86seg.js b/modules/pcjs/lib/x86seg.js index 5fd49c290..fbf9a994c 100644 --- a/modules/pcjs/lib/x86seg.js +++ b/modules/pcjs/lib/x86seg.js @@ -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