From 173ef0a3fe69cd291ca773504c724c4bb8cc3927 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Mon, 24 Nov 2014 18:09:11 -0800 Subject: [PATCH] Support for call gates and stack-switching on call/retf/iret instructions --- modules/pcjs/lib/chipset.js | 40 ++++++------- modules/pcjs/lib/debugger.js | 15 ++++- modules/pcjs/lib/x86cpu.js | 37 ++++++++---- modules/pcjs/lib/x86grps.js | 8 +-- modules/pcjs/lib/x86help.js | 112 ++++++++++++++++++++++++++--------- modules/pcjs/lib/x86opxx.js | 22 +++---- modules/pcjs/lib/x86seg.js | 101 ++++++++++++++++++++++++------- modules/shared/lib/strlib.js | 8 ++- 8 files changed, 242 insertions(+), 101 deletions(-) diff --git a/modules/pcjs/lib/chipset.js b/modules/pcjs/lib/chipset.js index 73ea7dcf1..1ea6550db 100644 --- a/modules/pcjs/lib/chipset.js +++ b/modules/pcjs/lib/chipset.js @@ -2993,7 +2993,7 @@ ChipSet.prototype.outPICLo = function(iPIC, bOut, addrFrom) } else { if (DEBUG) { this.messageDebugger("outPIC" + iPIC + "(" + str.toHexByte(pic.port) + "): unexpected EOI command, IRQ " + nIRQ + " not in service", Debugger.MESSAGE.PIC | Debugger.MESSAGE.WARN); - if (!SAMPLER) this.cpu.stopCPU(); + if (this.dbg && !SAMPLER) this.dbg.stopCPU(); } } /* @@ -3001,7 +3001,7 @@ ChipSet.prototype.outPICLo = function(iPIC, bOut, addrFrom) */ if (DEBUG && (bOCW2 & ChipSet.PIC_LO.OCW2_SET_ROTAUTO)) { this.messageDebugger("outPIC" + iPIC + "(" + str.toHexByte(pic.port) + "): unsupported OCW2 rotate command: " + str.toHexByte(bOut), Debugger.MESSAGE.PIC | Debugger.MESSAGE.WARN); - this.cpu.stopCPU(); + if (this.dbg) this.dbg.stopCPU(); } } else if (bOCW2 == ChipSet.PIC_LO.OCW2_SET_PRI) { @@ -3016,7 +3016,7 @@ ChipSet.prototype.outPICLo = function(iPIC, bOut, addrFrom) */ if (DEBUG) { this.messageDebugger("outPIC" + iPIC + "(" + str.toHexByte(pic.port) + "): unsupported OCW2 automatic EOI command: " + str.toHexByte(bOut), Debugger.MESSAGE.PIC | Debugger.MESSAGE.WARN); - this.cpu.stopCPU(); + if (this.dbg) this.dbg.stopCPU(); } } } else { @@ -3477,10 +3477,10 @@ ChipSet.prototype.getTimerCycleLimit = function(iTimer, nCycles) var ticksElapsed = ((nCyclesUpdate - timer.nCyclesStart) / this.nTicksDivisor) | 0; if (DEBUG) this.assert(ticksElapsed >= 0); var countStart = this.getTimerStart(iTimer); - var countRemain = countStart - ticksElapsed; - if (timer.mode == ChipSet.TIMER_CTRL.MODE3) countRemain -= ticksElapsed; - if (DEBUG) this.assert(countRemain > 0); - var nCyclesRemain = (countRemain * this.nTicksDivisor) | 0; + var count = countStart - ticksElapsed; + if (timer.mode == ChipSet.TIMER_CTRL.MODE3) count -= ticksElapsed; + if (DEBUG) this.assert(count > 0); + var nCyclesRemain = (count * this.nTicksDivisor) | 0; if (timer.mode == ChipSet.TIMER_CTRL.MODE3) nCyclesRemain >>= 1; if (nCycles > nCyclesRemain) nCycles = nCyclesRemain; } @@ -3620,19 +3620,19 @@ ChipSet.prototype.updateTimer = function(iTimer, fCycleReset) * divisor (eg, 4 for MODEL_5150 and MODEL_5160, 5 for MODEL_5170, etc) is nTicksDivisor, which initBus() * calculates using the base CPU speed returned by cpu.getCyclesPerSecond(). */ - var ticks = ((nCycles - timer.nCyclesStart) / this.nTicksDivisor) | 0; + var ticksElapsed = ((nCycles - timer.nCyclesStart) / this.nTicksDivisor) | 0; - if (ticks < 0) { - if (DEBUG) this.messageDebugger("updateTimer(" + iTimer + "): negative tick count (" + ticks + ")", Debugger.MESSAGE.TIMER); + if (ticksElapsed < 0) { + if (DEBUG) this.messageDebugger("updateTimer(" + iTimer + "): negative tick count (" + ticksElapsed + ")", Debugger.MESSAGE.TIMER); timer.nCyclesStart = nCycles; - ticks = 0; + ticksElapsed = 0; } var countInit = this.getTimerInit(iTimer); var countStart = this.getTimerStart(iTimer); var fFired = false; - var count = countStart - ticks; + var count = countStart - ticksElapsed; /* * NOTE: This mode is used by ROM BIOS test code that wants to verify timer interrupts are arriving @@ -3699,7 +3699,7 @@ ChipSet.prototype.updateTimer = function(iTimer, fCycleReset) */ else if (timer.mode == ChipSet.TIMER_CTRL.MODE3) { - count -= ticks; + count -= ticksElapsed; if (count <= 0) { timer.fOUT = !timer.fOUT; count = countInit + count; @@ -3724,7 +3724,7 @@ ChipSet.prototype.updateTimer = function(iTimer, fCycleReset) } if (DEBUG && DEBUGGER && this.dbg && this.dbg.messageEnabled(Debugger.MESSAGE.TIMER | Debugger.MESSAGE.LOG)) { - this.log("TIMER" + iTimer + " count: " + count + ", ticks: " + ticks + ", fired: " + (fFired? "true" : "false")); + this.log("TIMER" + iTimer + " count: " + count + ", ticks: " + ticksElapsed + ", fired: " + (fFired? "true" : "false")); } timer.countCurrent[0] = count & 0xff; @@ -4224,9 +4224,9 @@ ChipSet.prototype.out8042InBuffCmd = function(port, bOut, addrFrom) break; default: - if (DEBUG && DEBUGGER && this.dbg) { - this.dbg.message("unrecognized 8042 command: " + str.toHexByte(this.b8042InBuff)); - this.cpu.stopCPU(); + if (DEBUG) { + this.messageDebugger("unrecognized 8042 command: " + str.toHexByte(this.b8042InBuff)); + if (this.dbg) this.dbg.stopCPU(); } break; } @@ -4301,9 +4301,9 @@ ChipSet.prototype.set8042OutPort = function(b) * KBC.CMD.PULSE_OUTPORT command, so if a RESET is detected via this command, we should try to * determine if that's what the caller intended. */ - if (DEBUG && DEBUGGER && this.dbg) { - this.dbg.message("unexpected 8042 output port reset: " + str.toHexByte(b)); - this.cpu.stopCPU(); + if (DEBUG) { + this.messageDebugger("unexpected 8042 output port reset: " + str.toHexByte(b)); + if (this.dbg) this.dbg.stopCPU(); } this.cpu.resetRegs(); } diff --git a/modules/pcjs/lib/debugger.js b/modules/pcjs/lib/debugger.js index 7ef370e4c..c96f468db 100644 --- a/modules/pcjs/lib/debugger.js +++ b/modules/pcjs/lib/debugger.js @@ -1432,6 +1432,7 @@ if (DEBUGGER) { this.println("dumpDesc(" + str.toHexWord(seg.sel) + "): %" + str.toHex(seg.addrDesc, this.cchAddr)); var sType; + var fGate = false; if (seg.type & X86.DESC.ACC.TYPE.SEG) { if (seg.type & X86.DESC.ACC.TYPE.CODE) { sType = "code"; @@ -1458,15 +1459,19 @@ if (DEBUGGER) { break; case X86.DESC.ACC.TYPE.GATE_CALL: sType = "call gate"; + fGate = true; break; case X86.DESC.ACC.TYPE.GATE_TASK: sType = "task gate"; + fGate = true; break; case X86.DESC.ACC.TYPE.GATE_INT: sType = "int gate"; + fGate = true; break; case X86.DESC.ACC.TYPE.GATE_TRAP: sType = "trap gate"; + fGate = true; break; default: break; @@ -1475,7 +1480,13 @@ if (DEBUGGER) { if (sType && !(seg.acc & X86.DESC.ACC.PRESENT)) sType += ",not present"; - this.println("base=" + str.toHex(seg.base, this.cchAddr) + " limit=" + str.toHexWord(seg.limit) + " dpl=" + str.toHexByte(seg.dpl) + " type=" + str.toHexByte(seg.acc >>> 8) + " (" + sType + ")"); + var sDump; + if (fGate) { + sDump = "seg=" + str.toHexWord(seg.base & 0xffff) + " off=" + str.toHexWord(seg.limit); + } else { + sDump = "base=" + str.toHex(seg.base, this.cchAddr) + " limit=" + str.toHexWord(seg.limit); + } + this.println(sDump + " dpl=" + str.toHexByte(seg.dpl) + " type=" + str.toHexByte(seg.type >> 8) + " (" + sType + ")"); }; /** @@ -2333,7 +2344,7 @@ if (DEBUGGER) { if (sel == this.cpu.segDS.sel) return this.cpu.segDS; if (sel == this.cpu.segES.sel) return this.cpu.segES; if (sel == this.cpu.segSS.sel) return this.cpu.segSS; - var seg = new X86Seg(this.cpu, X86Seg.ID.OTHER, "DBG"); + var seg = new X86Seg(this.cpu, X86Seg.ID.DEBUG, "DBG"); /* * TODO: Confirm that it's OK for this function to drop any error from seg.load() on the floor.... */ diff --git a/modules/pcjs/lib/x86cpu.js b/modules/pcjs/lib/x86cpu.js index 30828f912..987403441 100644 --- a/modules/pcjs/lib/x86cpu.js +++ b/modules/pcjs/lib/x86cpu.js @@ -160,11 +160,11 @@ function X86CPU(parmsCPU) { if (SAMPLER) { /* - * For now, we're just going to sample EIP values + * For now, we're just going to sample EIP values (well, EIP + cycle count) */ this.nSamples = 50000; - this.nSampleFreq = 1; - this.nSampleSkip = 4778000; + this.nSampleFreq = 1000; + this.nSampleSkip = 0; this.aSamples = new Array(this.nSamples); for (var i = 0; i < this.nSamples; i++) this.aSamples[i] = -1; this.iSampleNext = 0; @@ -1290,7 +1290,7 @@ X86CPU.prototype.setIP = function(off) }; /** - * setCSIP(off, sel) + * setCSIP(off, sel, fCall) * * This function is a little different from the other segment setters, only because it turns out that CS is * never set without an accompanying IP (well, except for a few undocumented instructions, like POP CS, which @@ -1300,19 +1300,32 @@ X86CPU.prototype.setIP = function(off) * 16-bit values, so there's never any need to mask them with 0xffff (although it doesn't hurt to assert that). * * And even though this function is called setCSIP(), please note the order of the parameters is IP,CS, - * which matches the order that CS:IP values are normally stored in memory, allowing us to make calls like: + * which matches the order that CS:IP values are normally stored in memory, allowing us to make calls like this: * * this.setCSIP(this.popWord(), this.popWord()); * * @this {X86CPU} * @param {number} off * @param {number} sel + * @param {boolean} [fCall] is true if "CALLF" in progress + * @return {boolean} true if "RETF" performed a stack switch */ -X86CPU.prototype.setCSIP = function(off, sel) +X86CPU.prototype.setCSIP = function(off, sel, fCall) { if (DEBUG) this.assert((off & 0xffff) == off); - this.regEIP = this.segCS.load(sel) + (this.regIP = off); + this.segCS.fCall = fCall; + /* + * We break this operation into the following discrete steps (eg, set IP, load CS, and then update EIP) + * so that the protected-mode version of segCS.load(sel) has the option of modifying IP when sel refers to a + * call gate. + */ + this.regIP = off; + var base = this.segCS.load(sel); + if (base != null) { + this.regEIP = base + this.regIP; + } if (PREFETCH) this.flushPrefetch(this.regEIP); + return this.segCS.fReturn; }; /** @@ -2548,18 +2561,16 @@ X86CPU.prototype.stepCPU = function(nMinCycles) this.stopCPU(); break; } + var t = this.regEIP + this.getCycles(); var n = this.aSamples[this.iSampleNext]; if (n !== -1) { - if (n !== this.regEIP) { - this.println("sample deviation at index " + this.iSampleNext + ": current EIP=" + str.toHex(this.regEIP) + ", target EIP=" + str.toHex(n)); + if (n !== t) { + this.println("sample deviation at index " + this.iSampleNext + ": current EIP=" + str.toHex(this.regEIP)); this.stopCPU(); break; } } else { - this.aSamples[this.iSampleNext] = this.regEIP; - } - if (this.iSampleNext == 54) { - fDebugSkip = false; // just some no-op statement we can set a breakpoint on + this.aSamples[this.iSampleNext] = t; } this.iSampleNext++; } diff --git a/modules/pcjs/lib/x86grps.js b/modules/pcjs/lib/x86grps.js index 32996da35..f48e333f7 100644 --- a/modules/pcjs/lib/x86grps.js +++ b/modules/pcjs/lib/x86grps.js @@ -1128,9 +1128,7 @@ var X86Grps = { if (this.regEA < 0) { return X86Grps.opGrpUndefined.call(this, dst, src); } - this.pushWord(this.segCS.sel); - this.pushWord(this.regIP); - this.setCSIP(dst, this.getWord(this.regEA + 2)); + X86Help.opHelpCallF.call(this, dst, this.getWord(this.regEA + 2)); this.nStepCycles -= this.CYCLES.nOpCyclesCallDM; if (EAFUNCS) this.setEAWord = this.setEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE; return dst; @@ -1153,7 +1151,7 @@ var X86Grps = { * @param {number} src (null) * @return {number} */ - opGrpJMPf: function(dst, src) { + opGrpJMPdw: function(dst, src) { if (this.regEA < 0) { return X86Grps.opGrpUndefined.call(this, dst, src); } @@ -1329,7 +1327,7 @@ X86Grps.aOpGrp4b = [ X86Grps.aOpGrp4w = [ X86Grps.opGrpINCw, X86Grps.opGrpDECw, X86Grps.opGrpCALLw, X86Grps.opGrpCALLdw, // 0xFF(reg=0x0-0x3) - X86Grps.opGrpJMPw, X86Grps.opGrpJMPf, X86Grps.opGrpPUSHw, X86Grps.opGrpFault // 0xFF(reg=0x4-0x7) + X86Grps.opGrpJMPw, X86Grps.opGrpJMPdw, X86Grps.opGrpPUSHw, X86Grps.opGrpFault // 0xFF(reg=0x4-0x7) ]; /* diff --git a/modules/pcjs/lib/x86help.js b/modules/pcjs/lib/x86help.js index ff5a5cccc..d8272ac4b 100644 --- a/modules/pcjs/lib/x86help.js +++ b/modules/pcjs/lib/x86help.js @@ -277,7 +277,7 @@ var X86Help = { * TODO: This instruction's 80286 documentation does not discuss conforming code segments; determine * if we need a special check for them. */ - if (this.segVER.load(src, true) >= 0) { + if (this.segVER.load(src, true) != null) { if (this.segVER.dpl >= this.segCS.cpl && this.segVER.dpl >= (src & X86.SEL.RPL)) { this.setZF(); return this.segVER.acc & X86.DESC.ACC.MASK; @@ -304,7 +304,7 @@ var X86Help = { * TODO: LSL is explicitly documented as ALSO requiring a non-null selector, so we check X86.SEL.MASK; * are there any other instructions that were, um, less explicit but also require a non-null selector? */ - if ((src & X86.SEL.MASK) && this.segVER.load(src, true) >= 0) { + if ((src & X86.SEL.MASK) && this.segVER.load(src, true) != null) { var fConforming = ((this.segVER.acc & X86.DESC.ACC.TYPE.CODE_CONFORMING_EXECONLY) == X86.DESC.ACC.TYPE.CODE_CONFORMING_EXECONLY); if ((fConforming || this.segVER.dpl >= this.segCS.cpl) && this.segVER.dpl >= (src & X86.SEL.RPL)) { this.setZF(); @@ -435,6 +435,23 @@ var X86Help = { if (this.regMSW & X86.MSW.PE) this.setProtMode(true); }, + /** + * opHelpCallF(off, sel) + * + * 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} off + * @param {number} sel + */ + opHelpCallF: function(off, sel) { + var regCS = this.segCS.sel; + var regIP = this.regIP; + this.setCSIP(off, sel, true); + this.pushWord(regCS); + this.pushWord(regIP); + }, /** * opHelpDIVOverflow() * @@ -489,7 +506,7 @@ var X86Help = { return; } } - this.setCSIP(this.popWord(), this.popWord()); + this.setCSIP(this.popWord(), this.popWord(), false); this.setPS(this.popWord()); if (this.cIntReturn) this.checkIntReturn(this.regEIP); }, @@ -543,7 +560,7 @@ var X86Help = { * * "[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" * - * Huh? Why would real-mode care? See http://localhost:8088/pubs/pc/reference/intel/80286/progref/#page-260 + * TODO: Verify that 80286 real-mode actually enforces the above. See http://localhost:8088/pubs/pc/reference/intel/80286/progref/#page-260 */ offIDT = this.addrIDT + (nIDT << 2); this.descIDT.off = this.getWord(offIDT); @@ -552,6 +569,11 @@ var X86Help = { 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(). + * * @this {X86CPU} * @param {number|null|undefined} nError */ @@ -567,6 +589,8 @@ var X86Help = { /** * opHelpSwitchTSS(selNew, fNest) * + * Helper implementing TSS (Task State Segment) task switching. + * * @this {X86CPU} * @param {number} selNew * @param {boolean} fNest is true if nesting, false if un-nesting @@ -638,6 +662,10 @@ var X86Help = { return true; }, /** + * opHelpFault(nFault, nError, fHalt) + * + * Helper to dispatch faults. + * * @this {X86CPU} * @param {number} nFault * @param {number} [nError] @@ -669,39 +697,69 @@ var X86Help = { return; } } - X86Help.opHelpFaultMessage.call(this, nFault, nError, fHalt); + if (X86Help.opHelpFaultMessage.call(this, nFault, nError, fHalt)) { + fFault = false; + } if (fFault) X86Help.opHelpINT.call(this, this.nFault = nFault, nError, 0); }, /** + * opHelpFaultMessage() + * + * Aside from giving the Debugger an opportunity to report every fault, this also gives us the ability to + * halt exception processing in tracks: return true to prevent the fault handler from being dispatched. + * + * TODO: Provide the Debugger with some UI to control its "interference" with fault dispatching, and to + * continue the dispatch after it has interfered. + * * @this {X86CPU} * @param {number} nFault * @param {number} [nError] * @param {boolean} [fHalt] will halt the CPU if true *and* a Debugger is loaded + * @return {boolean} true to halt the CPU, false if not */ opHelpFaultMessage: function(nFault, nError, fHalt) { - if (DEBUGGER && this.dbg) { - var bitsMessage = Debugger.MESSAGE.FAULT; - var bOpcode = this.bus.getByteDirect(this.regEIP); - /* - * OS/2 1.0 uses an INT3 (0xCC) opcode in conjunction with an invalid IDT to trigger a triple-fault - * reset and return to real-mode, and these resets happen quite frequently during boot; for example, - * OS/2 startup messages are displayed using a series of INT 0x10 BIOS calls for each character, and - * each series of BIOS calls requires a round-trip mode switch. - * - * Since we really only want to halt on "bad" faults, not "good" (ie, intentional) faults, we take - * advantage of the fact that all 3 faults comprising the triple-fault point to an INT3 (0xCC) opcode, - * and so whenever we see that opcode, we ignore the caller's fHalt flag, and suppress FAULT messages - * unless CPU messages are also enabled. - * - * When a triple fault shows up, nFault is -1; it displays as "ff" only because we truncate it to a byte. - */ - if (bOpcode == X86.OPCODE.INT3) { - fHalt = false; - bitsMessage |= Debugger.MESSAGE.CPU; - } - this.messageDebugger("Fault " + str.toHexByte(nFault) + (nError != null? " (" + str.toHexWord(nError) + ")" : "") + " on opcode 0x" + str.toHexByte(bOpcode) + " at " + str.toHexAddr(this.regIP, this.segCS.sel) + " (%" + str.toHex(this.regEIP, 6) + ")", bitsMessage); - if (fHalt) this.dbg.stopCPU(); + /* + * TODO: When we're done examining all GP faults, change the following to "fHalt || false" + */ + fHalt = fHalt || (nFault == X86.EXCEPTION.GP_FAULT); + + var bitsMessage = Debugger.MESSAGE.FAULT; + var bOpcode = this.bus.getByteDirect(this.regEIP); + /* + * OS/2 1.0 uses an INT3 (0xCC) opcode in conjunction with an invalid IDT to trigger a triple-fault + * reset and return to real-mode, and these resets happen quite frequently during boot; for example, + * OS/2 startup messages are displayed using a series of INT 0x10 BIOS calls for each character, and + * each series of BIOS calls requires a round-trip mode switch. + * + * Since we really only want to halt on "bad" faults, not "good" (ie, intentional) faults, we take + * advantage of the fact that all 3 faults comprising the triple-fault point to an INT3 (0xCC) opcode, + * and so whenever we see that opcode, we ignore the caller's fHalt flag, and suppress FAULT messages + * unless CPU messages are also enabled. + * + * When a triple fault shows up, nFault is -1; it displays as "ff" only because we truncate it to a byte. + */ + if (bOpcode == X86.OPCODE.INT3) { + fHalt = false; + bitsMessage |= Debugger.MESSAGE.CPU; } + /* + * Similarly, the PC AT ROM BIOS deliberately generates a couple of GP faults as part of the POST + * (Power-On Self Test); we don't want to ignore those, but we don't want to halt on them either. We + * detect those faults by virtue of EIP being in the range %0F0000 to %0FFFFF. + */ + if (this.regEIP >= 0x0F0000 && this.regEIP <= 0x0FFFFF) { + fHalt = false; + } + var sMessage = "Fault " + str.toHexByte(nFault) + (nError != null? " (" + str.toHexWord(nError) + ")" : "") + " on opcode 0x" + str.toHexByte(bOpcode) + " at " + str.toHexAddr(this.regIP, this.segCS.sel) + " (%" + str.toHex(this.regEIP, 6) + ")"; + + if (DEBUGGER && this.dbg) { + this.messageDebugger(sMessage, bitsMessage); + if (fHalt) this.dbg.stopCPU(); + } else if (fHalt) { + this.notice(sMessage); + this.stopCPU(); + } + return fHalt; } }; diff --git a/modules/pcjs/lib/x86opxx.js b/modules/pcjs/lib/x86opxx.js index d36f5c32a..2cbe4b738 100644 --- a/modules/pcjs/lib/x86opxx.js +++ b/modules/pcjs/lib/x86opxx.js @@ -1970,12 +1970,8 @@ var X86OpXX = { * * op=0x9A (call seg:off) */ - opCALLf: function() { - var newIP = this.getIPWord(); - var newCS = this.getIPWord(); - this.pushWord(this.segCS.sel); - this.pushWord(this.regIP); - this.setCSIP(newIP, newCS); + opCALLF: function() { + X86Help.opHelpCallF.call(this, this.getIPWord(), this.getIPWord()); this.nStepCycles -= this.CYCLES.nOpCyclesCallF; }, /** @@ -2789,8 +2785,12 @@ var X86OpXX = { */ opRETFn: function() { var n = this.getIPWord(); - this.setCSIP(this.popWord(), this.popWord()); + var regIP = this.popWord(); + var regCS = this.popWord(); this.regSP = (this.regSP + n) & 0xffff; + if (this.setCSIP(regIP, regCS, false)) { + this.regSP = (this.regSP + n) & 0xffff; + } if (this.cIntReturn) this.checkIntReturn(this.regEIP); this.nStepCycles -= this.CYCLES.nOpCyclesRetFn; }, @@ -2800,7 +2800,7 @@ var X86OpXX = { * op=0xCB (retf) */ opRETF: function() { - this.setCSIP(this.popWord(), this.popWord()); + this.setCSIP(this.popWord(), this.popWord(), false); this.nStepCycles -= this.CYCLES.nOpCyclesRetF; }, /** @@ -3079,7 +3079,7 @@ var X86OpXX = { * * op=0xEA (jmp seg:off) */ - opJMPf: function() { + opJMPF: function() { this.setCSIP(this.getIPWord(), this.getIPWord()); this.nStepCycles -= this.CYCLES.nOpCyclesJmpF; }, @@ -3422,7 +3422,7 @@ X86OpXX.aOps = [ X86OpXX.opMOVSegSrc, X86OpXX.opLEA, X86OpXX.opMOVSegDst, X86OpXX.opPOPmw, // 0x8C-0x8F X86OpXX.opNOP, X86OpXX.opXCHGCX, X86OpXX.opXCHGDX, X86OpXX.opXCHGBX, // 0x90-0x93 X86OpXX.opXCHGSP, X86OpXX.opXCHGBP, X86OpXX.opXCHGSI, X86OpXX.opXCHGDI, // 0x94-0x97 - X86OpXX.opCBW, X86OpXX.opCWD, X86OpXX.opCALLf, X86OpXX.opWAIT, // 0x98-0x9B + X86OpXX.opCBW, X86OpXX.opCWD, X86OpXX.opCALLF, X86OpXX.opWAIT, // 0x98-0x9B X86OpXX.opPUSHF, X86OpXX.opPOPF, X86OpXX.opSAHF, X86OpXX.opLAHF, // 0x9C-0x9F X86OpXX.opMOVALDst, X86OpXX.opMOVAXDst, X86OpXX.opMOVALSrc, X86OpXX.opMOVAXSrc, // 0xA0-0xA3 X86OpXX.opMOVSb, X86OpXX.opMOVSw, X86OpXX.opCMPSb, X86OpXX.opCMPSw, // 0xA4-0xA7 @@ -3448,7 +3448,7 @@ X86OpXX.aOps = [ X86OpXX.opESC, X86OpXX.opESC, X86OpXX.opESC, X86OpXX.opESC, // 0xDC-0xDF X86OpXX.opLOOPNZ, X86OpXX.opLOOPZ, X86OpXX.opLOOP, X86OpXX.opJCXZ, // 0xE0-0xE3 X86OpXX.opINb, X86OpXX.opINw, X86OpXX.opOUTb, X86OpXX.opOUTw, // 0xE4-0xE7 - X86OpXX.opCALL, X86OpXX.opJMP, X86OpXX.opJMPf, X86OpXX.opJMPs, // 0xE8-0xEB + X86OpXX.opCALL, X86OpXX.opJMP, X86OpXX.opJMPF, X86OpXX.opJMPs, // 0xE8-0xEB X86OpXX.opINDXb, X86OpXX.opINDXw, X86OpXX.opOUTDXb, X86OpXX.opOUTDXw, // 0xEC-0xEF /* * On an 8086/8088, opcode 0xF1 is assumed to be an alias for 0xF0; in any case, it definitely behaves like diff --git a/modules/pcjs/lib/x86seg.js b/modules/pcjs/lib/x86seg.js index 72d75b1a0..039762306 100644 --- a/modules/pcjs/lib/x86seg.js +++ b/modules/pcjs/lib/x86seg.js @@ -59,7 +59,8 @@ function X86Seg(cpu, id, sName, fProt) this.addrDesc = null; this.cpl = 0; this.dpl = 0; - this.updateAccess(fProt); + this.awScratch = (this.id == X86Seg.ID.CODE? new Array(32) : []); + this.updateAccess(fProt || false); } X86Seg.ID = { @@ -69,7 +70,8 @@ X86Seg.ID = { STACK: 3, // "SS" TSS: 4, // "TSS" LDT: 5, // "LDT" - OTHER: 6 // "VER", "DBG", etc + OTHER: 6, // "VER" + DEBUG: 7 // "DBG" }; /* @@ -112,7 +114,7 @@ X86Seg.loadReal = function loadReal(sel, fSuppress) * * @this {X86Seg} * @param {number} sel - * @param {boolean} [fSuppress] is true to suppress any errors + * @param {boolean} [fSuppress] is true to suppress any errors, cycle assessment, etc * @return {number|null} base address of selected segment, or null if error */ X86Seg.loadProt = function loadProt(sel, fSuppress) @@ -133,7 +135,7 @@ X86Seg.loadProt = function loadProt(sel, fSuppress) * I simply noted that "POP segreg" takes 5 cycles in real mode and 20 in protected mode, so I'm * starting with a 15-cycle difference. Obviously the difference will be much greater when the load fails. */ - this.cpu.nStepCycles -= 15; + if (!fSuppress) this.cpu.nStepCycles -= 15; return this.loadDesc8(sel, addrDesc); } return null; @@ -272,7 +274,7 @@ X86Seg.prototype.loadDesc6 = function(sel, addrDesc) this.addrDesc = addrDesc; this.updateAccess(); - this.messageDebugger(base, limit, acc); + this.messageDebugger(sel, base, limit, acc); return base; }; @@ -303,20 +305,64 @@ X86Seg.prototype.loadDesc8 = function(sel, addrDesc) var ext = (DEBUG? this.cpu.getWord(addrDesc + X86.DESC.EXT.OFFSET) : 0); while (true) { - /* - * For LSL, we must support X86.DESC.ACC.TYPE.SEG as well as TSS and LDT. - */ - if (!(acc & X86.DESC.ACC.TYPE.SEG) && type > X86.DESC.ACC.TYPE.TSS_BUSY) { - base = null; - break; - } if (sel) { /* - * TODO: These tests are far from complete; the main purpose right now is to - * catch cases (eg, call gates) that we need to add support for. + * TODO: These descriptor tests are far from complete.... */ if (this.id == X86Seg.ID.CODE) { - if (type < X86.DESC.ACC.TYPE.CODE_EXECONLY) { + this.fReturn = false; + var rpl = sel & X86.SEL.RPL; + var dpl = (acc & X86.DESC.ACC.DPL.MASK) >> X86.DESC.ACC.DPL.SHIFT; + var regSP; + if (type == X86.DESC.ACC.TYPE.GATE_CALL) { + /* + * Since we are X86Seg.ID.CODE, we can use this.cpl instead of the more generic this.cpu.segCS.cpl + */ + if (rpl < this.cpl) rpl = this.cpl; + if (rpl <= dpl) { + var cplPrev = this.cpl; + if (this.load(base & 0xffff, true) != null) { + this.cpu.regIP = limit; + if (this.cpl < cplPrev) { + if (this.fCall !== true) { + base = null; + break; + } + regSP = this.cpu.regSP; + var i = 0, nWords = (acc & 0x1f); + while (nWords--) { + 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; + 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); + while (i) this.cpu.pushWord(this.awScratch[--i]); + } + 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) { + if (this.fCall !== false) { + base = null; + break; + } + regSP = this.cpu.popWord(); + this.cpu.segSS.load(this.cpu.popWord()); + this.cpu.regSP = regSP; + this.fReturn = true; + } + } + else { X86Help.opHelpFault.call(this.cpu, X86.EXCEPTION.GP_FAULT, sel, true); base = null; break; @@ -336,6 +382,15 @@ X86Seg.prototype.loadDesc8 = function(sel, addrDesc) break; } } + else if (this.id == X86Seg.ID.OTHER) { + /* + * For LSL, we must support any descriptor marked X86.DESC.ACC.TYPE.SEG, as well as TSS and LDT descriptors. + */ + if (!(acc & X86.DESC.ACC.TYPE.SEG) && type > X86.DESC.ACC.TYPE.TSS_BUSY) { + base = null; + break; + } + } } this.sel = sel; this.base = base; @@ -344,9 +399,9 @@ X86Seg.prototype.loadDesc8 = function(sel, addrDesc) this.type = type; this.addrDesc = addrDesc; this.updateAccess(); - this.messageDebugger(base, limit, acc, ext); break; } + this.messageDebugger(sel, base, limit, acc, ext); return base; }; @@ -418,7 +473,10 @@ X86Seg.prototype.restore = function(a) */ X86Seg.prototype.updateAccess = function(fProt) { - if (fProt === undefined) { + if (fProt !== undefined) { + this.fCall = null; // true if "CALLF" in progress, false if "RETF [n]" in progress, null/undefined otherwise (X86Seg.ID.CODE only) + this.fReturn = false; // true if "RETF" performed, false otherwise + } else { fProt = !!(this.cpu.regMSW & X86.MSW.PE); } if (fProt) { @@ -452,21 +510,22 @@ X86Seg.prototype.updateAccess = function(fProt) }; /** - * messageDebugger(base, limit, acc, ext) + * messageDebugger(sel base, limit, acc, ext) * - * @param {number} base + * @param {number} sel + * @param {number|null} base * @param {number} limit * @param {number} acc * @param {number} [ext] */ -X86Seg.prototype.messageDebugger = function(base, limit, acc, ext) +X86Seg.prototype.messageDebugger = function(sel, base, limit, acc, ext) { if (DEBUG) { if (DEBUGGER) { var ch = (this.sName.length < 3? " " : ""); var sDPL = " dpl=" + this.dpl; if (this.id == X86Seg.ID.CODE) sDPL += " cpl=" + this.cpl; - this.cpu.messageDebugger("loadSeg(" + this.sName + "):" + ch + " base=" + str.toHex(base) + " limit=" + str.toHexWord(limit) + " acc=" + str.toHexWord(acc) + sDPL, Debugger.MESSAGE.SEG); + this.cpu.messageDebugger("loadSeg(" + this.sName + "):" + ch + "sel=" + str.toHexWord(sel) + " base=" + str.toHex(base) + " limit=" + str.toHexWord(limit) + " acc=" + str.toHexWord(acc) + sDPL, Debugger.MESSAGE.SEG); } this.cpu.assert(base != null && (!ext || ext == X86.DESC.EXT.AVAIL)); } diff --git a/modules/shared/lib/strlib.js b/modules/shared/lib/strlib.js index eb8738dfe..7c162ae2a 100644 --- a/modules/shared/lib/strlib.js +++ b/modules/shared/lib/strlib.js @@ -111,7 +111,7 @@ str.parseInt = function(s, base) * s = "00000000".substr(0, 8 - s.length) + s; * s = s.substr(0, cch).toUpperCase(); * - * @param {number|undefined} n is a 32-bit value + * @param {number|null|undefined} n is a 32-bit value * @param {number} [cch] is the desired number of hex digits (8 is both the default and the maximum) * @return {string} the hex representation of n */ @@ -123,7 +123,11 @@ str.toHex = function(n, cch) } else { if (cch > 8) cch = 8; } - if (isNaN(n)) { // detects BOTH NaN and undefined + /* + * An initial "falsey" check for null takes care of both null and undefined; + * we can't rely entirely on isNaN(), because isNaN(null) returns false, oddly enough. + */ + if (n == null || isNaN(n)) { while (cch-- > 0) s = '?' + s; } else { while (cch-- > 0) {