diff --git a/modules/pcjs/lib/x86cpu.js b/modules/pcjs/lib/x86cpu.js index c36a1498d..d46df6c4e 100644 --- a/modules/pcjs/lib/x86cpu.js +++ b/modules/pcjs/lib/x86cpu.js @@ -1821,8 +1821,8 @@ X86CPU.prototype.getCS = function() X86CPU.prototype.setCS = function(sel) { var regEIP = this.getIP(); - this.regLIP = this.segCS.load(sel) + regEIP; - this.regLIPLimit = this.segCS.base + this.segCS.limit; + this.regLIP = (this.segCS.load(sel) + regEIP)|0; + this.regLIPLimit = (this.segCS.base + this.segCS.limit)|0; if (I386) this.resetSizes(); if (!BUGS_8086) this.opFlags |= this.OPFLAG_NOINTR_8086; if (PREFETCH) this.flushPrefetch(this.regLIP); @@ -1872,12 +1872,12 @@ X86CPU.prototype.getSS = function() X86CPU.prototype.setSS = function(sel, fInterruptable) { var regESP = this.getSP(); - this.regLSP = this.segSS.load(sel) + regESP; + this.regLSP = (this.segSS.load(sel) + regESP)|0; if (this.segSS.fExpDown) { - this.regLSPLimit = this.segSS.base + this.segSS.addrMask; - this.regLSPLimitLow = this.segSS.base + this.segSS.limit; + this.regLSPLimit = (this.segSS.base + this.segSS.addrMask)|0; + this.regLSPLimitLow = (this.segSS.base + this.segSS.limit)|0; } else { - this.regLSPLimit = this.segSS.base + this.segSS.limit; + this.regLSPLimit = (this.segSS.base + this.segSS.limit)|0; this.regLSPLimitLow = this.segSS.base; } if (!BUGS_8086 && !fInterruptable) this.opFlags |= X86.OPFLAG.NOINTR; @@ -1966,7 +1966,7 @@ X86CPU.prototype.setGS = function(sel) */ X86CPU.prototype.getIP = function() { - return this.regLIP - this.segCS.base; + return (this.regLIP - this.segCS.base)|0; }; /** @@ -1983,7 +1983,7 @@ X86CPU.prototype.getIP = function() */ X86CPU.prototype.setIP = function(off) { - this.regLIP = this.segCS.base + (off & (I386? this.dataMask : 0xffff)); + this.regLIP = (this.segCS.base + (off & (I386? this.dataMask : 0xffff)))|0; if (PREFETCH) this.flushPrefetch(this.regLIP); }; @@ -2018,8 +2018,8 @@ X86CPU.prototype.setCSIP = function(off, sel, fCall) this.regEIP = off; var base = this.segCS.load(sel); if (base !== X86.ADDR_INVALID) { - this.regLIP = base + (this.regEIP & (I386? this.dataMask : 0xffff)); - this.regLIPLimit = base + this.segCS.limit; + this.regLIP = (base + (this.regEIP & (I386? this.dataMask : 0xffff)))|0; + this.regLIPLimit = (base + this.segCS.limit)|0; if (I386) this.resetSizes(); if (PREFETCH) this.flushPrefetch(this.regLIP); return this.segCS.fStackSwitch; @@ -2039,23 +2039,76 @@ X86CPU.prototype.setCSBase = function(addr) { var regIP = this.getIP(); addr = this.segCS.setBase(addr); - this.regLIP = addr + regIP; - this.regLIPLimit = addr + this.segCS.limit; + this.regLIP = (addr + regIP)|0; + this.regLIPLimit = (addr + this.segCS.limit)|0; }; /** * advanceIP(inc) * * @this {X86CPU} - * @param {number} inc (may be +/-) + * @param {number} inc (positive) */ X86CPU.prototype.advanceIP = function(inc) { - this.regLIP += inc; - if (this.regLIP <= this.regLIPLimit) { - if (PREFETCH) this.advancePrefetch(inc); - } else { - this.setIP(this.regLIP - this.segCS.base); + this.assert(inc > 0); + this.regLIP = (this.regLIP + inc)|0; + /* + * Properly comparing regLIP to regLIPLimit would normally require coercing both to unsigned + * (ie, floating-point) values. But instead, we do a subtraction, (regLIPLimit - regLIP), and + * if the result is negative, we need only be concerned if the signs of both numbers are the same + * (ie, the sign of their XOR'ed union is positive). + * + * TODO: I'm combining the old 8088 address-wrap check with the new segment-limit check, + * even though the correct time to do the latter is immediately BEFORE a fetch, not AFTER; eg, + * consider the following code: + * + * AX=0100 BX=0015 CX=0080 DX=F859 SP=0A62 BP=0A98 SI=0000 DI=0000 + * SS=0038[1759E0,0B5F] DS=02E8[0107A0,017F] ES=0970[009700,6949] A20=ON + * CS=02E0[010080,06FB] LD=0028[000000,0000] GD=[11AEE0,4977] ID=[120082,03FF] + * TR=0010 MS=FFF3 PS=3202 V0 D0 I1 T0 S0 Z0 A0 P0 C0 + * 02E0:06F9 C20400 RET 0004 + * + * After fetching the 3rd byte of the "RET 0004" opcode at CS:06FB, the CPU wants to automatically + * advance IP to 06FC, which of course, exceeds the limit, but that doesn't matter unless we actually + * fetch a byte from 06FC, which won't happen. I'm working around this for now by applying a -1 + * fudge factor to the fault check. + */ + var off = (this.regLIPLimit - this.regLIP)|0; + if (off < 0 && (this.regLIPLimit ^ this.regLIP) >= 0) { + if (this.model <= X86.MODEL_8088) { + this.setIP(this.regLIP - this.segCS.base); + } else if (off < -1) { // fudge factor + X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0); + } + } +}; + +/** + * rewindIP(dec) + * + * @this {X86CPU} + * @param {number} dec (negative) + */ +X86CPU.prototype.rewindIP = function(dec) +{ + this.assert(dec < 0); + this.regLIP = (this.regLIP + dec)|0; + /* + * Properly comparing regLIP to regLIPLimit would normally require coercing both to unsigned + * (ie, floating-point) values. But instead, we do a subtraction, (regLIPLimit - regLIP), and + * if the result is negative, we need only be concerned if the signs of both numbers are the same + * (ie, the sign of their XOR'ed union is positive). + */ + if (((this.regLIPLimit - this.regLIP)|0) >= 0) { + if (PREFETCH) this.advancePrefetch(dec); + } + else if ((this.regLIPLimit ^ this.regLIP) >= 0) { + if (this.model <= X86.MODEL_8088) { + this.setIP(this.regLIP - this.segCS.base); + } else { + X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0); + } } }; @@ -2071,7 +2124,7 @@ X86CPU.prototype.getSP = function() this.assert(!((this.regLSP - this.segSS.base) & ~this.segSS.addrMask)); return (this.regESP & ~this.segSS.addrMask) | (this.regLSP - this.segSS.base); } - return this.regLSP - this.segSS.base; + return (this.regLSP - this.segSS.base)|0; }; /** @@ -2084,9 +2137,9 @@ X86CPU.prototype.setSP = function(off) { if (I386) { this.regESP = off; - this.regLSP = this.segSS.base + (off & this.segSS.addrMask); + this.regLSP = (this.segSS.base + (off & this.segSS.addrMask))|0; } else { - this.regLSP = this.segSS.base + off; + this.regLSP = (this.segSS.base + off)|0; } }; @@ -3366,9 +3419,7 @@ X86CPU.prototype.getIPByte = function() { var b = (PREFETCH? this.getBytePrefetch(this.regLIP) : this.getByte(this.regLIP)); if (BACKTRACK) this.bus.updateBackTrackCode(this.regLIP, this.backTrack.btiMemLo); - if (++this.regLIP > this.regLIPLimit) { - this.setIP(this.regLIP - this.segCS.base); - } + this.advanceIP(1); return b; }; @@ -3385,10 +3436,7 @@ X86CPU.prototype.getIPShort = function() this.bus.updateBackTrackCode(this.regLIP, this.backTrack.btiMemLo); this.bus.updateBackTrackCode(this.regLIP + 1, this.backTrack.btiMemHi); } - this.regLIP += 2; - if (this.regLIP > this.regLIPLimit) { - this.setIP(this.regLIP - this.segCS.base); - } + this.advanceIP(2); return w; }; @@ -3405,10 +3453,7 @@ X86CPU.prototype.getIPLong = function() this.bus.updateBackTrackCode(this.regLIP, this.backTrack.btiMemLo); this.bus.updateBackTrackCode(this.regLIP + 1, this.backTrack.btiMemHi); } - this.regLIP += 4; - if (this.regLIP > this.regLIPLimit) { - this.setIP(this.regLIP - this.segCS.base); - } + this.advanceIP(4); return l; }; @@ -3428,10 +3473,7 @@ X86CPU.prototype.getIPAddr = function() this.bus.updateBackTrackCode(this.regLIP, this.backTrack.btiMemLo); this.bus.updateBackTrackCode(this.regLIP + 1, this.backTrack.btiMemHi); } - this.regLIP += this.addrSize; - if (this.regLIP > this.regLIPLimit) { - this.setIP(this.regLIP - this.segCS.base); - } + this.advanceIP(this.addrSize); return w; }; @@ -3448,10 +3490,7 @@ X86CPU.prototype.getIPWord = function() this.bus.updateBackTrackCode(this.regLIP, this.backTrack.btiMemLo); this.bus.updateBackTrackCode(this.regLIP + 1, this.backTrack.btiMemHi); } - this.regLIP += this.dataSize; - if (this.regLIP > this.regLIPLimit) { - this.setIP(this.regLIP - this.segCS.base); - } + this.advanceIP(this.dataSize); return w; }; @@ -3465,9 +3504,7 @@ X86CPU.prototype.getIPDisp = function() { var w = ((PREFETCH? this.getBytePrefetch(this.regLIP) : this.getByte(this.regLIP)) << 24) >> 24; if (BACKTRACK) this.bus.updateBackTrackCode(this.regLIP, this.backTrack.btiMemLo); - if (++this.regLIP > this.regLIPLimit) { - this.setIP(this.regLIP - this.segCS.base); - } + this.advanceIP(1); return w; }; @@ -3482,9 +3519,7 @@ X86CPU.prototype.getSIBAddr = function(mod) { var b = PREFETCH? this.getBytePrefetch(this.regLIP) : this.getByte(this.regLIP); if (BACKTRACK) this.bus.updateBackTrackCode(this.regLIP, this.backTrack.btiMemLo); - if (++this.regLIP > this.regLIPLimit) { - this.setIP(this.regLIP - this.segCS.base); - } + this.advanceIP(1); return X86ModSIB.aOpModSIB[b].call(this, mod); }; @@ -3497,10 +3532,24 @@ X86CPU.prototype.getSIBAddr = function(mod) X86CPU.prototype.popWord = function() { var w = this.getWord(this.regLSP); - this.regLSP += (I386? this.dataSize : 2); - if (this.regLSP > this.regLSPLimit) { - // TODO: Generate exception in protected mode - this.setSP(this.regLSP - this.segSS.base); + this.regLSP = (this.regLSP + (I386? this.dataSize : 2))|0; + /* + * Properly comparing regLSP to regLSPLimit would normally require coercing both to unsigned + * (ie, floating-point) values. But instead, we do a subtraction, (regLSPLimit - regLSP), and + * if the result is negative, we need only be concerned if the signs of both numbers are the same + * (ie, the sign of their XOR'ed union is positive). + * + * TODO: I'm combining the old 8088 address-wrap check with the new segment-limit check, + * even though the correct time to do the latter is immediately BEFORE the fetch, not AFTER; + * I'm working around this for now by applying a -1 fudge factor to the fault check. + */ + var off = ((this.regLSPLimit - this.regLSP)|0); + if (off < 0 && (this.regLSPLimit ^ this.regLSP) >= 0) { + if (this.model <= X86.MODEL_8088) { + this.setSP(this.regLSP - this.segSS.base); + } else if (off < -1) { // fudge factor + X86.fnFault.call(this, X86.EXCEPTION.SS_FAULT, 0); + } } return w; }; @@ -3514,10 +3563,19 @@ X86CPU.prototype.popWord = function() X86CPU.prototype.pushWord = function(w) { this.assert((w & this.dataMask) == w); - this.regLSP -= (I386? this.dataSize : 2); - if (this.regLSP < this.regLSPLimitLow) { - // TODO: Generate exception in protected mode (and bail) - this.setSP(this.regLSP - this.segSS.base); + this.regLSP = (this.regLSP - (I386? this.dataSize : 2))|0; + /* + * Properly comparing regLSP to regLSPLimitLow would normally require coercing both to unsigned + * (ie, floating-point) values. But instead, we do a subtraction, (regLSP - regLSPLimitLow), and + * if the result is negative, we need only be concerned if the signs of both numbers are the same + * (ie, the sign of their XOR'ed union is positive). + */ + if (((this.regLSP - this.regLSPLimitLow)|0) < 0 && (this.regLSPLimitLow ^ this.regLSP) >= 0) { + if (this.model <= X86.MODEL_8088) { + this.setSP(this.regLSP - this.segSS.base); + } else { + X86.fnFault.call(this, X86.EXCEPTION.SS_FAULT, 0); + } } this.setWord(this.regLSP, w); }; diff --git a/modules/pcjs/lib/x86func.js b/modules/pcjs/lib/x86func.js index 5065d9d52..ce539c65c 100644 --- a/modules/pcjs/lib/x86func.js +++ b/modules/pcjs/lib/x86func.js @@ -1197,8 +1197,9 @@ X86.fnINCw = function INCw(dst, src) * only knows how to load GDT and LDT descriptors, whereas interrupts must use setCS.loadIDT(), which * deals exclusively with IDT descriptors. * - * This means we must take care to replicate critical features of setCSIP(); eg, setting segCS.fCall - * before calling loadIDT(), updating LIP, and flushing the prefetch queue. + * This means we must take care to replicate critical features of setCSIP(); ie, setting segCS.fCall + * BEFORE calling loadIDT(), and updating regLIP and regLIPLimit, resetting default operand and address sizes, + * and flushing the prefetch queue AFTER calling loadIDT(). * * @this {X86CPU} * @param {number} nIDT @@ -1217,13 +1218,15 @@ X86.fnINT = function INT(nIDT, nError, nCycles) var oldIP = this.getIP(); var addr = this.segCS.loadIDT(nIDT); if (addr !== X86.ADDR_INVALID) { - this.regLIP = addr; - if (PREFETCH) this.flushPrefetch(this.regLIP); this.pushWord(oldPS); this.pushWord(oldCS); this.pushWord(oldIP); if (nError != null) this.pushWord(nError); this.nFault = -1; + this.regLIP = addr; + this.regLIPLimit = (this.segCS.base + this.segCS.limit)|0; + if (I386) this.resetSizes(); + if (PREFETCH) this.flushPrefetch(this.regLIP); } }; diff --git a/modules/pcjs/lib/x86ops.js b/modules/pcjs/lib/x86ops.js index 47cfc033c..5daf0363a 100644 --- a/modules/pcjs/lib/x86ops.js +++ b/modules/pcjs/lib/x86ops.js @@ -1483,7 +1483,7 @@ X86.opINSb = function INSb() this.regECX = (this.regECX & ~this.addrMask) | ((this.regECX - nDelta) & this.addrMask); if (nReps) { if (BUGS_8086) { - this.advanceIP(-2); // this instruction does not support multiple overrides + this.rewindIP(-2); // this instruction does not support multiple overrides this.assert(this.regLIP == this.opLIP); } else { this.regLIP = this.opLIP; @@ -1540,7 +1540,7 @@ X86.opINSw = function INSw() this.regECX = (this.regECX & ~this.addrMask) | ((this.regECX - nDelta) & this.addrMask); if (nReps) { if (BUGS_8086) { - this.advanceIP(-2); // this instruction does not support multiple overrides + this.rewindIP(-2); // this instruction does not support multiple overrides this.assert(this.regLIP == this.opLIP); } else { this.regLIP = this.opLIP; @@ -1585,7 +1585,7 @@ X86.opOUTSb = function OUTSb() this.regECX = (this.regECX & ~this.addrMask) | ((this.regECX - nDelta) & this.addrMask); if (nReps) { if (BUGS_8086) { - this.advanceIP(-2); // this instruction does not support multiple overrides + this.rewindIP(-2); // this instruction does not support multiple overrides this.assert(this.regLIP == this.opLIP); } else { this.regLIP = this.opLIP; @@ -1640,7 +1640,7 @@ X86.opOUTSw = function OUTSw() this.regECX = (this.regECX & ~this.addrMask) | ((this.regECX - nDelta) & this.addrMask); if (nReps) { if (BUGS_8086) { - this.advanceIP(-2); // this instruction does not support multiple overrides + this.rewindIP(-2); // this instruction does not support multiple overrides this.assert(this.regLIP == this.opLIP); } else { this.regLIP = this.opLIP; @@ -2558,7 +2558,7 @@ X86.opMOVSb = function MOVSb() this.regECX = (this.regECX & ~this.addrMask) | ((this.regECX - nDelta) & this.addrMask); if (nReps) { if (BUGS_8086) { - this.advanceIP(((this.opPrefixes & X86.OPFLAG.SEG)? -3 : -2)); + this.rewindIP(((this.opPrefixes & X86.OPFLAG.SEG)? -3 : -2)); this.assert(this.regLIP == this.opLIP); } else { this.regLIP = this.opLIP; @@ -2593,7 +2593,7 @@ X86.opMOVSw = function MOVSw() this.regECX = (this.regECX & ~this.addrMask) | ((this.regECX - nDelta) & this.addrMask); if (nReps) { if (BUGS_8086) { - this.advanceIP(((this.opPrefixes & X86.OPFLAG.SEG)? -3 : -2)); + this.rewindIP(((this.opPrefixes & X86.OPFLAG.SEG)? -3 : -2)); this.assert(this.regLIP == this.opLIP); } else { this.regLIP = this.opLIP; @@ -2638,7 +2638,7 @@ X86.opCMPSb = function CMPSb() */ if (nReps && this.getZF() == (this.opPrefixes & X86.OPFLAG.REPZ)) { if (BUGS_8086) { - this.advanceIP(((this.opPrefixes & X86.OPFLAG.SEG)? -3 : -2)); + this.rewindIP(((this.opPrefixes & X86.OPFLAG.SEG)? -3 : -2)); this.assert(this.regLIP == this.opLIP); } else { this.regLIP = this.opLIP; @@ -2683,7 +2683,7 @@ X86.opCMPSw = function CMPSw() */ if (nReps && this.getZF() == (this.opPrefixes & X86.OPFLAG.REPZ)) { if (BUGS_8086) { - this.advanceIP(((this.opPrefixes & X86.OPFLAG.SEG)? -3 : -2)); + this.rewindIP(((this.opPrefixes & X86.OPFLAG.SEG)? -3 : -2)); this.assert(this.regLIP == this.opLIP); } else { this.regLIP = this.opLIP; @@ -2741,7 +2741,7 @@ X86.opSTOSb = function STOSb() this.regECX = (this.regECX & ~this.addrMask) | ((this.regECX - nDelta) & this.addrMask); if (nReps) { if (BUGS_8086) { - this.advanceIP(-2); // this instruction does not support multiple overrides + this.rewindIP(-2); // this instruction does not support multiple overrides this.assert(this.regLIP == this.opLIP); } else { this.regLIP = this.opLIP; @@ -2783,7 +2783,7 @@ X86.opSTOSw = function STOSw() this.regECX = (this.regECX & ~this.addrMask) | ((this.regECX - nDelta) & this.addrMask); if (nReps) { if (BUGS_8086) { - this.advanceIP(-2); // this instruction does not support multiple overrides + this.rewindIP(-2); // this instruction does not support multiple overrides this.assert(this.regLIP == this.opLIP); } else { this.regLIP = this.opLIP; @@ -2817,7 +2817,7 @@ X86.opLODSb = function LODSb() this.regECX = (this.regECX & ~this.addrMask) | ((this.regECX - nDelta) & this.addrMask); if (nReps) { if (BUGS_8086) { - this.advanceIP(((this.opPrefixes & X86.OPFLAG.SEG)? -3 : -2)); + this.rewindIP(((this.opPrefixes & X86.OPFLAG.SEG)? -3 : -2)); this.assert(this.regLIP == this.opLIP); } else { this.regLIP = this.opLIP; @@ -2853,7 +2853,7 @@ X86.opLODSw = function LODSw() this.regECX = (this.regECX & ~this.addrMask) | ((this.regECX - nDelta) & this.addrMask); if (nReps) { if (BUGS_8086) { - this.advanceIP(((this.opPrefixes & X86.OPFLAG.SEG)? -3 : -2)); + this.rewindIP(((this.opPrefixes & X86.OPFLAG.SEG)? -3 : -2)); this.assert(this.regLIP == this.opLIP); } else { this.regLIP = this.opLIP; @@ -2894,7 +2894,7 @@ X86.opSCASb = function SCASb() */ if (nReps && this.getZF() == (this.opPrefixes & X86.OPFLAG.REPZ)) { if (BUGS_8086) { - this.advanceIP(-2); // this instruction does not support multiple overrides + this.rewindIP(-2); // this instruction does not support multiple overrides this.assert(this.regLIP == this.opLIP); } else { this.regLIP = this.opLIP; @@ -2935,7 +2935,7 @@ X86.opSCASw = function SCASw() */ if (nReps && this.getZF() == (this.opPrefixes & X86.OPFLAG.REPZ)) { if (BUGS_8086) { - this.advanceIP(-2); // this instruction does not support multiple overrides + this.rewindIP(-2); // this instruction does not support multiple overrides this.assert(this.regLIP == this.opLIP); } else { this.regLIP = this.opLIP; @@ -3806,7 +3806,7 @@ X86.opHLT = function HLT() * on the theory that whoever's using the Debugger would like to see HLTs. */ if (DEBUGGER && this.dbg && this.messageEnabled(Messages.HALT)) { - this.advanceIP(-1); // this is purely for the Debugger's benefit, to show the HLT + this.rewindIP(-1); // this is purely for the Debugger's benefit, to show the HLT this.stopCPU(); return; } @@ -3815,7 +3815,7 @@ X86.opHLT = function HLT() * in the water (we have no NMI generation mechanism at the moment). */ if (!this.getIF()) { - if (DEBUGGER && this.dbg) this.advanceIP(-1); + if (DEBUGGER && this.dbg) this.rewindIP(-1); this.stopCPU(); } }; diff --git a/modules/pcjs/lib/x86seg.js b/modules/pcjs/lib/x86seg.js index 2d2e0f598..6821d9148 100644 --- a/modules/pcjs/lib/x86seg.js +++ b/modules/pcjs/lib/x86seg.js @@ -853,6 +853,9 @@ X86Seg.prototype.switchTSS = function switchTSS(selNew, fNest) * but the CS base must be set to 0x00FF0000 or 0xFFFF0000, respectively. To simplify life for setBase() * callers, we allow them to specify 32-bit bases, which we then truncate to 24 bits as needed. * + * WARNING: Since the CPU must maintain regLIP as the sum of the CS base and the current IP, all calls + * to segCS.setBase() need to go through setCSBase(). + * * @this {X86Seg} * @param {number} addr * @return {number} addr, truncated as needed