From 5b64488286a9d1495b3c1d7c0ca61cb09ae29008 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Fri, 21 Aug 2015 15:19:16 -0700 Subject: [PATCH] Made RETF restartable when the target segment is not present --- modules/pcjs/lib/debugger.js | 2 +- modules/pcjs/lib/x86.js | 2 +- modules/pcjs/lib/x86cpu.js | 26 ++++++++++++++-- modules/pcjs/lib/x86func.js | 53 ++++++++++++++++++++++++-------- modules/pcjs/lib/x86seg.js | 58 +++++++++++++++++++++--------------- 5 files changed, 100 insertions(+), 41 deletions(-) diff --git a/modules/pcjs/lib/debugger.js b/modules/pcjs/lib/debugger.js index 8d6883262..4c91df715 100644 --- a/modules/pcjs/lib/debugger.js +++ b/modules/pcjs/lib/debugger.js @@ -627,7 +627,7 @@ if (DEBUGGER) { }; Debugger.TRACE_LIMIT = 100000; - Debugger.HISTORY_LIMIT = DEBUG? 100000 : 10000; + Debugger.HISTORY_LIMIT = DEBUG? 100000 : 1000; /* * Opcode 0x0F has a distinguished history: diff --git a/modules/pcjs/lib/x86.js b/modules/pcjs/lib/x86.js index 301d8ac12..13a58366a 100644 --- a/modules/pcjs/lib/x86.js +++ b/modules/pcjs/lib/x86.js @@ -330,7 +330,7 @@ var X86 = { EXT: 0x0001, IDT: 0x0002, LDT: 0x0004, - MASK: 0xFFF8 // index of corresponding entry in GDT, LDT or IDT + SELMASK: 0xFFFC }, RESULT: { /* diff --git a/modules/pcjs/lib/x86cpu.js b/modules/pcjs/lib/x86cpu.js index 1c34b19ca..58b54386d 100644 --- a/modules/pcjs/lib/x86cpu.js +++ b/modules/pcjs/lib/x86cpu.js @@ -1127,7 +1127,7 @@ X86CPU.prototype.getReg = function(i) reg = this.regEBX; break; case 0x4: - reg = this.regESP; + reg = this.getSP(); break; case 0x5: reg = this.regEBP; @@ -1165,7 +1165,7 @@ X86CPU.prototype.setReg = function(i, reg) this.regEBX = reg; break; case 0x4: - this.regESP = reg; + this.setSP(reg); break; case 0x5: this.regEBP = reg; @@ -1295,6 +1295,13 @@ X86CPU.prototype.resetRegs = function() */ this.nFault = -1; + /* + * These are used to snapshot regLIP and regLSP, to help make instructions restartable; + * currently opLIP is updated prior to every instruction, but opLSP is updated only for + * "problematic" instructions (eg, RETF) and should otherwise remain set to X86.ADDR_INVALID. + */ + this.opLIP = this.opLSP = X86.ADDR_INVALID; + /* * Segment registers used to be defined as separate variables (eg, regCS and regCS0 stored the segment * number and base linear address, respectively), but segment registers are now defined as X86Seg objects. @@ -1419,6 +1426,21 @@ X86CPU.prototype.resetRegs = function() this.setProtMode(); }; +/** + * zeroSeg(seg) + * + * Helper to zero a segment register as privilege transitions require. + * + * @this {X86CPU} + * @param {X86Seg} seg + */ +X86CPU.prototype.zeroSeg = function(seg) +{ + if ((seg.sel & X86.SEL.MASK) && seg.dpl < this.nCPL && (seg.acc & X86.DESC.ACC.TYPE.CODE_CONFORMING) != X86.DESC.ACC.TYPE.CODE_CONFORMING) { + seg.load(0); + } +}; + /** * setAddrSize(size) * diff --git a/modules/pcjs/lib/x86func.js b/modules/pcjs/lib/x86func.js index f541e5b7a..c43d4f3ce 100644 --- a/modules/pcjs/lib/x86func.js +++ b/modules/pcjs/lib/x86func.js @@ -2228,14 +2228,35 @@ X86.fnRCRd = function RCRd(dst, src) /** * fnRETF(n) * - * For protected-mode, this function must be prepared to pop any arguments off the current stack AND - * whatever stack we may have switched to (setCSIP() returns true only when a stack switch has occurred). + * For protected-mode, this function must pop any arguments off the current stack AND whatever stack + * we may have switched to; setCSIP() returns true if a stack switch occurred, false if not, and null + * if an error occurred. + * + * Take a look at our counterpart, fnCALLF(): + * + * if (this.setCSIP(off, sel, true) != null) { + * this.pushWord(oldCS); + * this.pushWord(oldIP); + * } + * + * That code makes opCALLF() restartable, because it doesn't modify the stack unless setCSIP() succeeds. + * + * Here, our task is a little more complicated, because 1) it's not convenient to defer our stack + * operations (it's much simpler to perform them BEFORE the setCSIP() call rather than AFTER); 2) we + * have to deal with an additional stack adjustment value (n); and 3) if setCSIP() triggers a fault + * (eg, NP_FAULT), fnFault() must be able to do the rewinding, which happens BEFORE setCSIP() returns. + * + * The current hack to make the stack "rewindable" involves copying regLSP to opLSP, similar to what we do + * for EIP (ie, by copying regLIP into opLIP prior to executing every opcode). However, I don't really want + * to snapshot more data inside the opcode loop, so my compromise is to set opLSP only within "problematic" + * instructions (like this one), and set it back to X86.ADDR_INVALID when we're done. * * @this {X86CPU} * @param {number} n */ X86.fnRETF = function RETF(n) { + this.opLSP = this.regLSP; var newIP = this.popWord(); var newCS = this.popWord(); @@ -2246,12 +2267,12 @@ X86.fnRETF = function RETF(n) if (this.setCSIP(newIP, newCS, false)) { // returns true if a stack switch occurred /* * Fool me once, shame on... whatever. If setCSIP() indicates a stack switch occurred, - * make sure we're in protected mode, because automatic stack switches can't occur in real mode, - * and adjusting SP again under those circumstances will likely cause great harm. + * make sure we're in protected mode, because automatic stack switches can't occur in real mode. */ this.assert(!!(this.regCR0 & X86.CR0.MSW.PE)); if (n) this.setSP(this.getSP() + n); // TODO: optimize + /* * As per Intel documentation: "If any of [the DS or ES] registers refer to segments whose DPL is * less than the new CPL (excluding conforming code segments), the segment register is loaded with @@ -2261,15 +2282,14 @@ 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.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.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); + this.zeroSeg(this.segDS); + this.zeroSeg(this.segES); + if (I386 && this.model >= X86.MODEL_80386) { + this.zeroSeg(this.segFS); + this.zeroSeg(this.segGS); } } + this.opLSP = X86.ADDR_INVALID; if (MAXDEBUG && n == 2 && this.cIntReturn) this.checkIntReturn(this.regLIP); }; @@ -3693,12 +3713,15 @@ X86.fnFault = function(nFault, nError, fHalt, nCycles) { /* * X86.OPFLAG.FAULT flag is used by selected opcodes to provide an early exit, restore register(s), or whatever is - * needed to help ensure instruction restartability; there is currently no mechanism for snapping and restoring all - * registers for any instruction that might fault, so it's every opcode for themselves.... + * needed to help ensure instruction restartability; there is currently no general-purpose mechanism for snapping + * and restoring all registers for any instruction that might fault, so it's every opcode for themselves. * * X86.EXCEPTION.DEBUG exceptions set their own special flag, X86.OPFLAG.DEBUG, to prevent redundant DEBUG exceptions, * so we don't need to set OPFLAG.FAULT in that case, because a DEBUG exception doesn't actually prevent an instruction * from executing. + * + * TODO: Review the restartability of all our opcode handlers, starting with those that affect the segment registers + * and then moving on to the rest, and determine whether we really need a general-purpose solution instead. */ if (nFault == X86.EXCEPTION.DEBUG) { this.opFlags |= X86.OPFLAG.DEBUG; @@ -3719,6 +3742,10 @@ X86.fnFault = function(nFault, nError, fHalt, nCycles) * Single-fault (error code is passed through, and the responsible instruction is restartable) */ this.setIP(this.opLIP - this.segCS.base); + if (this.opLSP != X86.ADDR_INVALID) { + this.setSP((this.regESP & ~this.segSS.addrMask) | (this.opLSP - this.segSS.base)); + this.opLSP = X86.ADDR_INVALID; + } fDispatch = true; } else if (this.nFault != X86.EXCEPTION.DF_FAULT) { /* diff --git a/modules/pcjs/lib/x86seg.js b/modules/pcjs/lib/x86seg.js index fcaea721b..3aafce0fa 100644 --- a/modules/pcjs/lib/x86seg.js +++ b/modules/pcjs/lib/x86seg.js @@ -231,7 +231,7 @@ X86Seg.prototype.loadProt = function loadProt(sel) return this.loadDesc8(addrDesc, sel); } if (this.id < X86Seg.ID.VER) { - X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel); + X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel & X86.ERRCODE.SELMASK); } } return X86.ADDR_INVALID; @@ -282,9 +282,9 @@ 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, true) + cpu.regEIP; } - X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, nIDT | X86.ERRCODE.IDT | X86.ERRCODE.EXT, true); + X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, nIDT | X86.ERRCODE.IDT, true); return X86.ADDR_INVALID; }; @@ -499,7 +499,7 @@ X86Seg.prototype.loadAcc = function(sel, fGDT) return cpu.getShort(addrDesc + X86.DESC.ACC.OFFSET); } } - X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel); + X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel & X86.ERRCODE.SELMASK); return X86.DESC.ACC.INVALID; }; @@ -540,7 +540,7 @@ X86Seg.prototype.loadDesc6 = function(addrDesc, sel) }; /** - * loadDesc8(addrDesc, sel) + * loadDesc8(addrDesc, sel, fIDT) * * Used to load a protected-mode selector that refers to an 8-byte "descriptor table" (GDT, LDT, IDT) entry: * @@ -554,9 +554,10 @@ X86Seg.prototype.loadDesc6 = function(addrDesc, sel) * @this {X86Seg} * @param {number} addrDesc is the descriptor address * @param {number} sel is the associated selector, or nIDT*8 if IDT descriptor + * @param {boolean} [fIDT] is true if sel refers to the IDT (only affects error handling) * @return {number} base address of selected segment, or ADDR_INVALID if error */ -X86Seg.prototype.loadDesc8 = function(addrDesc, sel) +X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fIDT) { var cpu = this.cpu; var limit = cpu.getShort(addrDesc + X86.DESC.LIMIT.OFFSET); @@ -584,12 +585,12 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel) if (this.id == X86Seg.ID.CODE) { this.fStackSwitch = false; var fCall = this.fCall; - var regPSClear, nFaultError, regSP; + var regPSClear, regSP; var rpl = sel & X86.SEL.RPL; var dpl = (acc & X86.DESC.ACC.DPL.MASK) >> X86.DESC.ACC.DPL.SHIFT; if (selMasked && !(acc & X86.DESC.ACC.PRESENT)) { - if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.NP_FAULT, sel); + if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.NP_FAULT, sel & X86.ERRCODE.SELMASK); base = addrDesc = X86.ADDR_INVALID; break; } @@ -632,19 +633,16 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel) else if (type == X86.DESC.ACC.TYPE.GATE_CALL || type == X86.DESC.ACC.TYPE.GATE386_CALL) { fGate = true; regPSClear = 0; - nFaultError = sel; 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; regPSClear = (X86.PS.NT | X86.PS.TF | X86.PS.IF); - nFaultError = sel | X86.ERRCODE.EXT; cpu.assert(!(acc & 0x1f)); } else if (type == X86.DESC.ACC.TYPE.GATE286_TRAP || type == X86.DESC.ACC.TYPE.GATE386_TRAP) { fGate = true; regPSClear = (X86.PS.NT | X86.PS.TF); - nFaultError = sel | X86.ERRCODE.EXT; cpu.assert(!(acc & 0x1f)); } else if (type == X86.DESC.ACC.TYPE.GATE_TASK) { @@ -722,6 +720,10 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel) cpu.setSS(cpu.getShort(addrTSS + offSS), true); cpu.setSP(cpu.getLong(addrTSS + offSP)); if (regPS & X86.PS.VM) { + /* + * segFS amd segGS exist only on 80386 machines + */ + cpu.assert(I386 && cpu.model >= X86.MODEL_80386); cpu.pushWord(cpu.segGS.sel); cpu.setGS(0); cpu.pushWord(cpu.segFS.sel); @@ -739,14 +741,10 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel) } return this.base; } - cpu.assert(false); - if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, nFaultError, true); - base = addrDesc = X86.ADDR_INVALID; - break; } - else if (fGate !== false) { + if (fGate !== false) { cpu.assert(false); - if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel, true); + if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, (sel & X86.ERRCODE.SELMASK) | (fIDT? X86.ERRCODE.IDT : 0), true); base = addrDesc = X86.ADDR_INVALID; break; } @@ -754,7 +752,7 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel) else if (this.id == X86Seg.ID.DATA) { if (selMasked) { if (!(acc & X86.DESC.ACC.PRESENT)) { - if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.NP_FAULT, sel); + if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.NP_FAULT, sel & X86.ERRCODE.SELMASK); base = addrDesc = X86.ADDR_INVALID; break; } @@ -780,7 +778,7 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel) * * So, if the ACC field is zero, we won't set the last fnFault() parameter (fHalt) to true. */ - if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel, !!acc); + if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel & X86.ERRCODE.SELMASK, !!acc); base = addrDesc = X86.ADDR_INVALID; break; } @@ -788,12 +786,12 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel) } else if (this.id == X86Seg.ID.STACK) { if (!(acc & X86.DESC.ACC.PRESENT)) { - if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.SS_FAULT, sel); + if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.SS_FAULT, sel & X86.ERRCODE.SELMASK); base = addrDesc = X86.ADDR_INVALID; break; } if (!selMasked || type < X86.DESC.ACC.TYPE.SEG || (type & (X86.DESC.ACC.TYPE.CODE | X86.DESC.ACC.TYPE.WRITABLE)) != X86.DESC.ACC.TYPE.WRITABLE) { - if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel, true); + if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel & X86.ERRCODE.SELMASK, true); base = addrDesc = X86.ADDR_INVALID; break; } @@ -801,7 +799,7 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel) else if (this.id == X86Seg.ID.TSS) { var typeTSS = type & ~X86.DESC.ACC.TSS_BUSY; if (!selMasked || typeTSS != X86.DESC.ACC.TYPE.TSS286 && typeTSS != X86.DESC.ACC.TYPE.TSS386) { - if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel, true); + if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel & X86.ERRCODE.SELMASK, true); base = addrDesc = X86.ADDR_INVALID; break; } @@ -885,7 +883,7 @@ X86Seg.prototype.switchTSS = function switchTSS(selNew, fNest) * TODO: Verify that it is (always) correct to require that the BUSY bit be currently set. */ if (!(cpu.segTSS.type & X86.DESC.ACC.TSS_BUSY)) { - X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, selNew, true); + X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, selNew & X86.ERRCODE.SELMASK, true); return false; } /* @@ -905,7 +903,7 @@ X86Seg.prototype.switchTSS = function switchTSS(selNew, fNest) if (fNest !== false) { if (cpu.segTSS.type & X86.DESC.ACC.TSS_BUSY) { - X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, selNew, true); + X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, selNew & X86.ERRCODE.SELMASK, true); return false; } cpu.setShort(cpu.segTSS.addrDesc + X86.DESC.ACC.OFFSET, cpu.segTSS.acc |= X86.DESC.ACC.TSS_BUSY); @@ -979,8 +977,14 @@ X86Seg.prototype.switchTSS = function switchTSS(selNew, fNest) cpu.setLong(addrOld + X86.TSS386.TASK_CS, cpu.segCS.sel); cpu.setLong(addrOld + X86.TSS386.TASK_SS, cpu.segSS.sel); cpu.setLong(addrOld + X86.TSS386.TASK_DS, cpu.segDS.sel); + + /* + * segFS amd segGS exist only on 80386 machines + */ + cpu.assert(I386 && cpu.model >= X86.MODEL_80386); cpu.setLong(addrOld + X86.TSS386.TASK_FS, cpu.segFS.sel); cpu.setLong(addrOld + X86.TSS386.TASK_GS, cpu.segGS.sel); + /* * Reload all registers from the new TSS; it's important to reload the LDTR sooner * rather than later, so that as segment registers are reloaded, any LDT selectors will @@ -999,8 +1003,14 @@ X86Seg.prototype.switchTSS = function switchTSS(selNew, fNest) cpu.regEDI = cpu.getLong(addrNew + X86.TSS386.TASK_EDI); cpu.segES.load(cpu.getShort(addrNew + X86.TSS386.TASK_ES)); cpu.segDS.load(cpu.getShort(addrNew + X86.TSS386.TASK_DS)); + + /* + * segFS amd segGS exist only on 80386 machines + */ + cpu.assert(I386 && cpu.model >= X86.MODEL_80386); cpu.segFS.load(cpu.getShort(addrNew + X86.TSS386.TASK_FS)); cpu.segGS.load(cpu.getShort(addrNew + X86.TSS386.TASK_GS)); + cpu.setCSIP(cpu.getLong(addrNew + X86.TSS386.TASK_EIP), cpu.getShort(addrNew + X86.TSS386.TASK_CS)); offSS = X86.TSS386.TASK_SS; offSP = X86.TSS386.TASK_ESP;