Assorted V86-mode fixes and IOPM support

This commit is contained in:
Jeff Parsons 2015-07-24 16:53:39 -07:00
commit afcdd055e1
7 changed files with 445 additions and 75 deletions

View file

@ -1662,7 +1662,7 @@ if (DEBUGGER) {
{
if (dbgAddr.sel != null) {
var seg = this.getSegment(dbgAddr.sel);
if (!seg || dbgAddr.off > seg.limit) {
if (!seg || (dbgAddr.off >>> 0) >= seg.offMax) {
/*
* TODO: This automatic wrap-to-zero is OK for normal segments, but for expand-down segments, not so much.
*/
@ -1777,7 +1777,7 @@ if (DEBUGGER) {
}
};
Debugger.aTSSFields = {
Debugger.TSS286 = {
"PREV_TSS": 0x00,
"CPL0_SP": 0x02,
"CPL0_SS": 0x04,
@ -1802,6 +1802,35 @@ if (DEBUGGER) {
"TASK_LDT": 0x2a
};
Debugger.TSS386 = {
"PREV_TSS": 0x00,
"CPL0_ESP": 0x04,
"CPL0_SS": 0x08,
"CPL1_ESP": 0x0c,
"CPL1_SS": 0x10,
"CPL2_ESP": 0x14,
"CPL2_SS": 0x18,
"TASK_CR3": 0x1C,
"TASK_EIP": 0x20,
"TASK_PS": 0x24,
"TASK_EAX": 0x28,
"TASK_ECX": 0x2C,
"TASK_EDX": 0x30,
"TASK_EBX": 0x34,
"TASK_ESP": 0x38,
"TASK_EBP": 0x3C,
"TASK_ESI": 0x40,
"TASK_EDI": 0x44,
"TASK_ES": 0x48,
"TASK_CS": 0x4C,
"TASK_SS": 0x50,
"TASK_DS": 0x54,
"TASK_FS": 0x58,
"TASK_GS": 0x5C,
"TASK_LDT": 0x60,
"TASK_IOPM": 0x64
};
/**
* dumpBus(s)
*
@ -2011,15 +2040,34 @@ if (DEBUGGER) {
if (!seg) return;
var sDump = "";
for (var sField in Debugger.aTSSFields) {
var off = Debugger.aTSSFields[sField];
var ch = (sField.length < 8? ' ' : '');
var addr = seg.base + off;
var w = this.cpu.probeAddr(addr) | (this.cpu.probeAddr(addr + 1) << 8);
var type = seg.type & ~X86.DESC.ACC.TSS_BUSY;
var cch = (type == X86.DESC.ACC.TYPE.TSS286? 4 : 8);
var aTSSFields = (type == X86.DESC.ACC.TYPE.TSS286? Debugger.TSS286 : Debugger.TSS386);
var off, addr, v;
for (var sField in aTSSFields) {
off = aTSSFields[sField];
addr = seg.base + off;
v = this.cpu.probeAddr(addr) | (this.cpu.probeAddr(addr + 1) << 8);
if (type == X86.DESC.ACC.TYPE.TSS386) {
v |= (this.cpu.probeAddr(addr + 2) << 16) | (this.cpu.probeAddr(addr + 3) << 24);
}
if (sDump) sDump += '\n';
sDump += str.toHexWord(off) + " " + sField + ": " + ch + str.toHexWord(w);
sDump += str.toHexWord(off) + ' ' + str.pad(sField + ':', 11) + str.toHex(v, cch);
}
if (type == X86.DESC.ACC.TYPE.TSS386) {
var iPort = 0;
off = (v >>> 16);
/*
* We arbitrarily cut the IOPM dump off at port 0x3FF, because we're not interested in anything above that.
*/
while (off < seg.offMax && iPort < 0x3ff) {
addr = seg.base + off;
v = this.cpu.probeAddr(addr) | (this.cpu.probeAddr(addr + 1) << 8);
sDump += "\nports " + str.toHexWord(iPort) + '-' + str.toHexWord(iPort+15) + ": " + str.toBinBytes(v, 2);
iPort += 16;
off += 2;
}
}
this.println(sDump);
};
@ -3841,7 +3889,7 @@ if (DEBUGGER) {
};
/**
* parseAddr(sAddr, type)
* parseAddr(sAddr, type, fNoChecks)
*
* As discussed above, dbgAddr variables contain one or more of: off, sel, and addr. They represent
* a segmented address (sel:off) when sel is defined or a linear address (addr) when sel is undefined
@ -3865,9 +3913,10 @@ if (DEBUGGER) {
* @this {Debugger}
* @param {string|undefined} sAddr
* @param {number|undefined} [type] is the address segment type, in case sAddr doesn't specify a segment
* @param {boolean} [fNoChecks] (eg, true when setting breakpoints that may not be valid now, but will be later)
* @return {{DbgAddr}}
*/
Debugger.prototype.parseAddr = function(sAddr, type)
Debugger.prototype.parseAddr = function(sAddr, type, fNoChecks)
{
var dbgAddr;
var dbgAddrNext = (type === Debugger.ADDR_CODE? this.dbgAddrNextCode : this.dbgAddrNextData);
@ -3902,7 +3951,7 @@ if (DEBUGGER) {
}
dbgAddr = this.newAddr(off, sel, addr);
this.checkLimit(dbgAddr);
if (!fNoChecks) this.checkLimit(dbgAddr);
return dbgAddr;
};
@ -3998,7 +4047,7 @@ if (DEBUGGER) {
var fError = false;
var sExpOrig = sExp;
var aVals = [], aOps = [];
var asValues = sExp.split(/[|^&+%\/*-]/); // RegExp of "binops" only (unary and others saved for a rainy day)
var asValues = sExp.split(/[|^&+%\/*-]/); // RegExp of "binops" only (unary and other "ops" saved for a rainy day)
for (var i = 0; i < asValues.length; i++) {
var sValue = asValues[i];
var s = str.trim(asValues[i]);
@ -4026,7 +4075,7 @@ if (DEBUGGER) {
}
if (!fError) {
value = aVals.pop();
if (fPrint) this.println(sExpOrig + "=" + str.toHex(value) + "h bin=" + str.toBinBytes(value) + " dec=" + value + '.');
if (fPrint) this.println(sExpOrig + '=' + str.toHex(value) + "h (" + value + ". " + str.toBinBytes(value) + ')');
} else {
if (fPrint) this.println("error parsing '" + sExpOrig + "' at character " + (sExpOrig.length - sExp.length));
}
@ -4405,6 +4454,10 @@ if (DEBUGGER) {
* These two new commands operate as toggles so that if "*" is used to trap all input (or output),
* you can also use these commands to NOT trap specific ports.
*
* TODO: Update the "bl" command to include any/all I/O breakpoints, and the "bc" command to
* clear them. Because "bi" and "bo" commands are piggy-backing on Bus functions, those breakpoints
* are outside the realm of what "bl" and "bc" are aware of.
*
* @this {Debugger}
* @param {string} sCmd
* @param {string} [sAddr]
@ -4437,7 +4490,7 @@ if (DEBUGGER) {
}
var dbgAddr = {};
if (sAddr != "*") {
dbgAddr = this.parseAddr(sAddr, Debugger.ADDR_CODE);
dbgAddr = this.parseAddr(sAddr, Debugger.ADDR_CODE, true);
if (dbgAddr.off == null) return;
}
sAddr = (dbgAddr.off == null? sAddr : str.toHexWord(dbgAddr.off));
@ -5747,7 +5800,11 @@ if (DEBUGGER) {
}
}
else {
this.println(">> " + sCmd);
var sPrompt = ">> ";
if (this.cpu.regCR0 & X86.CR0.MSW.PE) {
sPrompt = (this.cpu.regPS & X86.PS.VM)? "-- " : "## ";
}
this.println(sPrompt + sCmd);
}
sCmd = sCmd.toLowerCase();

View file

@ -1070,6 +1070,7 @@ X86CPU.prototype.initProcessor = function()
* nested-task weirdness in real-mode.
*/
this.PS_CLEAR_RM = X86.PS.NT;
this.PS_DIRECT |= X86.PS.RF | X86.PS.VM;
this.aOps[X86.OPCODE.FS] = X86.opFS; // 0x64
this.aOps[X86.OPCODE.GS] = X86.opGS; // 0x65
this.aOps[X86.OPCODE.OS] = X86.opOS; // 0x66
@ -1596,7 +1597,7 @@ X86CPU.prototype.checkIntReturn = function(addr)
};
/**
* setProtMode(fProt)
* setProtMode(fProt, fV86)
*
* Update any opcode handlers that operate significantly differently in real-mode vs. protected-mode, and
* notify all the segment registers about the mode change as well -- but only those that are "bi-modal"; internal
@ -1609,23 +1610,27 @@ X86CPU.prototype.checkIntReturn = function(addr)
*
* @this {X86CPU}
* @param {boolean} [fProt] (use the current MSW PE bit if not specified)
* @param {boolean} [fV86] true if the X86.PS.VM (V86-mode) flag is set (or is about to be)
*/
X86CPU.prototype.setProtMode = function(fProt)
X86CPU.prototype.setProtMode = function(fProt, fV86)
{
if (fProt === undefined) {
fProt = !!(this.regCR0 & X86.CR0.MSW.PE);
}
if (!fProt != !(this.regCR0 & X86.CR0.MSW.PE) && this.messageEnabled()) {
this.printMessage("CPU switching to " + (fProt? "protected" : "real") + "-mode", this.bitsMessage, true);
if (fV86 === undefined) {
fV86 = !!(this.regPS & X86.PS.VM);
}
this.aOpGrp6 = (fProt? X86.aOpGrp6Prot : X86.aOpGrp6Real);
this.segCS.updateMode();
this.segDS.updateMode();
this.segSS.updateMode();
this.segES.updateMode();
if (!fProt != !(this.regCR0 & X86.CR0.MSW.PE) && this.messageEnabled()) {
this.printMessage("CPU switching to " + (fProt? (fV86? "v86" : "protected") : "real") + "-mode", this.bitsMessage, true);
}
this.aOpGrp6 = (fProt && !fV86? X86.aOpGrp6Prot : X86.aOpGrp6Real);
this.segCS.updateMode(false, fProt, fV86);
this.segDS.updateMode(false, fProt, fV86);
this.segSS.updateMode(false, fProt, fV86);
this.segES.updateMode(false, fProt, fV86);
if (I386 && this.model >= X86.MODEL_80386) {
this.segFS.updateMode();
this.segGS.updateMode();
this.segFS.updateMode(false, fProt, fV86);
this.segGS.updateMode(false, fProt, fV86);
this.resetSizes();
}
};
@ -2740,6 +2745,31 @@ X86CPU.prototype.setPS = function(regPS, cpl)
}
};
/**
* checkIOPM(port, nPorts)
*
* @this {X86CPU}
* @param {number} port (0x0000 to 0xffff)
* @param {number} nPorts (1 to 4)
* @return {boolean} true if allowed, false if not
*/
X86CPU.prototype.checkIOPM = function(port, nPorts)
{
var bitsPorts = 0;
if (I386 && (this.regCR0 & X86.CR0.MSW.PE) && this.segCS.cpl > this.nIOPL && this.segTSS.addrIOPM) {
var offIOPM = port >>> 3;
var addrIOPM = this.segTSS.addrIOPM + offIOPM;
bitsPorts = ((1 << nPorts) - 1) << (port & 0x7);
while (bitsPorts && addrIOPM <= this.segTSS.addrIOPMLimit) {
var bits = this.getByte(addrIOPM);
if (bits & bitsPorts) break;
bitsPorts >>>= 8;
addrIOPM++;
}
}
return !bitsPorts;
};
/**
* traceLog(prop, dst, src, flagsIn, flagsOut, resultLo, resultHi)
*

View file

@ -142,6 +142,28 @@ X86.fnANDw = function ANDw(dst, src)
*/
X86.fnARPL = function ARPL(dst, src)
{
/*
* ARPL is one of several protected-mode instructions that are meaningless and not allowed in either real-mode
* or V86-mode; others include LAR, LSL, VERR and VERW. More meaningful but potentially harmful protected-mode
* instructions that ARE allowed in real-mode but NOT in V86-mode include LIDT, LGDT, LMSW, CLTS, HLT, and
* control register MOV instructions.
*
* ARPL is somewhat more noteworthy because enhanced-mode Windows (going back to at least Windows 3.00, and
* possibly even the earliest versions of Windows/386) selected the ARPL opcode as a controlled means of exiting
* V86-mode via its UD_FAULT exception. Windows would use the same ARPL for all controlled exits, using different
* segment:offset pointers to the ARPL to differentiate them. ARPL was probably chosen because it could trigger
* a UD_FAULT with a single byte (0x63); any subsequent address bytes would be irrelevant.
*
* TODO: You may have noticed that setProtMode() already swaps out a 0x0F opcode dispatch table for another based
* on the mode, because none of the "GRP6" 0x0F opcodes (eg, SLDT, STR, LLDT, LTR, VERR and VERW) are allowed in
* real-mode, and it was easy to swap all those handlers in/out with a single update. We've extended that particular
* swap to include V86-mode as well, but we might want to consider swapping out more opcode handlers in a similar
* fashion, instead of using these in-line mode tests.
*/
if (!(this.regCR0 & X86.CR0.MSW.PE) || I386 && (this.regPS & X86.PS.VM)) {
X86.opInvalid.call(this);
return dst;
}
this.nStepCycles -= (10 + (this.regEA === X86.ADDR_INVALID? 0 : 1));
if ((dst & X86.SEL.RPL) < (src & X86.SEL.RPL)) {
dst = (dst & ~X86.SEL.RPL) | (src & X86.SEL.RPL);
@ -1247,7 +1269,7 @@ X86.fnIRET = function IRET()
if (this.regPS & X86.PS.NT) {
var addrNew = this.segTSS.base;
/*
* Fortunately, X86.TSS286.PREV_TSS and X86.TSS386.PREV_TSS are at the same TSS offset.
* Fortunately, X86.TSS286.PREV_TSS and X86.TSS386.PREV_TSS refer to the same TSS offset.
*/
var sel = this.getShort(addrNew + X86.TSS286.PREV_TSS);
this.segCS.switchTSS(sel, false);
@ -1259,6 +1281,51 @@ X86.fnIRET = function IRET()
var newCS = this.popWord();
var newPS = this.popWord();
if (I386) {
if (this.regPS & X86.PS.VM) {
/*
* On the 80386, in V86-mode, RF is the only defined EFLAGS bit above bit 15 that may be changed by IRETD.
* This is less restrictive than POPFD, which cannot change ANY bits above bit 15; see opPOPF() for details.
*/
newPS = (newPS & (0xffff | X86.PS.RF)) | (this.regPS & ~(0xffff | X86.PS.RF));
}
else {
if (newPS & X86.PS.VM) {
this.assert(!!(this.regCR0 & X86.CR0.MSW.PE));
/*
* We have to assume that a full V86-mode interrupt frame was on the protected-mode stack; namely:
*
* GS
* FS
* DS
* ES
* SS
* ESP
* EFLAGS
* CS
* EIP
*
* We've already popped EIP, CS, and EFLAGS into newIP, newCS and newPS, respectively, so we must now
* pop the rest, while we're still in protected-mode, before the switch to V86-mode alters the current
* operand size (among other things).
*/
var newSP = this.popWord();
var newSS = this.popWord();
var newES = this.popWord();
var newDS = this.popWord();
var newFS = this.popWord();
var newGS = this.popWord();
this.setProtMode(true, true); // flip the switch to V86-mode now
this.setSS(newSS);
this.setSP(newSP);
this.setES(newES);
this.setDS(newDS);
this.setFS(newFS);
this.setGS(newGS);
}
}
}
// if (DEBUG) this.printMessage(" returning to " + str.toHex(newCS, 4) + ':' + str.toHex(newIP, this.dataSize << 1), this.bitsMessage, true);
if (this.setCSIP(newIP, newCS, false) != null) {
@ -1478,7 +1545,10 @@ X86.fnLFS = function LFS(dst, src)
*/
X86.fnLGDT = function LGDT(dst, src)
{
if (this.regEA === X86.ADDR_INVALID) {
/*
* TODO: Consider swapping out this function whenever setProtMode() changes the mode to V86-mode.
*/
if (this.regEA === X86.ADDR_INVALID || I386 && (this.regPS & X86.PS.VM)) {
X86.opInvalid.call(this);
} else {
/*
@ -1537,7 +1607,10 @@ X86.fnLGS = function LGS(dst, src)
*/
X86.fnLIDT = function LIDT(dst, src)
{
if (this.regEA === X86.ADDR_INVALID) {
/*
* TODO: Consider swapping out this function whenever setProtMode() changes the mode to V86-mode.
*/
if (this.regEA === X86.ADDR_INVALID || I386 && (this.regPS & X86.PS.VM)) {
X86.opInvalid.call(this);
} else {
/*
@ -1586,9 +1659,16 @@ X86.fnLLDT = function LLDT(dst, src) {
*/
X86.fnLMSW = function LMSW(dst, src)
{
this.setMSW(dst);
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? 3 : 6);
this.opFlags |= X86.OPFLAG.NOWRITE;
/*
* TODO: Consider swapping out this function whenever setProtMode() changes the mode to V86-mode.
*/
if (I386 && (this.regPS & X86.PS.VM)) {
X86.opInvalid.call(this);
} else {
this.setMSW(dst);
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? 3 : 6);
this.opFlags |= X86.OPFLAG.NOWRITE;
}
return dst;
};
@ -3640,7 +3720,10 @@ X86.fnFault = function(nFault, nError, fHalt, nCycles)
fDispatch = false;
}
if (fDispatch) X86.fnINT.call(this, this.nFault = nFault, nError, nCycles || 0);
if (fDispatch) {
this.nFault = nFault;
X86.fnINT.call(this, nFault, nError, nCycles || 0);
}
/*
* Since this fault is likely being issued in the context of an instruction that hasn't finished
@ -3652,7 +3735,7 @@ X86.fnFault = function(nFault, nError, fHalt, nCycles)
* opPUSHA(): if a GP fault occurs on any PUSH other than the last, a subsequent PUSH is likely to
* cause another fault, which we will misinterpret as a double-fault.
*
* TODO: Throw a special JavaScript exception that cpu.js must intercept and quietly ignore.
* TODO: Throw a special JavaScript exception that cpu.js must intercept and quietly redirect.
*/
this.opFlags |= (X86.OPFLAG.NOREAD | X86.OPFLAG.NOWRITE);
};
@ -3718,6 +3801,15 @@ X86.fnFaultMessage = function(nFault, nError, fHalt)
bitsMessage |= Messages.CPU;
}
/*
* Windows 3.00 (and other versions of enhanced-mode Windows) use an ARPL in V86-mode to switch out
* of V86-mode; we don't need to report the UD_FAULT by default.
*/
if (bOpcode == X86.OPCODE.ARPL && (this.regPS & X86.PS.VM)) {
fHalt = false;
bitsMessage |= Messages.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

View file

@ -73,6 +73,13 @@ X86.opGRP7 = function GRP7()
*/
X86.opLAR = function LAR()
{
/*
* TODO: Consider swapping out this function whenever setProtMode() changes the mode to real-mode or V86-mode.
*/
if (!(this.regCR0 & X86.CR0.MSW.PE) || I386 && (this.regPS & X86.PS.VM)) {
X86.opInvalid.call(this);
return;
}
this.aOpModRegWord[this.getIPByte()].call(this, X86.fnLAR);
};
@ -85,6 +92,13 @@ X86.opLAR = function LAR()
*/
X86.opLSL = function LSL()
{
/*
* TODO: Consider swapping out this function whenever setProtMode() changes the mode to real-mode or V86-mode.
*/
if (!(this.regCR0 & X86.CR0.MSW.PE) || I386 && (this.regPS & X86.PS.VM)) {
X86.opInvalid.call(this);
return;
}
this.aOpModRegWord[this.getIPByte()].call(this, X86.fnLSL);
};
@ -187,6 +201,9 @@ X86.opLOADALL = function LOADALL()
*/
X86.opCLTS = function CLTS()
{
/*
* NOTE: The following code shouldn't need to test for X86.PS.VM because V86-mode is CPL 3.
*/
if (this.segCS.cpl) {
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
return;
@ -216,18 +233,19 @@ X86.opCLTS = function CLTS()
*/
X86.opMOVrc = function MOVrc()
{
var bModRM = this.getIPByte();
/*
* NOTE: The following code shouldn't need to test for X86.PS.VM because V86-mode is CPL 3.
*/
if (this.segCS.cpl) {
/*
* You're not allowed to read control registers if the current privilege level is not zero
* (TODO: I'm issuing this AFTER fetching the ModRM byte, but I assume it makes no difference).
*/
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
return;
}
var reg;
var bModRM = this.getIPByte();
switch((bModRM & 0x38) >> 3) {
case 0x0:
reg = this.regCR0;
@ -298,18 +316,19 @@ X86.opMOVrc = function MOVrc()
*/
X86.opMOVcr = function MOVcr()
{
var bModRM = this.getIPByte();
/*
* NOTE: The following code shouldn't need to test for X86.PS.VM because V86-mode is CPL 3.
*/
if (this.segCS.cpl) {
/*
* You're not allowed to write control registers if the current privilege level is not zero
* (TODO: I'm issuing this AFTER fetching the ModRM byte, but I assume it makes no difference).
*/
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
return;
}
var reg;
var bModRM = this.getIPByte();
switch(bModRM & 0x7) {
case 0x0:
reg = this.regEAX;

View file

@ -1502,12 +1502,17 @@ X86.opINSb = function INSb()
}
if (nReps--) {
var b = this.bus.checkPortInputNotify(this.regEDX & 0xffff, this.regLIP - nDelta - 1);
var port = this.regEDX & 0xffff;
if (!this.checkIOPM(port, 1)) {
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
return;
}
var b = this.bus.checkPortInputNotify(port, this.regLIP - nDelta - 1);
if (BACKTRACK) this.backTrack.btiMemLo = this.backTrack.btiIO;
this.setSOByte(this.segES, this.regEDI & this.addrMask, b);
this.regEDI = (this.regEDI & ~this.addrMask) | ((this.regEDI + ((this.regPS & X86.PS.DF)? -1 : 1)) & this.addrMask);
this.nStepCycles -= nCycles;
this.regECX = (this.regECX & ~this.addrMask) | ((this.regECX - nDelta) & this.addrMask);
this.nStepCycles -= nCycles;
if (nReps) {
if (BUGS_8086) {
this.rewindIP(-2); // this instruction does not support multiple overrides
@ -1550,8 +1555,13 @@ X86.opINSw = function INSw()
if (nReps--) {
var addrFrom = this.regLIP - nDelta - 1;
var w = 0, shift = 0;
var port = this.regEDX & 0xffff;
if (!this.checkIOPM(port, 1)) {
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
return;
}
for (var n = 0; n < this.dataSize; n++) {
w |= this.bus.checkPortInputNotify(this.regEDX & 0xffff, addrFrom) << shift;
w |= this.bus.checkPortInputNotify(port, addrFrom) << shift;
shift += 8;
if (BACKTRACK) {
if (!n) {
@ -1563,8 +1573,8 @@ X86.opINSw = function INSw()
}
this.setSOWord(this.segES, this.regEDI & this.addrMask, w);
this.regEDI = (this.regEDI & ~this.addrMask) | ((this.regEDI + ((this.regPS & X86.PS.DF)? -this.dataSize : this.dataSize)) & this.addrMask);
this.nStepCycles -= nCycles;
this.regECX = (this.regECX & ~this.addrMask) | ((this.regECX - nDelta) & this.addrMask);
this.nStepCycles -= nCycles;
if (nReps) {
if (BUGS_8086) {
this.rewindIP(-2); // this instruction does not support multiple overrides
@ -1604,12 +1614,17 @@ X86.opOUTSb = function OUTSb()
if (this.opPrefixes & X86.OPFLAG.REPEAT) nCycles = 4;
}
if (nReps--) {
var port = this.regEDX & 0xffff;
if (!this.checkIOPM(port, 1)) {
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
return;
}
var b = this.getSOByte(this.segDS, this.regESI & this.addrMask);
this.regESI = (this.regESI & ~this.addrMask) | ((this.regESI + ((this.regPS & X86.PS.DF)? -1 : 1)) & this.addrMask);
this.nStepCycles -= nCycles;
if (BACKTRACK) this.backTrack.btiIO = this.backTrack.btiMemLo;
this.bus.checkPortOutputNotify(this.regEDX & 0xffff, b, this.regLIP - nDelta - 1);
this.bus.checkPortOutputNotify(port, b, this.regLIP - nDelta - 1);
this.regESI = (this.regESI & ~this.addrMask) | ((this.regESI + ((this.regPS & X86.PS.DF)? -1 : 1)) & this.addrMask);
this.regECX = (this.regECX & ~this.addrMask) | ((this.regECX - nDelta) & this.addrMask);
this.nStepCycles -= nCycles;
if (nReps) {
if (BUGS_8086) {
this.rewindIP(-2); // this instruction does not support multiple overrides
@ -1650,9 +1665,12 @@ X86.opOUTSw = function OUTSw()
}
if (nReps--) {
var w = this.getSOWord(this.segDS, this.regESI & this.addrMask);
this.regESI = (this.regESI & ~this.addrMask) | ((this.regESI + ((this.regPS & X86.PS.DF)? -this.dataSize : this.dataSize)) & this.addrMask);
this.nStepCycles -= nCycles;
var addrFrom = this.regLIP - nDelta - 1, shift = 0;
var port = this.regEDX & 0xffff;
if (!this.checkIOPM(port, 1)) {
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
return;
}
for (var n = 0; n < this.dataSize; n++) {
if (BACKTRACK) {
if (!n) {
@ -1661,10 +1679,12 @@ X86.opOUTSw = function OUTSw()
this.backTrack.btiIO = this.backTrack.btiMemHi;
}
}
this.bus.checkPortOutputNotify(this.regEDX & 0xffff, (w >> shift) & 0xff, addrFrom);
this.bus.checkPortOutputNotify(port, (w >> shift) & 0xff, addrFrom);
shift += 8;
}
this.regESI = (this.regESI & ~this.addrMask) | ((this.regESI + ((this.regPS & X86.PS.DF)? -this.dataSize : this.dataSize)) & this.addrMask);
this.regECX = (this.regECX & ~this.addrMask) | ((this.regECX - nDelta) & this.addrMask);
this.nStepCycles -= nCycles;
if (nReps) {
if (BUGS_8086) {
this.rewindIP(-2); // this instruction does not support multiple overrides
@ -2458,7 +2478,19 @@ X86.opPUSHF = function PUSHF()
*/
X86.opPOPF = function POPF()
{
this.setPS(this.popWord());
/*
* TODO: Consider swapping out this function whenever setProtMode() changes the mode to V86-mode.
*/
if (I386 && (this.regPS & X86.PS.VM) && this.nIOPL < 3) {
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
return;
}
/*
* On the 80386, regardless of mode, VM and RF (the only defined EFLAGS bit above bit 15) are never changed by POPFD.
*/
var newPS = this.popWord();
if (I386) newPS = (newPS & 0xffff) | (this.regPS & ~0xffff);
this.setPS(newPS);
/*
* NOTE: I'm assuming that neither POPF nor IRET are required to set NOINTR like STI does.
*/
@ -3353,6 +3385,13 @@ X86.opRETF = function RETF()
*/
X86.opINT3 = function INT3()
{
/*
* TODO: Consider swapping out this function whenever setProtMode() changes the mode to V86-mode.
*/
if (I386 && (this.regPS & X86.PS.VM) && this.nIOPL < 3) {
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
return;
}
/*
* To give our own Debugger the ability to stop execution on INT3, I thought about treating this as
* a fault rather than an interrupt, in order to leverage the existing Debugger logic inside fnFault()
@ -3389,6 +3428,13 @@ X86.opINT3 = function INT3()
*/
X86.opINTn = function INTn()
{
/*
* TODO: Consider swapping out this function whenever setProtMode() changes the mode to V86-mode.
*/
if (I386 && (this.regPS & X86.PS.VM) && this.nIOPL < 3) {
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
return;
}
var nInt = this.getIPByte();
if (this.checkIntNotify(nInt)) {
X86.fnINT.call(this, nInt, null, 0);
@ -3405,6 +3451,13 @@ X86.opINTn = function INTn()
X86.opINTO = function INTO()
{
if (this.getOF()) {
/*
* TODO: Consider swapping out this function whenever setProtMode() changes the mode to V86-mode.
*/
if (I386 && (this.regPS & X86.PS.VM) && this.nIOPL < 3) {
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
return;
}
X86.fnINT.call(this, X86.EXCEPTION.OVERFLOW, null, this.cycleCounts.nOpCyclesIntOD);
return;
}
@ -3639,6 +3692,10 @@ X86.opJCXZ = function JCXZ()
X86.opINb = function INb()
{
var port = this.getIPByte();
if (!this.checkIOPM(port, 1)) {
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
return;
}
this.regEAX = (this.regEAX & ~0xff) | this.bus.checkPortInputNotify(port, this.regLIP - 2);
if (BACKTRACK) this.backTrack.btiAL = this.backTrack.btiIO;
this.nStepCycles -= this.cycleCounts.nOpCyclesInP;
@ -3652,6 +3709,10 @@ X86.opINb = function INb()
X86.opINw = function INw()
{
var port = this.getIPByte();
if (!this.checkIOPM(port, 2)) {
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
return;
}
this.regEAX = this.bus.checkPortInputNotify(port, this.regLIP - 2);
if (BACKTRACK) this.backTrack.btiAL = this.backTrack.btiIO;
/*
@ -3672,6 +3733,10 @@ X86.opINw = function INw()
X86.opOUTb = function OUTb()
{
var port = this.getIPByte();
if (!this.checkIOPM(port, 1)) {
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
return;
}
this.bus.checkPortOutputNotify(port, this.regEAX & 0xff, this.regLIP - 2);
this.nStepCycles -= this.cycleCounts.nOpCyclesOutP;
};
@ -3684,6 +3749,10 @@ X86.opOUTb = function OUTb()
X86.opOUTw = function OUTw()
{
var port = this.getIPByte();
if (!this.checkIOPM(port, 2)) {
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
return;
}
this.bus.checkPortOutputNotify(port, this.regEAX & 0xff, this.regLIP - 2);
/*
* TODO: Specs are clear that bits 8-15 of the port address for the FIRST byte of I/O will be zero, but
@ -3754,7 +3823,12 @@ X86.opJMPs = function JMPs()
*/
X86.opINDXb = function INDXb()
{
this.regEAX = (this.regEAX & ~0xff) | this.bus.checkPortInputNotify(this.regEDX & 0xffff, this.regLIP - 1);
var port = this.regEDX & 0xffff;
if (!this.checkIOPM(port, 1)) {
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
return;
}
this.regEAX = (this.regEAX & ~0xff) | this.bus.checkPortInputNotify(port, this.regLIP - 1);
if (BACKTRACK) this.backTrack.btiAL = this.backTrack.btiIO;
this.nStepCycles -= this.cycleCounts.nOpCyclesInDX;
};
@ -3766,9 +3840,14 @@ X86.opINDXb = function INDXb()
*/
X86.opINDXw = function INDXw()
{
this.regEAX = this.bus.checkPortInputNotify(this.regEDX & 0xffff, this.regLIP - 1);
var port = this.regEDX & 0xffff;
if (!this.checkIOPM(port, 2)) {
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
return;
}
this.regEAX = this.bus.checkPortInputNotify(port, this.regLIP - 1);
if (BACKTRACK) this.backTrack.btiAL = this.backTrack.btiIO;
this.regEAX |= (this.bus.checkPortInputNotify((this.regEDX + 1) & 0xffff, this.regLIP - 1) << 8);
this.regEAX |= (this.bus.checkPortInputNotify((port + 1) & 0xffff, this.regLIP - 1) << 8);
if (BACKTRACK) this.backTrack.btiAH = this.backTrack.btiIO;
this.nStepCycles -= this.cycleCounts.nOpCyclesInDX;
};
@ -3780,8 +3859,13 @@ X86.opINDXw = function INDXw()
*/
X86.opOUTDXb = function OUTDXb()
{
var port = this.regEDX & 0xffff;
if (!this.checkIOPM(port, 1)) {
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
return;
}
if (BACKTRACK) this.backTrack.btiIO = this.backTrack.btiAL;
this.bus.checkPortOutputNotify(this.regEDX & 0xffff, this.regEAX & 0xff, this.regLIP - 1);
this.bus.checkPortOutputNotify(port, this.regEAX & 0xff, this.regLIP - 1);
this.nStepCycles -= this.cycleCounts.nOpCyclesOutDX;
};
@ -3792,10 +3876,15 @@ X86.opOUTDXb = function OUTDXb()
*/
X86.opOUTDXw = function OUTDXw()
{
var port = this.regEDX & 0xffff;
if (!this.checkIOPM(port, 2)) {
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
return;
}
if (BACKTRACK) this.backTrack.btiIO = this.backTrack.btiAL;
this.bus.checkPortOutputNotify(this.regEDX & 0xffff, this.regEAX & 0xff, this.regLIP - 1);
this.bus.checkPortOutputNotify(port, this.regEAX & 0xff, this.regLIP - 1);
if (BACKTRACK) this.backTrack.btiIO = this.backTrack.btiAH;
this.bus.checkPortOutputNotify((this.regEDX + 1) & 0xffff, this.regEAX >> 8, this.regLIP - 1);
this.bus.checkPortOutputNotify((port + 1) & 0xffff, this.regEAX >> 8, this.regLIP - 1);
this.nStepCycles -= this.cycleCounts.nOpCyclesOutDX;
};
@ -3863,6 +3952,13 @@ X86.opREPZ = function REPZ()
*/
X86.opHLT = function HLT()
{
/*
* TODO: Consider swapping out this function whenever setProtMode() changes the mode to V86-mode.
*/
if (I386 && (this.regPS & X86.PS.VM)) {
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
return;
}
/*
* The CPU is never REALLY halted by a HLT instruction; instead, by setting X86.INTFLAG.HALT,
* we are signalling to stepCPU() that it's free to end the current burst AND that it should not

View file

@ -91,6 +91,11 @@ function X86Seg(cpu, id, sName, fProt)
this.addrDesc = X86.ADDR_INVALID;
this.dataSize = this.addrSize = 2;
this.dataMask = this.addrMask = 0xffff;
this.loadV86 = this.loadReal;
this.checkReadV86 = this.checkReadReal;
this.checkWriteV86 = this.checkWriteReal;
/*
* The following properties are used for CODE segments only (ie, segCS); if the process of loading
* CS also requires a stack switch, then fStackSwitch will be set to true; additionally, if the stack
@ -242,7 +247,7 @@ X86Seg.prototype.loadIDTReal = function loadIDTReal(nIDT)
*
* "[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
* TODO: Verify that 80286 real-mode actually enforces the above. See http://www.pcjs.org/pubs/pc/reference/intel/80286/progref/#page-260
*/
var addrIDT = cpu.addrIDT + (nIDT << 2);
var off = cpu.getShort(addrIDT);
@ -536,7 +541,7 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fSuppress)
if (this.id == X86Seg.ID.CODE) {
this.fStackSwitch = false;
var fCall = this.fCall;
var regPSMask, nFaultError, regSP;
var regPSClear, nFaultError, regSP;
var rpl = sel & X86.SEL.RPL;
var dpl = (acc & X86.DESC.ACC.DPL.MASK) >> X86.DESC.ACC.DPL.SHIFT;
@ -579,19 +584,19 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fSuppress)
}
else if (type == X86.DESC.ACC.TYPE.GATE_CALL || type == X86.DESC.ACC.TYPE.GATE386_CALL) {
fGate = true;
regPSMask = ~0;
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;
regPSMask = ~(X86.PS.NT | X86.PS.TF | X86.PS.IF);
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;
regPSMask = ~(X86.PS.NT | X86.PS.TF);
regPSClear = (X86.PS.NT | X86.PS.TF);
nFaultError = sel | X86.ERRCODE.EXT;
cpu.assert(!(acc & 0x1f));
}
@ -614,6 +619,7 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fSuppress)
* TODO: Verify the PRESENT bit of the gate descriptor, and issue NP_FAULT as appropriate.
*/
cplPrev = this.cpl;
/*
* For gates, there is no "base" and "limit", but rather "selector" and "offset"; the selector
* is located where the first 16 bits of base are normally stored, and the offset comes from the
@ -623,11 +629,24 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fSuppress)
if (I386 && (type & X86.DESC.ACC.NONSEG_386)) {
limit = limitOrig | (ext << 16);
}
/*
* At a minimum, we need to clear X86.PS.VM now, so that the following load will initialize the
* new code segment properly.
*/
var regPS = cpu.regPS;
cpu.regPS &= ~regPSClear;
if (cpu.regPS & X86.PS.VM) {
cpu.regPS &= ~X86.PS.VM;
cpu.setProtMode(true, false);
}
if (this.load(selCode, true) === X86.ADDR_INVALID) {
cpu.assert(false);
base = addrDesc = X86.ADDR_INVALID;
break;
}
cpu.regEIP = limit;
if (this.cpl < cplPrev) {
if (fCall !== true) {
@ -655,13 +674,22 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fSuppress)
offSS = offSP + 4;
cpu.setSS(cpu.getShort(addrTSS + offSS), true);
cpu.setSP(cpu.getLong(addrTSS + offSP));
if (regPS & X86.PS.VM) {
cpu.pushWord(cpu.segGS.sel);
cpu.setGS(0);
cpu.pushWord(cpu.segFS.sel);
cpu.setFS(0);
cpu.pushWord(cpu.segDS.sel);
cpu.setDS(0);
cpu.pushWord(cpu.segES.sel);
cpu.setES(0);
}
}
cpu.pushWord(regSSPrev);
cpu.pushWord(regSPPrev);
while (i) cpu.pushWord(this.awParms[--i]);
this.fStackSwitch = true;
}
cpu.regPS &= regPSMask;
return this.base;
}
cpu.assert(false);
@ -730,6 +758,14 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fSuppress)
base = addrDesc = X86.ADDR_INVALID;
break;
}
/*
* For more efficient IOPM lookups, we cache the starting linear address in segTSS.addrIOPM, and the
* last valid address in segTSS.addrIOPMLimit.
*/
if (typeTSS == X86.DESC.ACC.TYPE.TSS386) {
this.addrIOPM = (base + cpu.getShort(base + X86.TSS386.TASK_IOPM))|0;
this.addrIOPMLimit = (base + this.limit)|0;
}
}
else if (this.id == X86Seg.ID.OTHER) {
/*
@ -935,7 +971,7 @@ X86Seg.prototype.switchTSS = function switchTSS(selNew, fNest)
}
/*
* Fortunately, X86.TSS286.PREV_TSS and X86.TSS386.PREV_TSS are at the same TSS offset.
* Fortunately, X86.TSS286.PREV_TSS and X86.TSS386.PREV_TSS refer to the same TSS offset.
*/
if (fNest) cpu.setShort(addrNew + X86.TSS286.PREV_TSS, selOld);
@ -1032,7 +1068,7 @@ X86Seg.prototype.restore = function(a)
};
/**
* updateMode(fLoad, fProt)
* updateMode(fLoad, fProt, fV86)
*
* Ensures that the segment register's access (ie, load and check methods) matches the specified (or current)
* operating mode (real or protected).
@ -1040,9 +1076,9 @@ X86Seg.prototype.restore = function(a)
* @this {X86Seg}
* @param {boolean} [fLoad] true if the segment was just (re)loaded, false if not
* @param {boolean} [fProt] true for protected-mode access, false for real-mode access, undefined for current mode
* @return {boolean}
* @param {boolean} [fV86] true for V86-mode access, false for protected-mode access, undefined for current mode
*/
X86Seg.prototype.updateMode = function(fLoad, fProt)
X86Seg.prototype.updateMode = function(fLoad, fProt, fV86)
{
if (fProt === undefined) {
fProt = !!(this.cpu.regCR0 & X86.CR0.MSW.PE);
@ -1061,6 +1097,30 @@ X86Seg.prototype.updateMode = function(fLoad, fProt)
this.checkRead = this.checkReadProt;
this.checkWrite = this.checkWriteProt;
if (fV86 === undefined) {
fV86 = !!(this.cpu.regPS & X86.PS.VM);
}
if (fV86) {
this.load = this.loadV86;
this.checkRead = this.checkReadV86;
this.checkWrite = this.checkWriteV86;
/*
* One important feature of V86-mode (as compared to real-mode) are that other segment attributes
* (eg, limit, operand size, address size, etc) ARE updated, whereas in real-mode, segment attributes
* remain set to whatever was in effect in protected-mode.
*/
this.cpl = this.dpl = 3;
this.dataSize = this.addrSize = 2;
this.dataMask = this.addrMask = 0xffff;
this.limit = 0xffff;
this.offMax = this.limit + 1;
this.addrSize = this.dataSize;
this.addrDesc = X86.ADDR_INVALID;
this.fStackSwitch = false;
return;
}
/*
* TODO: For null GDT selectors, should we rely on the descriptor being invalid, or should we assume that
* the null descriptor might contain uninitialized (or other) data? I'm assuming the latter, hence the
@ -1134,6 +1194,12 @@ X86Seg.prototype.updateMode = function(fLoad, fProt)
this.addrMask = this.dataMask;
}
} else {
/*
* One important feature of real-mode (as compared to V86-mode) are that other segment attributes
* (eg, limit, operand size, address size, etc) are NOT updated, enabling features like "big real-mode"
* (aka "unreal mode"), which is used by system software like HIMEM.SYS to access extended memory from
* real-mode.
*/
this.load = this.loadReal;
this.loadIDT = this.loadIDTReal;
this.checkRead = this.checkReadReal;
@ -1142,7 +1208,6 @@ X86Seg.prototype.updateMode = function(fLoad, fProt)
this.addrDesc = X86.ADDR_INVALID;
this.fStackSwitch = false;
}
return fProt;
};
/**

View file

@ -42,13 +42,14 @@ var str = {};
* So use this function to validate the entire string.
*
* @param {string} s is the string representation of some number
* @param {number} [base] is the radix of the number represented above (only 10 and 16 are supported)
* @param {number} [base] is the radix of the number represented above (only 2, 10 and 16 are supported)
* @return {boolean} true if valid, false if invalid (or the specified base isn't supported)
*/
str.isValidInt = function(s, base)
{
if (!base || base == 10) return s.match(/^[0-9]+$/) !== null;
if (base == 16) return s.match(/^[0-9a-f]+$/i) !== null;
if (base == 2) return s.match(/^[01]+$/i) !== null;
return false;
};
@ -56,8 +57,11 @@ str.isValidInt = function(s, base)
* parseInt(s, base)
*
* This is a wrapper around the built-in parseInt() function, which recognizes certain prefixes (eg,
* '$' or "0x" for hex) and suffixes (eg, 'h' for hex or '.' for decimal), and then calls isValidInt()
* to ensure we don't get partial values (see isValidInt() for details).
* '$' or "0x" for hex) and suffixes (eg, 'h' for hex, or '.' for decimal), and then calls isValidInt()
* to ensure we don't convert strings that contain partial values (see isValidInt() for details).
*
* We don't support multiple prefix/suffix combinations, nor do we support the "0b" prefix (or "b" suffix)
* for binary, because 1) it's not commonly used, and 2) it conflicts with valid hex sequences.
*
* @param {string} s is the string representation of some number
* @param {number} [base] is the default radix to use (default is 16); can be overridden by prefixes/suffixes
@ -84,7 +88,7 @@ str.parseInt = function(s, base)
base = 10;
chSuffix = null;
}
if (chSuffix === null) s = s.substr(0, s.length-1);
if (chSuffix == null) s = s.substr(0, s.length-1);
}
var v;
if (str.isValidInt(s, base) && !isNaN(v = parseInt(s, base))) {
@ -108,13 +112,16 @@ str.toBin = function(n, cch)
var s = "";
if (cch === undefined) {
cch = 32;
s = "b";
} else {
if (cch > 32) cch = 32;
}
/*
* 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.
*
* Alternatively, we could mask and shift n regardless of whether it's null/undefined/NaN,
* since JavaScript coerces such operands to zero, but I think there's "value" in seeing those
* values displayed differently.
*/
if (n == null || isNaN(n)) {
while (cch-- > 0) s = '?' + s;
@ -155,8 +162,8 @@ str.toBinBytes = function(n, cb)
*
* You might be tempted to use the built-in n.toString(16) instead, but it doesn't zero-pad and it
* doesn't properly convert negative values; for example, if n is -2147483647, then n.toString(16)
* will return "-7fffffff" instead of "80000001". Moreover, if n is undefined, n.toString() will throw
* an exception, whereas toHex() will simply return '?' characters.
* will return "-7fffffff" instead of "80000001". Moreover, if n is undefined, n.toString() will
* throw an exception, whereas this function will return '?' characters.
*
* NOTE: The following work-around (adapted from code found on StackOverflow) would be another solution,
* taking care of negative values, zero-padding, and upper-casing, but not null/undefined/NaN values:
@ -180,6 +187,10 @@ str.toHex = function(n, cch)
/*
* 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.
*
* Alternatively, we could mask and shift n regardless of whether it's null/undefined/NaN,
* since JavaScript coerces such operands to zero, but I think there's "value" in seeing those
* values displayed differently.
*/
if (n == null || isNaN(n)) {
while (cch-- > 0) s = '?' + s;