From 8d83504d9ac672b47e050ef226ace173bdc9ab7c Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Fri, 4 Sep 2015 14:13:09 -0700 Subject: [PATCH] Added support for "call breaks", and fixed BTC, BTR and BTS (hopefully correctly this time), --- modules/pcjs/lib/debugger.js | 105 ++++++++++++++++++++++++++++++--- modules/pcjs/lib/interrupts.js | 24 ++++---- modules/pcjs/lib/x86cpu.js | 8 +-- modules/pcjs/lib/x86func.js | 59 ++++++++++-------- modules/pcjs/lib/x86seg.js | 81 +++++++++++++++++++++---- 5 files changed, 215 insertions(+), 62 deletions(-) diff --git a/modules/pcjs/lib/debugger.js b/modules/pcjs/lib/debugger.js index f18edd46a..793aa3882 100644 --- a/modules/pcjs/lib/debugger.js +++ b/modules/pcjs/lib/debugger.js @@ -1271,7 +1271,7 @@ if (DEBUGGER) { this.messageDump(Messages.TSS, function onDumpTSS(asArgs) { dbg.dumpTSS(asArgs); }); this.messageDump(Messages.DOS, function onDumpDOS(asArgs) { dbg.dumpDOS(asArgs); }); - if (Interrupts.WINDBG.ENABLED) { + if (Interrupts.WINDBG.ENABLED || Interrupts.WINDBGRM.ENABLED) { this.fWinDbg = null; this.cpu.addIntNotify(Interrupts.WINDBG.VECTOR, this.intWindowsDebugger.bind(this)); } @@ -1283,12 +1283,21 @@ if (DEBUGGER) { this.setReady(); }; - if (Interrupts.WINDBG.ENABLED) { + if (Interrupts.WINDBG.ENABLED || Interrupts.WINDBGRM.ENABLED) { /** * intWindowsDebugger() * * This intercepts calls to the Windows Debugger protected-mode interface (INT 0x41). * + * It's enabled if Interrupts.WINDBG.ENABLED is true, but it must ALSO be enabled if + * Interrupts.WINDBGRM.ENABLED is true, because if the latter decides to respond to + * requests, then we must start responding, too, because Windows assumes that the former + * is installed whenever it detects the latter. + * + * 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. + * * @this {Debugger} * @param {number} addr * @return {boolean} true to proceed with the INT 0x41 software interrupt, false to skip @@ -1328,10 +1337,15 @@ if (DEBUGGER) { return true; } + /* + * NOTE: If this.fWinDbg is true, then all cases should return false, because we're taking full + * responsibility for all requests (don't assume there's valid interrupt handler inside the machine). + */ switch(AX) { case Interrupts.WINDBG.IS_LOADED: if (this.fWinDbg) { cpu.regEAX = (cpu.regEAX & ~0xffff) | Interrupts.WINDBG.LOADED; + return false; } break; @@ -1341,14 +1355,16 @@ if (DEBUGGER) { */ sModule = this.getSZ(this.newAddr(DI, ES)); limit = (seg = this.getSegment(CX))? seg.limit : 0; - if (!this.fWinDbg) { + if (this.fWinDbg) { this.println(sModule + "!undefined " + ((SI & 0x1)? "data" : "code") + '(' + str.toHex(BX+1, 4) + ")=#" + str.toHex(CX, 4) + " len " + str.toHex(limit+1)); + return false; } break; default: if (this.fWinDbg) { this.println("INT 0x41: " + str.toHexWord(AX)); + return false; } break; } @@ -1387,7 +1403,11 @@ if (DEBUGGER) { if ((cpu.regEAX & 0xffff) != Interrupts.WINDBGRM.LOADED) { cpu.regEAX = (cpu.regEAX & ~0xffff) | Interrupts.WINDBGRM.LOADED; dbg.println("INT 0x68 handling enabled"); - dbg.fWinDbgRM = true; + /* + * If we turn on INT 0x68 handling, we must also turn on INT 0x41 handling, + * because Windows assumes that the latter handler exists whenever the former does. + */ + dbg.fWinDbg = dbg.fWinDbgRM = true; } else { dbg.println("INT 0x68 monitoring enabled"); dbg.fWinDbgRM = false; @@ -1402,9 +1422,20 @@ if (DEBUGGER) { case Interrupts.WINDBGRM.IS_LOADED: if (this.fWinDbgRM) { cpu.regEAX = (cpu.regEAX & ~0xffff) | Interrupts.WINDBGRM.LOADED; + return false; } break; + case Interrupts.WINDBGRM.PREP_PMODE: + if (this.fWinDbgRM) { + var a = cpu.segCS.addCallBreak(this.callWindowsDebuggerPMInit.bind(this)); + if (a) { + cpu.regEDI = a[0]; + cpu.setES(a[1]); + } + } + return false; + case Interrupts.WINDBGRM.LOAD_SEG: if (AL == 0x20) { /* @@ -1450,22 +1481,80 @@ if (DEBUGGER) { var dbgAddrDevice = this.newAddr(this.getLong(dbgAddr, 4), this.getShort(dbgAddr, 2)); var dbgAddrModule = this.newAddr(this.getLong(dbgAddr, 4), this.getShort(dbgAddr, 2)); var selAlias = this.getShort(dbgAddr, 2) || sel; - if (!this.fWinDbgRM) { - this.log(this.getSZ(dbgAddrModule) + '!' + this.getSZ(dbgAddrDevice) + "!undefined " + ((AL & 0x1)? "data" : "code") + '(' + str.toHex(nSeg, 4) + ")=" + str.toHex(selAlias, 4) + ':' + str.toHex(off) + " len " + str.toHex(len)); + if (this.fWinDbgRM) { + this.println(this.getSZ(dbgAddrModule) + '!' + this.getSZ(dbgAddrDevice) + "!undefined " + ((AL & 0x1)? "data" : "code") + '(' + str.toHex(nSeg, 4) + ")=" + str.toHex(selAlias, 4) + ':' + str.toHex(off) + " len " + str.toHex(len)); } } - cpu.regEAX = (cpu.regEAX & ~0xff) | 0x01; + if (this.fWinDbgRM) { + cpu.regEAX = (cpu.regEAX & ~0xff) | 0x01; + return false; + } break; default: if (this.fWinDbgRM) { - this.log("INT 0x68: " + str.toHexByte(AH)); + this.println("INT 0x68: " + str.toHexByte(AH)); + return false; } break; } return true; }; + + /** + * callWindowsDebuggerPMInit() + * + * This intercepts calls to the Windows Debugger "PMInit" interface; eg: + * + * AL = function code + * + * 0 - initialize IDT + * ES:EDI points to protected mode IDT + * + * 1 - initialize page checking + * BX = physical selector + * ECX = linear bias + * + * 2 - specify that debug queries are supported + * + * 3 - initialize spare PTE + * EBX = linear address of spare PTE + * EDX = linear address the PTE represents + * + * 4 - set Enter/Exit VMM routine address + * EBX = Enter VMM routine address + * ECX = Exit VMM routine address + * EDX = $_Debug_Out_Service address + * ESI = $_Trace_Out_Service address + * The VMM enter/exit routines must return with a retfd + * + * 5 - get debugger size/physical address + * returns: AL = 0 (don't call AL = 1) + * ECX = size in bytes + * ESI = starting physical code/data address + * + * 6 - set debugger base/initialize spare PTE + * EBX = linear address of spare PTE + * EDX = linear address the PTE represents + * ESI = starting linear address of debug code/data + * + * 7 - enable memory context functions + * + * @this {Debugger} + * @return {boolean} (must always return false to skip the call, because the call is using a CALLBREAK address) + */ + Debugger.prototype.callWindowsDebuggerPMInit = function() + { + var cpu = this.cpu; + var AL = cpu.regEAX & 0xff; + 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; + } + return false; + } } /** diff --git a/modules/pcjs/lib/interrupts.js b/modules/pcjs/lib/interrupts.js index db31bd41b..949e80f12 100644 --- a/modules/pcjs/lib/interrupts.js +++ b/modules/pcjs/lib/interrupts.js @@ -62,21 +62,19 @@ var Interrupts = { ALT_DISK: 0x40, // HDC BIOS saves original FDC BIOS vector here ALT_VIDEO: 0x6D, // IBM VGA BIOS saves original video BIOS vector here WINDBG: { // Windows Debugger protected-mode interface - VECTOR: 0x41, - IS_LOADED: 0x004F, // AX command - LOADED: 0xF386, // returned in AX if Windows Debugger loaded - LOAD_SEG: 0x0050, // SI==0 if code, 1 if data; BX==segnum-1; CX==selector; ES:[E]DI->module name - ENABLED: true // support for WINDBGRM interrupts can be disabled + VECTOR: 0x41, // (AX==command) + IS_LOADED: 0x004F, // DS_DebLoaded + LOADED: 0xF386, // DS_DebPresent (returned in AX if Windows Debugger loaded) + LOAD_SEG: 0x0050, // DS_LoadSeg (SI==0 if code, 1 if data; BX==segnum-1; CX==selector; ES:[E]DI->module name) + ENABLED: true // support for WINDBG interrupts can be disabled (but NOT if WINDBGRM is enabled) }, WINDBGRM: { // Windows Debugger real-mode interface - VECTOR: 0x68, - IS_LOADED: 0x43, // AH command - LOADED: 0xF386, // returned in AX if Windows Debugger loaded - LOAD_SEG: 0x50, // AL=segment type, ES:DI->D386_Device_Params - /* - * This must be disabled until we're able to respond intelligently to requests like D386_Prepare_PMode (0x44) - */ - ENABLED: false // support for WINDBGRM interrupts can be disabled + VECTOR: 0x68, // (AH==command) + IS_LOADED: 0x43, // D386_Identify + LOADED: 0xF386, // D386_Id (returned in AX if Windows Debugger loaded) + PREP_PMODE: 0x44, // D386_Prepare_PMode (must return a 16:32 address in ES:EDI to a "PMinit" handler) + LOAD_SEG: 0x50, // D386_Load_Segment (AL=segment type, ES:DI->D386_Device_Params) + ENABLED: true // support for WINDBGRM interrupts can be disabled }, FUNCS: {} // filled in only if DEBUGGER is true }; diff --git a/modules/pcjs/lib/x86cpu.js b/modules/pcjs/lib/x86cpu.js index db26840d1..25578e5b0 100644 --- a/modules/pcjs/lib/x86cpu.js +++ b/modules/pcjs/lib/x86cpu.js @@ -2310,18 +2310,14 @@ X86CPU.prototype.setCSIP = function(off, sel, fCall) /* * We break this operation into the following discrete steps (eg, set IP, load CS, and then update IP) so * that segCS.load(sel) has the ability to modify IP when sel refers to a gate (call, interrupt, trap, etc). - * - * NOTE: regEIP acts merely as a conduit for the IP, if any, that segCS.load() may load; regLIP is still our - * internal instruction pointer. Callers that need the real IP must call getIP(). */ - this.regEIP = off; - var base = this.segCS.loadCode(sel, fCall); + var base = this.segCS.loadCode(off, sel, fCall); if (base !== X86.ADDR_INVALID) { /* * TODO: Should this code be factored into a setLIP() function? The other primary client would be fnINT(). */ if (I386) this.resetSizes(); - this.regLIP = (base + (this.regEIP & (I386? this.maskData : 0xffff)))|0; + this.regLIP = (base + (this.segCS.offIP & (I386? this.maskData : 0xffff)))|0; this.regLIPLimit = (base + this.segCS.limit)|0; this.nCPL = this.segCS.cpl; // cache the current CPL where it's more convenient if (PREFETCH) this.flushPrefetch(this.regLIP); diff --git a/modules/pcjs/lib/x86func.js b/modules/pcjs/lib/x86func.js index 649d7e7b3..6cd6c3219 100644 --- a/modules/pcjs/lib/x86func.js +++ b/modules/pcjs/lib/x86func.js @@ -369,21 +369,29 @@ X86.fnBTMem = function BTMem(dst, src) if (this.regEA === X86.ADDR_INVALID) { return X86.fnBT.call(this, dst, src); } - var offByte = src >>> 3; - if (offByte >= this.sizeData) { + + /* + * TODO: Consider a worker function that performs the following block of code for: BT, BTC, BTR, and BTS. + * It's somewhat inconvenient, because it needs to provide two results: an updated src AND an updated dst. + */ + if ((src >>> 3) >= this.sizeData) { /* - * offByte is src divided by 8, but now we need src divided by 16 or 32, according to the OPERAND size, + * We just divided src by 8, but now we need to divide src by 16 or 32, according to the OPERAND size, * which means shifting it right by either 4 or 5 bits. That gives us a short or long INDEX, which we then - * multiply by the OPERAND size to obtain to the corresponding short or long OFFSET that we add to regEA. + * multiply by the OPERAND size to obtain to the corresponding short or long OFFSET that we must add to + * the original EA offset. */ var i = src >>> (this.sizeData == 2? 4 : 5); - dst = this.getWord(this.regEA += i * this.sizeData); + dst = this.getEAWord(this.segEA, this.offEA + i * this.sizeData); } /* - * Now we convert src from a bit index into a bit mask. + * Now we convert src from a bit index to a bit mask. */ src = 1 << (src & (this.sizeData == 2? 0xf : 0x1f)); if (dst & src) this.setCF(); else this.clearCF(); + /* + * End of common code block + */ this.nStepCycles -= 6; this.opFlags |= X86.OPFLAG.NOWRITE; @@ -406,18 +414,19 @@ X86.fnBTCMem = function BTCMem(dst, src) if (this.regEA === X86.ADDR_INVALID) { return X86.fnBTC.call(this, dst, src); } - var offByte = src >>> 3; - if (offByte >= this.sizeData) { + + if ((src >>> 3) >= this.sizeData) { /* - * offByte is src divided by 8, but now we need src divided by 16 or 32, according to the OPERAND size, + * We just divided src by 8, but now we need to divide src by 16 or 32, according to the OPERAND size, * which means shifting it right by either 4 or 5 bits. That gives us a short or long INDEX, which we then - * multiply by the OPERAND size to obtain to the corresponding short or long OFFSET that we add to regEA. + * multiply by the OPERAND size to obtain to the corresponding short or long OFFSET that we must add to + * the original EA offset. */ var i = src >>> (this.sizeData == 2? 4 : 5); - dst = this.getWord(this.regEA += i * this.sizeData); + dst = this.getEAWord(this.segEA, this.offEA + i * this.sizeData); } /* - * Now we convert src from a bit index into a bit mask. + * Now we convert src from a bit index to a bit mask. */ src = 1 << (src & (this.sizeData == 2? 0xf : 0x1f)); if (dst & src) this.setCF(); else this.clearCF(); @@ -442,18 +451,19 @@ X86.fnBTRMem = function BTRMem(dst, src) if (this.regEA === X86.ADDR_INVALID) { return X86.fnBTR.call(this, dst, src); } - var offByte = src >>> 3; - if (offByte >= this.sizeData) { + + if ((src >>> 3) >= this.sizeData) { /* - * offByte is src divided by 8, but now we need src divided by 16 or 32, according to the OPERAND size, + * We just divided src by 8, but now we need to divide src by 16 or 32, according to the OPERAND size, * which means shifting it right by either 4 or 5 bits. That gives us a short or long INDEX, which we then - * multiply by the OPERAND size to obtain to the corresponding short or long OFFSET that we add to regEA. + * multiply by the OPERAND size to obtain to the corresponding short or long OFFSET that we must add to + * the original EA offset. */ var i = src >>> (this.sizeData == 2? 4 : 5); - dst = this.getWord(this.regEA += i * this.sizeData); + dst = this.getEAWord(this.segEA, this.offEA + i * this.sizeData); } /* - * Now we convert src from a bit index into a bit mask. + * Now we convert src from a bit index to a bit mask. */ src = 1 << (src & (this.sizeData == 2? 0xf : 0x1f)); if (dst & src) this.setCF(); else this.clearCF(); @@ -478,18 +488,19 @@ X86.fnBTSMem = function BTSMem(dst, src) if (this.regEA === X86.ADDR_INVALID) { return X86.fnBTS.call(this, dst, src); } - var offByte = src >>> 3; - if (offByte >= this.sizeData) { + + if ((src >>> 3) >= this.sizeData) { /* - * offByte is src divided by 8, but now we need src divided by 16 or 32, according to the OPERAND size, + * We just divided src by 8, but now we need to divide src by 16 or 32, according to the OPERAND size, * which means shifting it right by either 4 or 5 bits. That gives us a short or long INDEX, which we then - * multiply by the OPERAND size to obtain to the corresponding short or long OFFSET that we add to regEA. + * multiply by the OPERAND size to obtain to the corresponding short or long OFFSET that we must add to + * the original EA offset. */ var i = src >>> (this.sizeData == 2? 4 : 5); - dst = this.getWord(this.regEA += i * this.sizeData); + dst = this.getEAWord(this.segEA, this.offEA + i * this.sizeData); } /* - * Now we convert src from a bit index into a bit mask. + * Now we convert src from a bit index to a bit mask. */ src = 1 << (src & (this.sizeData == 2? 0xf : 0x1f)); if (dst & src) this.setCF(); else this.clearCF(); diff --git a/modules/pcjs/lib/x86seg.js b/modules/pcjs/lib/x86seg.js index 0dc9025c7..bb0db9c54 100644 --- a/modules/pcjs/lib/x86seg.js +++ b/modules/pcjs/lib/x86seg.js @@ -124,10 +124,14 @@ function X86Seg(cpu, id, sName, fProt) * * loadIDT() sets fCall to true unconditionally in protected-mode (fCall has no meaning in real-mode). */ - this.fCall = null; - this.fStackSwitch = false; - this.sizeFrame = 2; // must be set by all loadIDT() calls so that callers know the proper frame size - this.awParms = (this.id == X86Seg.ID.CODE? new Array(32) : []); + if (this.id == X86Seg.ID.CODE) { + this.offIP = 0; + this.fCall = null; + this.fStackSwitch = false; + this.sizeFrame = 2; // must be set by all loadIDT() calls so that callers know the proper frame size + this.awParms = new Array(32); + this.aCallBreaks = []; + } this.updateMode(true, fProt); } @@ -142,18 +146,41 @@ X86Seg.ID = { DBG: 7 // "DBG" }; +X86Seg.CALLBREAK_SEL = 0x0001; + /** - * loadCode(sel, fCall) + * addCallBreak(fn) * - * A simple wrapper function that encapsulates setting the fCall property for segCS loads. + * Returns a "call break" address in an [off, sel] array. The given function, fn(), is called + * 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'. * * @this {X86Seg} + * @param {function()} fn + * @return {Array.} containing offset and selector of call-break address + */ +X86Seg.prototype.addCallBreak = function(fn) +{ + this.aCallBreaks.push(fn); + return [this.aCallBreaks.length, X86Seg.CALLBREAK_SEL]; +}; + +/** + * loadCode(off, sel, fCall) + * + * A simple wrapper function that encapsulates setting offIP and fCall for segCS loads. + * + * @this {X86Seg} + * @param {number} off * @param {number} sel * @param {boolean|undefined} fCall is true if CALLF in progress, false if RETF/IRET in progress, undefined otherwise * @return {number} base address of selected segment, or X86.ADDR_INVALID if error */ -X86Seg.prototype.loadCode = function loadCode(sel, fCall) +X86Seg.prototype.loadCode = function loadCode(off, sel, fCall) { + this.offIP = off; this.fCall = fCall; return this.load(sel); }; @@ -295,7 +322,7 @@ X86Seg.prototype.loadIDTProt = function loadIDTProt(nIDT) var addrDesc = (cpu.addrIDT + nIDT)|0; if (((cpu.addrIDTLimit - addrDesc)|0) >= 7) { this.fCall = true; - return this.loadDesc8(addrDesc, nIDT) + cpu.regEIP; + return this.loadDesc8(addrDesc, nIDT) + this.offIP; } /* * TODO: Remove fHalt=true from this fnFault() call once this code path has been tested. @@ -637,6 +664,35 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe) this.sizeFrame = this.sizeData; var fCall = this.fCall; + + /* + * This special bit of code is currently used only by the Debugger, when it needs to inject + * a 16:32 callback address into the machine that it can intercept calls to. We call these + * "call break" addresses, because they're like private breakpoints that only operate when + * a particular address is called; specifically, an address with selector 0x0001 and an offset + * that forms an index (1-based) into the aCallBreaks function table. + * + * In protected-mode, 0x0001 is an invalid code selector (a null selector with an RPL of 1), + * and while it's not inconceivable that an operating system might use such a selector for + * some strange purpose, I've not seen such an operating system. And in any case, those + * operating systems are not likely to trigger the Debugger's call to addCallBreak(), so no + * call breaks will be generated, and this code will never execute. + * + * TODO: If we ever need this to be mode-independent, it can be moved somewhere where it will + * trigger for both real and protected-mode code segment loads, because CALLBREAK_SEL (0x0001) + * is also a very unlikely real-mode CS value (but again, not inconceivable). I think this is + * a reasonable solution, and it's likely the best we can do without injecting code into the + * machine that we could address -- and even then, it would not be a mode-independent address. + */ + if (fCall && sel == X86Seg.CALLBREAK_SEL && this.aCallBreaks.length) { + var iBreak = this.offIP - 1; + var fnCallBreak = this.aCallBreaks[iBreak]; + cpu.assert(fnCallBreak); + if (fnCallBreak && !fnCallBreak()) { + return X86.ADDR_INVALID; + } + } + var rpl = sel & X86.SEL.RPL; var dpl = (acc & X86.DESC.ACC.DPL.MASK) >> X86.DESC.ACC.DPL.SHIFT; @@ -789,7 +845,7 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe) this.sizeFrame = sizeGate; - cpu.regEIP = limit; + this.offIP = limit; cpu.assert(this.cpl == cplNew); if (this.cpl < cplOld) { @@ -943,9 +999,12 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe) default: /* - * The only other case used to be X86Seg.ID.DBG, but the Debugger uses probeDesc() now, so we should never get here. + * The only other cases are: + * + * X86Seg.ID.NULL, X86Seg.ID.LDT, and X86Seg.ID.DBG + * + * which correspond to segNULL, segLDT and segDebugger; however, segLDT is the only one that might require further validation (TODO: Investigate). */ - cpu.assert(false); break; }