diff --git a/modules/pcjs/lib/debugger.js b/modules/pcjs/lib/debugger.js index 25cc4c482..261667123 100644 --- a/modules/pcjs/lib/debugger.js +++ b/modules/pcjs/lib/debugger.js @@ -2945,8 +2945,8 @@ 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.segCS.cpl) { - this.printMessage("interrupts disabled at IOPL " + this.cpu.nIOPL + " and CPL " + this.cpu.segCS.cpl, true); + if (!(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; } } diff --git a/modules/pcjs/lib/x86cpu.js b/modules/pcjs/lib/x86cpu.js index 5f7d377bc..e2b189537 100644 --- a/modules/pcjs/lib/x86cpu.js +++ b/modules/pcjs/lib/x86cpu.js @@ -761,7 +761,7 @@ X86CPU.prototype.mapPageBlock = function(addr, fWrite, fSuppress) return this.memEmpty; } - if (!(pde & X86.PTE.USER) && this.segCS.cpl == 3) { + if (!(pde & X86.PTE.USER) && this.nCPL == 3) { if (!fSuppress) X86.fnPageFault.call(this, addr, true, fWrite); return this.memEmpty; } @@ -781,7 +781,7 @@ X86CPU.prototype.mapPageBlock = function(addr, fWrite, fSuppress) return this.memEmpty; } - if (!(pte & X86.PTE.USER) && this.segCS.cpl == 3) { + if (!(pte & X86.PTE.USER) && this.nCPL == 3) { if (!fSuppress) X86.fnPageFault.call(this, addr, true, fWrite); return this.memEmpty; } @@ -1263,7 +1263,7 @@ X86CPU.prototype.resetRegs = function() */ this.intFlags = X86.INTFLAG.NONE; - this.setCSIP(0, 0xffff); // this should be called before the first setPS() call + this.setCSIP(0, 0xffff); // this should be called before the first setPS() call, in part so that CPL will be set if (!I386) this.resetSizes(); @@ -1331,7 +1331,7 @@ X86CPU.prototype.resetRegs = function() /* * This resets the Processor Status flags (regPS), along with all the internal "result registers"; - * we've taken care to ensure that both segCS.cpl and nIOPL are initialized before this first setPS() call. + * we've taken care to ensure that both CPL and IOPL are initialized before this first setPS() call. */ this.setPS(0); @@ -1846,12 +1846,8 @@ X86CPU.prototype.getCS = function() */ X86CPU.prototype.setCS = function(sel) { - var regEIP = this.getIP(); - this.regLIP = (this.segCS.load(sel) + regEIP)|0; - this.regLIPLimit = (this.segCS.base + this.segCS.limit)|0; - if (I386) this.resetSizes(); + this.setCSIP(this.getIP(), sel); if (!BUGS_8086) this.opFlags |= this.OPFLAG_NOINTR_8086; - if (PREFETCH) this.flushPrefetch(this.regLIP); }; /** @@ -2044,10 +2040,16 @@ X86CPU.prototype.setCSIP = function(off, sel, fCall) this.regEIP = off; var base = this.segCS.load(sel); 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.dataMask : 0xffff)))|0; this.regLIPLimit = (base + this.segCS.limit)|0; - if (I386) this.resetSizes(); + this.nCPL = this.segCS.cpl; // cache the current CPL where it's more convenient if (PREFETCH) this.flushPrefetch(this.regLIP); + return this.segCS.fStackSwitch; } return null; @@ -2713,7 +2715,7 @@ X86CPU.prototype.setPS = function(regPS, cpl) * So, if the CPU is an 80286, we zero incoming bits 12-14 in real-mode (bit 15 is never allowed to * be modified, so there's no need to mask it). And if the CPU is an 80386, we zero only bit 14 (PS.NT), * allowing the IOPL bits to change; however, that should not affect any real-mode operations, since - * segCS.cpl will always be zero, making the IOPL setting irrelevant. + * CPL will always be zero, making IOPL irrelevant. * * It's still an open question whether an 80386 should also clear the Nested Task (PS.NT) flag in * real-mode; if not, then initProcessor() should set PS_CLEAR_RM to zero. @@ -2724,7 +2726,7 @@ X86CPU.prototype.setPS = function(regPS, cpl) * There are some cases (eg, an IRET returning to a less privileged code segment) where the CPL * we compare against should come from the outgoing code segment, so if the caller provided it, use it. */ - if (cpl === undefined) cpl = this.segCS.cpl; + if (cpl === undefined) cpl = this.nCPL; /* * Since PS.IOPL and PS.IF are part of PS_DIRECT, we need to take care of any 80286-specific behaviors @@ -2762,7 +2764,7 @@ X86CPU.prototype.setPS = function(regPS, cpl) X86CPU.prototype.checkIOPM = function(port, nPorts) { var bitsPorts = 0; - if (I386 && (this.regCR0 & X86.CR0.MSW.PE) && (this.segCS.cpl > this.nIOPL || (this.regPS & X86.PS.VM)) && this.segTSS.addrIOPM) { + if (I386 && (this.regCR0 & X86.CR0.MSW.PE) && (this.nCPL > this.nIOPL || (this.regPS & X86.PS.VM)) && this.segTSS.addrIOPM) { var offIOPM = port >>> 3; var addrIOPM = this.segTSS.addrIOPM + offIOPM; bitsPorts = ((1 << nPorts) - 1) << (port & 0x7); diff --git a/modules/pcjs/lib/x86func.js b/modules/pcjs/lib/x86func.js index 5c48d48f0..4003dbc63 100644 --- a/modules/pcjs/lib/x86func.js +++ b/modules/pcjs/lib/x86func.js @@ -1247,9 +1247,14 @@ X86.fnINT = function INT(nIDT, nError, nCycles) this.pushWord(oldIP); if (nError != null) this.pushWord(nError); this.nFault = -1; + + /* + * TODO: Should this code be factored into a setLIP() function? The other primary client would be setCSIP(). + */ + if (I386) this.resetSizes(); this.regLIP = addr; this.regLIPLimit = (this.segCS.base + this.segCS.limit)|0; - if (I386) this.resetSizes(); + this.nCPL = this.segCS.cpl; // cache the current CPL where it's more convenient if (PREFETCH) this.flushPrefetch(this.regLIP); } }; @@ -1265,6 +1270,7 @@ X86.fnIRET = function IRET() * TODO: We assess a fixed cycle cost up front, because at the moment, switchTSS() doesn't assess anything. */ this.nStepCycles -= this.cycleCounts.nOpCyclesIRet; + if (this.regCR0 & X86.CR0.MSW.PE) { if (this.regPS & X86.PS.NT) { var addrNew = this.segTSS.base; @@ -1276,7 +1282,8 @@ X86.fnIRET = function IRET() return; } } - var cpl = this.segCS.cpl; + + var cpl = this.nCPL; var newIP = this.popWord(); var newCS = this.popWord(); var newPS = this.popWord(); @@ -1390,7 +1397,7 @@ X86.fnLAR = function LAR(dst, src) */ this.clearZF(); if (this.segVER.load(src, true) !== X86.ADDR_INVALID) { - if (this.segVER.dpl >= this.segCS.cpl && this.segVER.dpl >= (src & X86.SEL.RPL)) { + if (this.segVER.dpl >= this.nCPL && this.segVER.dpl >= (src & X86.SEL.RPL)) { this.setZF(); dst = this.segVER.acc & ~X86.DESC.ACC.BASE1623; if (this.dataSize > 2) { @@ -1695,7 +1702,7 @@ X86.fnLSL = function LSL(dst, src) */ if ((src & X86.SEL.MASK) && this.segVER.load(src, true) !== X86.ADDR_INVALID) { var fConforming = ((this.segVER.acc & X86.DESC.ACC.TYPE.CODE_CONFORMING) == X86.DESC.ACC.TYPE.CODE_CONFORMING); - if ((fConforming || this.segVER.dpl >= this.segCS.cpl) && this.segVER.dpl >= (src & X86.SEL.RPL)) { + if ((fConforming || this.segVER.dpl >= this.nCPL) && this.segVER.dpl >= (src & X86.SEL.RPL)) { this.setZF(); return this.segVER.limit; } @@ -2256,11 +2263,11 @@ X86.fnRETF = function RETF(n) * it safe and using CODE_CONFORMING instead of CODE_CONFORMING_READABLE. Also, for the record, I've not * seen this situation occur yet (eg, in OS/2 1.0). */ - if ((this.segDS.sel & X86.SEL.MASK) && this.segDS.dpl < this.segCS.cpl && (this.segDS.acc & X86.DESC.ACC.TYPE.CODE_CONFORMING) != X86.DESC.ACC.TYPE.CODE_CONFORMING) { + if ((this.segDS.sel & X86.SEL.MASK) && this.segDS.dpl < this.nCPL && (this.segDS.acc & X86.DESC.ACC.TYPE.CODE_CONFORMING) != X86.DESC.ACC.TYPE.CODE_CONFORMING) { this.assert(false); // I'm not asserting this is bad, I just want to see it in action this.segDS.load(0); } - if ((this.segES.sel & X86.SEL.MASK) && this.segES.dpl < this.segCS.cpl && (this.segES.acc & X86.DESC.ACC.TYPE.CODE_CONFORMING) != X86.DESC.ACC.TYPE.CODE_CONFORMING) { + if ((this.segES.sel & X86.SEL.MASK) && this.segES.dpl < this.nCPL && (this.segES.acc & X86.DESC.ACC.TYPE.CODE_CONFORMING) != X86.DESC.ACC.TYPE.CODE_CONFORMING) { this.assert(false); // I'm not asserting this is bad, I just want to see it in action this.segES.load(0); } @@ -3352,7 +3359,7 @@ X86.fnVERR = function VERR(dst, src) * Otherwise, DPL must be greater than or equal to (have less or the same privilege as) both the * current privilege level and the selector's RPL. */ - if (this.segVER.dpl >= this.segCS.cpl && this.segVER.dpl >= (dst & X86.SEL.RPL) || + if (this.segVER.dpl >= this.nCPL && this.segVER.dpl >= (dst & X86.SEL.RPL) || (this.segVER.acc & X86.DESC.ACC.TYPE.CODE_CONFORMING) == X86.DESC.ACC.TYPE.CODE_CONFORMING) { this.setZF(); return dst; @@ -3390,7 +3397,7 @@ X86.fnVERW = function VERW(dst, src) * DPL must be greater than or equal to (have less or the same privilege as) both the current * privilege level and the selector's RPL. */ - if (this.segVER.dpl >= this.segCS.cpl && this.segVER.dpl >= (dst & X86.SEL.RPL)) { + if (this.segVER.dpl >= this.nCPL && this.segVER.dpl >= (dst & X86.SEL.RPL)) { this.setZF(); return dst; } @@ -3758,7 +3765,7 @@ X86.fnPageFault = function(addr, fPresent, fWrite) var nError = 0; if (fPresent) nError |= X86.PTE.PRESENT; if (fWrite) nError |= X86.PTE.READWRITE; - if (this.segCS.cpl == 3) nError |= X86.PTE.USER; + if (this.nCPL == 3) nError |= X86.PTE.USER; X86.fnFault.call(this, X86.EXCEPTION.PG_FAULT, nError); }; diff --git a/modules/pcjs/lib/x86op0f.js b/modules/pcjs/lib/x86op0f.js index 7c5c5691a..db08c8cfe 100644 --- a/modules/pcjs/lib/x86op0f.js +++ b/modules/pcjs/lib/x86op0f.js @@ -145,9 +145,9 @@ X86.opLSL = function LSL() */ X86.opLOADALL = function LOADALL() { - if (this.segCS.cpl) { + if (this.nCPL) { /* - * You're not allowed to use LOADALL if the current privilege level is something other than zero. + * To use LOADALL, CPL must be zero. */ X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0, true); return; @@ -202,9 +202,9 @@ X86.opLOADALL = function LOADALL() X86.opCLTS = function CLTS() { /* - * NOTE: The following code shouldn't need to test for X86.PS.VM because V86-mode is CPL 3. + * NOTE: The following code shouldn't need to also test X86.PS.VM, because V86-mode is CPL 3. */ - if (this.segCS.cpl) { + if (this.nCPL) { X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0); return; } @@ -234,9 +234,9 @@ X86.opCLTS = function CLTS() X86.opMOVrc = function MOVrc() { /* - * NOTE: The following code shouldn't need to test for X86.PS.VM because V86-mode is CPL 3. + * NOTE: The following code shouldn't need to also test X86.PS.VM, because V86-mode is CPL 3. */ - if (this.segCS.cpl) { + if (this.nCPL) { /* * You're not allowed to read control registers if the current privilege level is not zero */ @@ -317,9 +317,9 @@ X86.opMOVrc = function MOVrc() X86.opMOVcr = function MOVcr() { /* - * NOTE: The following code shouldn't need to test for X86.PS.VM because V86-mode is CPL 3. + * NOTE: The following code shouldn't need to also test X86.PS.VM, because V86-mode is CPL 3. */ - if (this.segCS.cpl) { + if (this.nCPL) { /* * You're not allowed to write control registers if the current privilege level is not zero */ diff --git a/modules/pcjs/lib/x86ops.js b/modules/pcjs/lib/x86ops.js index 73cf52808..c468e2993 100644 --- a/modules/pcjs/lib/x86ops.js +++ b/modules/pcjs/lib/x86ops.js @@ -2453,18 +2453,38 @@ X86.opWAIT = function WAIT() }; /** - * op=0x9C (PUSHF) + * op=0x9C (PUSHF/PUSHFD) * * @this {X86CPU} */ X86.opPUSHF = function PUSHF() { - this.pushWord(this.getPS()); + /* + * TODO: Consider swapping out this function whenever setProtMode() changes the mode to V86-mode. + */ + var regPS = this.getPS(); + if (I386) { + if ((regPS & X86.PS.VM) && this.nIOPL < 3) { + X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0); + return; + } + /* + * It doesn't matter whether this is PUSHF or PUSHFD: the VM and RF flags are never pushed, so + * we can always clear them. + * + * This does, however, beg the question: how does code running in V86-mode detect that's in V86-mode + * and not real-mode? By using the SMSW instruction and checking the PE (protected-mode enabled) bit. + * The SMSW instruction returns a subset of the CR0 bits, and unlike the MOV reg,CR0 instruction, is + * allowed in V86-mode. + */ + regPS &= ~(X86.PS.VM | X86.PS.RF); + } + this.pushWord(regPS); this.nStepCycles -= this.cycleCounts.nOpCyclesPushReg; }; /** - * op=0x9D (POPF) + * op=0x9D (POPF/POPFD) * * @this {X86CPU} */ @@ -2478,7 +2498,7 @@ X86.opPOPF = function POPF() return; } /* - * On the 80386, regardless of mode, VM and RF (the only defined EFLAGS bit above bit 15) are never changed by POPFD. + * Regardless of mode, VM and RF (the only defined EFLAGS bit above bit 15) are never changed by POPFD. */ var newPS = this.popWord(); if (I386) newPS = (newPS & 0xffff) | (this.regPS & ~0xffff); @@ -3471,6 +3491,13 @@ X86.opINTO = function INTO() */ X86.opIRET = function IRET() { + /* + * TODO: Consider swapping out this function whenever setProtMode() changes the mode to V86-mode. + */ + if (I386 && (this.regPS & X86.PS.VM) && this.nIOPL < 3) { + X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0); + return; + } X86.fnIRET.call(this); }; @@ -4058,6 +4085,14 @@ X86.opSTC = function STC() */ X86.opCLI = function CLI() { + /* + * The following code should be sufficient for all modes, because in real-mode, CPL is always zero, + * and in V86-mode, CPL is always 3. + */ + if (this.nCPL > this.nIOPL) { + X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0); + return; + } this.clearIF(); this.nStepCycles -= this.cycleCounts.nOpCyclesCLI; // CLI takes LONGER on an 80286 }; @@ -4069,6 +4104,14 @@ X86.opCLI = function CLI() */ X86.opSTI = function STI() { + /* + * The following code should be sufficient for all modes, because in real-mode, CPL is always zero, + * and in V86-mode, CPL is always 3. + */ + if (this.nCPL > this.nIOPL) { + X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0); + return; + } this.setIF(); this.opFlags |= X86.OPFLAG.NOINTR; this.nStepCycles -= 2; // STI takes 2 cycles on all CPUs