Support for call gates and stack-switching on call/retf/iret instructions

This commit is contained in:
Jeff Parsons 2014-11-24 18:09:11 -08:00 committed by jeffpar
commit 173ef0a3fe
8 changed files with 240 additions and 99 deletions

View file

@ -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();
}

View file

@ -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....
*/

View file

@ -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++;
}

View file

@ -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)
];
/*

View file

@ -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;
}
};

View file

@ -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

View file

@ -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));
}

View file

@ -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) {