diff --git a/modules/pcjs/lib/debugger.js b/modules/pcjs/lib/debugger.js index 6b1254804..f18edd46a 100644 --- a/modules/pcjs/lib/debugger.js +++ b/modules/pcjs/lib/debugger.js @@ -3365,7 +3365,7 @@ if (DEBUGGER) { /* * Halt if running with interrupts disabled and IOPL < CPL, because that's likely an error */ - if (!(this.cpu.regPS & X86.PS.IF) && this.cpu.nIOPL < this.cpu.nCPL) { + if (MAXDEBUG && !(this.cpu.regPS & X86.PS.IF) && this.cpu.nIOPL < this.cpu.nCPL) { this.printMessage("interrupts disabled at IOPL " + this.cpu.nIOPL + " and CPL " + this.cpu.nCPL, true); return true; } @@ -6931,6 +6931,8 @@ if (DEBUGGER) { var ch = sCmd.charAt(0); if (ch == '"' || ch == "'") return true; + this.sMessagePrev = null; + /* * I've relaxed the !isBusy() requirement, to maximize our ability to issue Debugger commands externally. */ diff --git a/modules/pcjs/lib/x86cpu.js b/modules/pcjs/lib/x86cpu.js index 908bd00a2..db26840d1 100644 --- a/modules/pcjs/lib/x86cpu.js +++ b/modules/pcjs/lib/x86cpu.js @@ -3922,6 +3922,7 @@ X86CPU.prototype.popWord = function() */ X86CPU.prototype.pushData = function(d, size) { + this.assert(size == 2 || size == 4); this.regLSP = (this.regLSP - size)|0; /* * Properly comparing regLSP to regLSPLimitLow would normally require coercing both to unsigned diff --git a/modules/pcjs/lib/x86func.js b/modules/pcjs/lib/x86func.js index 1a851bb27..649d7e7b3 100644 --- a/modules/pcjs/lib/x86func.js +++ b/modules/pcjs/lib/x86func.js @@ -353,6 +353,151 @@ X86.fnBTS = function BTS(dst, src) return dst | bit; }; +/** + * fnBTMem(dst, src) + * + * In this form of BT, src is a register operand, which is NOT truncated to mod 32 if dst is a memory operand; + * however, if dst is also a register operand, then we defer to the simpler function, fnBT(). + * + * @this {X86CPU} + * @param {number} dst + * @param {number} src + * @return {number} + */ +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) { + /* + * offByte is src divided by 8, but now we need src divided 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. + */ + var i = src >>> (this.sizeData == 2? 4 : 5); + dst = this.getWord(this.regEA += i * this.sizeData); + } + /* + * Now we convert src from a bit index into a bit mask. + */ + src = 1 << (src & (this.sizeData == 2? 0xf : 0x1f)); + if (dst & src) this.setCF(); else this.clearCF(); + + this.nStepCycles -= 6; + this.opFlags |= X86.OPFLAG.NOWRITE; + return dst; +}; + +/** + * fnBTCMem(dst, src) + * + * In this form of BTC, src is a register operand, which is NOT truncated to mod 32 if dst is a memory operand; + * however, if dst is also a register operand, then we defer to the simpler function, fnBTC(). + * + * @this {X86CPU} + * @param {number} dst + * @param {number} src + * @return {number} + */ +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) { + /* + * offByte is src divided by 8, but now we need src divided 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. + */ + var i = src >>> (this.sizeData == 2? 4 : 5); + dst = this.getWord(this.regEA += i * this.sizeData); + } + /* + * Now we convert src from a bit index into a bit mask. + */ + src = 1 << (src & (this.sizeData == 2? 0xf : 0x1f)); + if (dst & src) this.setCF(); else this.clearCF(); + + this.nStepCycles -= 8; + return dst ^ src; +}; + +/** + * fnBTRMem(dst, src) + * + * In this form of BTR, src is a register operand, which is NOT truncated to mod 32 if dst is a memory operand; + * however, if dst is also a register operand, then we defer to the simpler function, fnBTR(). + * + * @this {X86CPU} + * @param {number} dst + * @param {number} src + * @return {number} + */ +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) { + /* + * offByte is src divided by 8, but now we need src divided 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. + */ + var i = src >>> (this.sizeData == 2? 4 : 5); + dst = this.getWord(this.regEA += i * this.sizeData); + } + /* + * Now we convert src from a bit index into a bit mask. + */ + src = 1 << (src & (this.sizeData == 2? 0xf : 0x1f)); + if (dst & src) this.setCF(); else this.clearCF(); + + this.nStepCycles -= 8; + return dst & ~src; +}; + +/** + * fnBTSMem(dst, src) + * + * In this form of BTS, src is a register operand, which is NOT truncated to mod 32 if dst is a memory operand; + * however, if dst is also a register operand, then we defer to the simpler function, fnBTS(). + * + * @this {X86CPU} + * @param {number} dst + * @param {number} src + * @return {number} + */ +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) { + /* + * offByte is src divided by 8, but now we need src divided 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. + */ + var i = src >>> (this.sizeData == 2? 4 : 5); + dst = this.getWord(this.regEA += i * this.sizeData); + } + /* + * Now we convert src from a bit index into a bit mask. + */ + src = 1 << (src & (this.sizeData == 2? 0xf : 0x1f)); + if (dst & src) this.setCF(); else this.clearCF(); + + this.nStepCycles -= 8; + return dst | src; +}; + /** * fnCALLw(dst, src) * @@ -1246,10 +1391,11 @@ X86.fnINT = function INT(nIDT, nError, nCycles) var oldIP = this.getIP(); var addr = this.segCS.loadIDT(nIDT); if (addr !== X86.ADDR_INVALID) { - this.pushWord(oldPS); - this.pushWord(oldCS); - this.pushWord(oldIP); - if (nError != null) this.pushWord(nError); + var size = this.segCS.sizeFrame; + this.pushData(oldPS, size); + this.pushData(oldCS, size); + this.pushData(oldIP, size); + if (nError != null) this.pushData(nError, size); this.nFault = -1; /* * TODO: Should this code be factored into a setLIP() function? The other primary client would be setCSIP(). diff --git a/modules/pcjs/lib/x86op0f.js b/modules/pcjs/lib/x86op0f.js index a976107db..40c954fd8 100644 --- a/modules/pcjs/lib/x86op0f.js +++ b/modules/pcjs/lib/x86op0f.js @@ -1128,7 +1128,7 @@ X86.opPOPFS = function POPFS() */ X86.opBT = function BT() { - this.aOpModMemWord[this.getIPByte()].call(this, X86.fnBT); + this.aOpModMemWord[this.getIPByte()].call(this, X86.fnBTMem); if (this.regEA !== X86.ADDR_INVALID) this.nStepCycles -= 6; }; @@ -1195,7 +1195,7 @@ X86.opPOPGS = function POPGS() */ X86.opBTS = function BTS() { - this.aOpModMemWord[this.getIPByte()].call(this, X86.fnBTS); + this.aOpModMemWord[this.getIPByte()].call(this, X86.fnBTSMem); if (this.regEA !== X86.ADDR_INVALID) this.nStepCycles -= 5; }; @@ -1260,7 +1260,7 @@ X86.opLSS = function LSS() */ X86.opBTR = function BTR() { - this.aOpModMemWord[this.getIPByte()].call(this, X86.fnBTR); + this.aOpModMemWord[this.getIPByte()].call(this, X86.fnBTRMem); if (this.regEA !== X86.ADDR_INVALID) this.nStepCycles -= 5; }; @@ -1424,7 +1424,7 @@ X86.opGRP8 = function GRP8() */ X86.opBTC = function BTC() { - this.aOpModMemWord[this.getIPByte()].call(this, X86.fnBTC); + this.aOpModMemWord[this.getIPByte()].call(this, X86.fnBTCMem); if (this.regEA !== X86.ADDR_INVALID) this.nStepCycles -= 5; }; @@ -1602,8 +1602,8 @@ X86.aOps0F[0xA6] = X86.opInvalid; /* * When Windows 95 Setup initializes in protected-mode, it sets a DPMI exception handler for UD_FAULT and * then attempts to generate that exception with undefined opcode 0x0F,0xFF. Apparently, whoever wrote that code - * (davidw?) didn't get the Intel memo regarding the preferred invalid opcode (0x0F,0x0B, aka UD2), or perhaps Intel - * hadn't written that memo yet -- although if that's the case, then Intel should have followed Microsoft's lead and + * didn't get the Intel memo regarding the preferred invalid opcode (0x0F,0x0B, aka UD2), or perhaps Intel hadn't + * written that memo yet -- although if that's the case, then Intel should have followed Microsoft's lead and * selected 0x0F,0xFF instead of 0x0F,0x0B. * * In any case, this means we need to explicitly set the handler for that opcode to opInvalid(), too. diff --git a/modules/pcjs/lib/x86seg.js b/modules/pcjs/lib/x86seg.js index 934c3cfd0..0dc9025c7 100644 --- a/modules/pcjs/lib/x86seg.js +++ b/modules/pcjs/lib/x86seg.js @@ -126,6 +126,7 @@ function X86Seg(cpu, id, sName, fProt) */ 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) : []); this.updateMode(true, fProt); } @@ -273,6 +274,7 @@ X86Seg.prototype.loadIDTReal = function loadIDTReal(nIDT) */ var addrIDT = cpu.addrIDT + (nIDT << 2); var off = cpu.getShort(addrIDT); + this.sizeFrame = 2; cpu.regPS &= ~(X86.PS.TF | X86.PS.IF); return (this.load(cpu.getShort(addrIDT + 2)) + off)|0; }; @@ -632,11 +634,13 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe) case X86Seg.ID.CODE: this.fStackSwitch = false; + this.sizeFrame = this.sizeData; + var fCall = this.fCall; var rpl = sel & X86.SEL.RPL; var dpl = (acc & X86.DESC.ACC.DPL.MASK) >> X86.DESC.ACC.DPL.SHIFT; - var fGate, selCode, cplOld, addrTSS, offSP, lenSP, regSPPrev, regSSPrev, regPSClear, regSP; + var sizeGate, selCode, cplOld, addrTSS, offSP, lenSP, regSPPrev, regSSPrev, regPSClear, regSP; /* * TODO: As discussed below for X86Seg.ID.DATA, it's likely that testing the PRESENT bit should @@ -672,7 +676,7 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe) cpu.setSP(regSP); this.fStackSwitch = true; } - fGate = false; + sizeGate = 0; } else if (type == X86.DESC.ACC.TYPE.TSS286 || type == X86.DESC.ACC.TYPE.TSS386) { if (!this.switchTSS(sel, fCall)) { @@ -680,18 +684,33 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe) } return this.base; } - else if (type == X86.DESC.ACC.TYPE.GATE_CALL || type == X86.DESC.ACC.TYPE.GATE386_CALL) { - fGate = true; + else if (type == X86.DESC.ACC.TYPE.GATE_CALL) { + sizeGate = 2; regPSClear = 0; if (rpl < this.cpl) rpl = this.cpl; // set RPL to max(RPL,CPL) for call gates } - else if (type == X86.DESC.ACC.TYPE.GATE286_INT || type == X86.DESC.ACC.TYPE.GATE386_INT) { - fGate = true; + else if (type == X86.DESC.ACC.TYPE.GATE386_CALL) { + sizeGate = 4; + regPSClear = 0; + if (rpl < this.cpl) rpl = this.cpl; // set RPL to max(RPL,CPL) for call gates + } + else if (type == X86.DESC.ACC.TYPE.GATE286_INT) { + sizeGate = 2; regPSClear = (X86.PS.VM | X86.PS.NT | X86.PS.TF | X86.PS.IF); cpu.assert(!(acc & 0x1f)); } - else if (type == X86.DESC.ACC.TYPE.GATE286_TRAP || type == X86.DESC.ACC.TYPE.GATE386_TRAP) { - fGate = true; + else if (type == X86.DESC.ACC.TYPE.GATE386_INT) { + sizeGate = 4; + regPSClear = (X86.PS.VM | X86.PS.NT | X86.PS.TF | X86.PS.IF); + cpu.assert(!(acc & 0x1f)); + } + else if (type == X86.DESC.ACC.TYPE.GATE286_TRAP) { + sizeGate = 2; + regPSClear = (X86.PS.VM | X86.PS.NT | X86.PS.TF); + cpu.assert(!(acc & 0x1f)); + } + else if (type == X86.DESC.ACC.TYPE.GATE386_TRAP) { + sizeGate = 4; regPSClear = (X86.PS.VM | X86.PS.NT | X86.PS.TF); cpu.assert(!(acc & 0x1f)); } @@ -702,7 +721,7 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe) return this.base; } - if (fGate) { + if (sizeGate) { /* * Note that since GATE_INT/GATE_TRAP descriptors should appear in the IDT only, that means sel * will actually be nIDT * 8, which means the rpl will always be zero; additionally, the nWords @@ -768,6 +787,8 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe) return X86.ADDR_INVALID; } + this.sizeFrame = sizeGate; + cpu.regEIP = limit; cpu.assert(this.cpl == cplNew); @@ -816,7 +837,7 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe) } } - if (fGate !== false) { + if (sizeGate !== 0) { var nError = sel & X86.ERRCODE.SELMASK; if (addrDesc >= cpu.addrIDT && addrDesc < cpu.addrIDTLimit) nError |= X86.ERRCODE.IDT; /*