diff --git a/modules/pcjs/lib/debugger.js b/modules/pcjs/lib/debugger.js index 90bb8e2ec..341d11b39 100644 --- a/modules/pcjs/lib/debugger.js +++ b/modules/pcjs/lib/debugger.js @@ -2216,6 +2216,11 @@ if (DEBUGGER) { */ if (DEBUG) { this.assert(!(this.cpu.regAX & ~0xffff) && !(this.cpu.regBX & ~0xffff) && !(this.cpu.regCX & ~0xffff) && !(this.cpu.regDX & ~0xffff), "register out of bounds"); + /* + if (!fSkipBP && this.cInstructions == 11303367) { + return true; + } + */ if (!fSkipBP && MAXDEBUG) { if (!this.cpu.regIP) { this.println("suspicious IP"); @@ -4728,6 +4733,9 @@ if (DEBUGGER) { if (!sCmd) { sCmd = this.aPrevCmds[this.iPrevCmd+1]; } else { + if (this.iPrevCmd < 0 && this.aPrevCmds.length) { + this.iPrevCmd = 0; + } if (this.iPrevCmd < 0 || sCmd != this.aPrevCmds[this.iPrevCmd]) { this.aPrevCmds.splice(0, 0, sCmd); this.iPrevCmd = 0; diff --git a/modules/pcjs/lib/x86cpu.js b/modules/pcjs/lib/x86cpu.js index 148479124..6980e9704 100644 --- a/modules/pcjs/lib/x86cpu.js +++ b/modules/pcjs/lib/x86cpu.js @@ -1308,7 +1308,7 @@ X86CPU.prototype.setIP = function(off) * @param {number} off * @param {number} sel * @param {boolean} [fCall] is true if CALLF in progress, false if RETF in progress, null/undefined otherwise - * @return {boolean} true if a stack switch occurred; the only opcode that needs to care about this is opRETFn() + * @return {boolean|null} true if a stack switch occurred; the only opcode that really needs to care is opRETFn() */ X86CPU.prototype.setCSIP = function(off, sel, fCall) { @@ -1316,13 +1316,14 @@ X86CPU.prototype.setCSIP = function(off, sel, fCall) this.segCS.fCall = fCall; /* * We break this operation into the following discrete steps (eg, set IP, load CS, and then update EIP) - * so that segCS.load(sel) has the option of modifying IP when sel refers to a call gate. + * so that segCS.load(sel) has the option of modifying IP when sel refers to a gate (call, interrupt, trap, etc). */ this.regIP = off; var base = this.segCS.load(sel); - if (base != null) { - this.regEIP = base + this.regIP; + if (base == null) { + return null; } + this.regEIP = base + this.regIP; if (PREFETCH) this.flushPrefetch(this.regEIP); return this.segCS.fStackSwitch; }; diff --git a/modules/pcjs/lib/x86help.js b/modules/pcjs/lib/x86help.js index ab801ddcc..511fe560b 100644 --- a/modules/pcjs/lib/x86help.js +++ b/modules/pcjs/lib/x86help.js @@ -448,9 +448,10 @@ var X86Help = { opHelpCallF: function(off, sel) { var regCS = this.segCS.sel; var regIP = this.regIP; - this.setCSIP(off, sel, true); - this.pushWord(regCS); - this.pushWord(regIP); + if (this.setCSIP(off, sel, true) != null) { + this.pushWord(regCS); + this.pushWord(regIP); + } }, /** * opHelpDIVOverflow() @@ -474,10 +475,22 @@ var X86Help = { */ opHelpINT: function(nIDT, nError, nCycles) { /* - * TODO: We assess the cycle cost up front, because otherwise, if opHelpLoadIDT() fails and we end up in - * opHelpFault(), no cost may be assessed. Ultimately, opHelpFault() needs to determine an appropriate cost. + * TODO: We assess the cycle cost up front, because otherwise, if loadIDT() fails, no cost may be assessed. */ this.nStepCycles -= this.CYCLES.nOpCyclesInt + nCycles; + var regPS = this.getPS(); + var regCS = this.segCS.sel; + var regIP = this.regIP; + var base = this.segCS.loadIDT(nIDT); + if (base != null) { + this.regEIP = base + this.regIP; + this.pushWord(regPS); + this.pushWord(regCS); + this.pushWord(regIP); + if (nError != null) this.pushWord(nError); + this.nFault = -1; + } + /* if (X86Help.opHelpLoadIDT.call(this, nIDT)) { if (this.descIDT.maskPS) { X86Help.opHelpPushPS.call(this, nError); @@ -487,6 +500,7 @@ var X86Help = { return; } X86Help.opHelpFault.call(this, X86.EXCEPTION.GP_FAULT, (nIDT << 3) | X86.ERRCODE.IDT | X86.ERRCODE.EXT, true); + */ }, /** * opHelpIRET() @@ -506,9 +520,13 @@ var X86Help = { return; } } - this.setCSIP(this.popWord(), this.popWord(), false); - this.setPS(this.popWord()); - if (this.cIntReturn) this.checkIntReturn(this.regEIP); + var regIP = this.popWord(); + var regCS = this.popWord(); + var regPS = this.popWord(); + if (this.setCSIP(regIP, regCS, false) != null) { + this.setPS(regPS); + if (this.cIntReturn) this.checkIntReturn(this.regEIP); + } }, /** * opHelpLoadIDT(nIDT) @@ -568,30 +586,6 @@ var X86Help = { this.descIDT.maskPS = ~(X86.PS.TF | X86.PS.IF); return true; }, - /** - * opHelpPushPS(nError) - * - * Helper to push processor state, CS:IP, and optional error code onto the stack, and then jump - * to whatever CS:IP was fetched into descIDT by opHelpLoadIDT(). - * - * For protected-mode, this function must attempt to load the new code segment first, because if the new segment - * requires a change in privilege level, the return address must be pushed on the NEW stack, not the current stack. - * - * @this {X86CPU} - * @param {number|null|undefined} nError - */ - opHelpPushPS: function(nError) { - var regPS = this.getPS(); - var regCS = this.segCS.sel; - var regIP = this.regIP; - this.regPS &= this.descIDT.maskPS; - this.setCSIP(this.descIDT.off, this.descIDT.sel, true); - this.pushWord(regPS); - this.pushWord(regCS); - this.pushWord(regIP); - if (nError != null) this.pushWord(nError); - this.nFault = -1; - }, /** * opHelpSwitchTSS(selNew, fNest) * diff --git a/modules/pcjs/lib/x86seg.js b/modules/pcjs/lib/x86seg.js index f82a43b25..c0495869f 100644 --- a/modules/pcjs/lib/x86seg.js +++ b/modules/pcjs/lib/x86seg.js @@ -115,8 +115,6 @@ X86Seg.ID = { X86Seg.loadReal = function loadReal(sel, fSuppress) { this.sel = sel; - this.limit = 0xffff; - this.cpl = this.dpl = 0; return this.base = sel << 4; }; @@ -126,7 +124,7 @@ X86Seg.loadReal = function loadReal(sel, fSuppress) * This replaces the segment's default load() function whenever the segment is notified via updateAccess() by the * CPU's setProtMode() that the processor is now in protected-mode. * - * Segments in protected-mode are referenced by selectors, which are indexes into descriptor tables (GDT, LDT, IDT) + * Segments in protected-mode are referenced by selectors, which are indexes into descriptor tables (GDT or LDT) * whose descriptors are 4-word (8-byte) entries: * * word 0: segment limit (0-15) @@ -136,6 +134,8 @@ X86Seg.loadReal = function loadReal(sel, fSuppress) * * See X86.DESC for offset and bit definitions. * + * IDT descriptor entries are handled separately by loadIDT(). + * * @this {X86Seg} * @param {number} sel * @param {boolean} [fSuppress] is true to suppress any errors, cycle assessment, etc @@ -168,6 +168,52 @@ X86Seg.loadProt = function loadProt(sel, fSuppress) return null; }; +/** + * loadRealIDT(nIDT) + * + * @this {X86Seg} + * @param {number} nIDT + * @return {number|null} base address of selected segment, or null if error + */ +X86Seg.loadRealIDT = function loadRealIDT(nIDT) +{ + if (DEBUG) { + this.cpu.assert(nIDT >= 0 && nIDT < 256 && !this.cpu.addrIDT && this.cpu.addrIDTLimit == 0x03FF); + } + /* + * Intel documentation for INT/INTO under "REAL ADDRESS MODE EXCEPTIONS" says: + * + * "[T]he 80286 will shut down if the SP = 1, 3, or 5 before executing the INT or INTO instruction--due to lack of stack space" + * + * TODO: Verify that 80286 real-mode actually enforces the above. See http://localhost:8088/pubs/pc/reference/intel/80286/progref/#page-260 + */ + var offIDT = this.cpu.addrIDT + (nIDT << 2); + this.cpu.regIP = this.cpu.getWord(offIDT); + this.sel = this.cpu.getWord(offIDT + 2); + this.cpu.regPS &= ~(X86.PS.TF | X86.PS.IF); + return this.base = this.sel << 4; +}; + +/** + * loadProtIDT(nIDT) + * + * @this {X86Seg} + * @param {number} nIDT + * @return {number|null} base address of selected segment, or null if error + */ +X86Seg.loadProtIDT = function loadProtIDT(nIDT) +{ + if (DEBUG) this.cpu.assert(nIDT >= 0 && nIDT < 256); + + nIDT <<= 3; + var addrDesc = this.cpu.addrIDT + nIDT; + if (addrDesc + 7 <= this.cpu.addrIDTLimit) { + return this.loadDesc8(nIDT, addrDesc); + } + X86Help.opHelpFault.call(this.cpu, X86.EXCEPTION.GP_FAULT, nIDT | X86.ERRCODE.IDT | X86.ERRCODE.EXT, true); + return null; +}; + /** * checkReadReal(off, cb, fSuppress) * @@ -367,6 +413,9 @@ X86Seg.prototype.loadDesc8 = function(sel, addrDesc) var selMasked = sel & X86.SEL.MASK; while (true) { + + var cplPrev, addrTSS, offSP, offSS, regSPPrev, regSSPrev; + if (this.id == X86Seg.ID.CODE) { this.fStackSwitch = false; var fCall = this.fCall; @@ -379,7 +428,7 @@ X86Seg.prototype.loadDesc8 = function(sel, addrDesc) */ if (rpl < this.cpl) rpl = this.cpl; if (rpl <= dpl) { - var cplPrev = this.cpl; + cplPrev = this.cpl; if (this.load(base & 0xffff, true) != null) { this.cpu.regIP = limit; if (this.cpl < cplPrev) { @@ -393,11 +442,11 @@ X86Seg.prototype.loadDesc8 = function(sel, addrDesc) this.awScratch[i++] = this.cpu.getSOWord(this.cpu.segSS, regSP); regSP += 2; } - var addrTSS = this.cpu.segTSS.base; - var offSP = (this.cpl << 2) + X86.TSS.CPL0_SP; - var offSS = offSP + 2; - var regSPPrev = this.cpu.regSP; - var regSSPrev = this.cpu.segSS.sel; + addrTSS = this.cpu.segTSS.base; + offSP = (this.cpl << 2) + X86.TSS.CPL0_SP; + offSS = offSP + 2; + regSPPrev = this.cpu.regSP; + regSSPrev = this.cpu.segSS.sel; this.cpu.regSP = this.cpu.getWord(addrTSS + offSP); this.cpu.segSS.load(this.cpu.getWord(addrTSS + offSS)); this.cpu.pushWord(regSSPrev); @@ -409,6 +458,38 @@ X86Seg.prototype.loadDesc8 = function(sel, addrDesc) } } } + else if (type == X86.DESC.ACC.TYPE.GATE_INT || type == X86.DESC.ACC.TYPE.GATE_TRAP) { + if (rpl < this.cpl) rpl = this.cpl; + if (rpl <= dpl) { + cplPrev = this.cpl; + if (this.load(base & 0xffff, true) != null) { + this.cpu.regIP = limit; + if (this.cpl < cplPrev) { + if (fCall !== true) { + base = null; + break; + } + regSP = this.cpu.regSP; + addrTSS = this.cpu.segTSS.base; + offSP = (this.cpl << 2) + X86.TSS.CPL0_SP; + offSS = offSP + 2; + regSPPrev = this.cpu.regSP; + regSSPrev = this.cpu.segSS.sel; + this.cpu.regSP = this.cpu.getWord(addrTSS + offSP); + this.cpu.segSS.load(this.cpu.getWord(addrTSS + offSS)); + this.cpu.pushWord(regSSPrev); + this.cpu.pushWord(regSPPrev); + this.fStackSwitch = true; + } + if (type == X86.DESC.ACC.TYPE.GATE_INT) { + this.cpu.regPS &= ~(X86.PS.NT | X86.PS.TF | X86.PS.IF); + } else { + this.cpu.regPS &= ~(X86.PS.NT | X86.PS.TF); + } + return this.base; + } + } + } else if (type >= X86.DESC.ACC.TYPE.CODE_EXECONLY /* || dpl > this.cpu.segCS.cpl */) { rpl = sel & X86.SEL.RPL; if (rpl > this.cpl) { @@ -547,6 +628,7 @@ X86Seg.prototype.updateAccess = function(fProt) } if (fProt) { this.load = X86Seg.loadProt; + this.loadIDT = X86Seg.loadProtIDT; this.checkRead = X86Seg.checkReadProt; this.checkWrite = X86Seg.checkWriteProt; if (this.acc & X86.DESC.ACC.TYPE.SEG) { @@ -574,8 +656,10 @@ X86Seg.prototype.updateAccess = function(fProt) this.dpl = (this.acc & X86.DESC.ACC.DPL.MASK) >> X86.DESC.ACC.DPL.SHIFT; } else { this.load = X86Seg.loadReal; + this.loadIDT = X86Seg.loadRealIDT; this.checkRead = X86Seg.checkReadReal; this.checkWrite = X86Seg.checkWriteReal; + this.limit = 0xffff; this.cpl = this.dpl = 0; this.addrDesc = null; }