Some improvements for FOOTBALL, including support for the 386 LOADALL instruction

This commit is contained in:
Jeff Parsons 2016-01-28 22:22:36 -08:00
commit a5b9e0cae7
27 changed files with 4431 additions and 3981 deletions

View file

@ -4793,6 +4793,14 @@ if (DEBUGGER) {
if (typeCPU == null) typeCPU = type >> Debugger.TYPE_CPU_SHIFT;
if (iIns == Debugger.INS.LOADALL) {
if (typeCPU == Debugger.CPU_80286) {
sOperands = "[%800]";
} else if (typeCPU == Debugger.CPU_80386) {
sOperands = "ES:[" + (dbgAddr.fAddr32? 'E':'') + "DI]";
}
}
var typeSize = type & Debugger.TYPE_SIZE;
if (typeSize == Debugger.TYPE_NONE) {
continue;

View file

@ -193,7 +193,7 @@ var X86 = {
SEL: {
RPL: 0x0003, // requested privilege level (0-3)
LDT: 0x0004, // table indicator (0: GDT, 1: LDT)
MASK: 0xFFF8 // table index
MASK: 0xFFF8 // table offset
},
DESC: { // Descriptor Table Entry
LIMIT: { // LIMIT bits 0-15 (or OFFSET if this is an INTERRUPT or TRAP gate)

View file

@ -1123,8 +1123,10 @@ X86CPU.prototype.resetRegs = function()
* More recently, opCS was added to selectively snapshot an instruction's original CS in case an
* exception occurs accessing the stack after a new CS has been loaded, allowing the exception handler
* to recover the old CS and make instructions like CALLF restartable; otherwise, opCS should remain -1.
*
* Ditto for opSS and the SS register.
*/
this.opCS = -1;
this.opCS = this.opSS = -1;
this.opLIP = this.opLSP = X86.ADDR_INVALID;
/*
@ -2131,7 +2133,13 @@ X86CPU.prototype.setLIP = function(addr)
{
this.regLIP = addr|0;
this.regLIPLimit = (this.segCS.base + this.segCS.limit)|0;
/*
* TODO: Verify the proper source for CPL. Should it come from segCS.cpl or segCS.dpl?
* Also, note that LOADALL386 wants it to come from segSS.dpl.
*/
this.nCPL = this.segCS.cpl; // cache the current CPL where it's more convenient
if (I386) this.resetSizes();
/*
* Here, we need to additionally test whether the prefetch buffer (adwPrefetch) has been allocated yet,
@ -2983,7 +2991,7 @@ X86CPU.prototype.setBinding = function(sHTMLType, sBinding, control)
* probeAddr(addr, size, fLinear)
*
* Used by the Debugger to probe addresses without risk of triggering a page fault, and by internal
* functions, like fnFaultMessage(), that must also avoid triggering faults, since they're not part of
* functions, like fnCheckFault(), that must also avoid triggering faults, since they're not part of
* standard CPU operation.
*
* Since originally written, I've also relaxed the requirement that the request be contained entirely
@ -3767,10 +3775,21 @@ X86CPU.prototype.popWord = function()
/**
* pushData(d, width, size)
*
* This function serves two very limited purposes: 1) the ability to push data according to a previous
* operand size (width), and 2) the ability to write fewer bytes than the width if necessary (size).
*
* The former occurs when a 32-bit code segment performs a 16:32 call to a 16-bit code segment; after the
* new 16-bit code segment is loaded (and possible stack switch occurs), the return address (both segment
* and offset) must still be pushed as 32-bit values.
*
* The latter occurs with segment register pushes. When a 32-bit operand size is in effect (ie, width is 4),
* only the low 16 bits should be written (size must be 2). For all other kinds of pushes, width and size are
* impliedly the same.
*
* @this {X86CPU}
* @param {number} d is the data to push at current SP; SP decreased by size
* @param {number} width is the width of the data to push, in bytes (must be either 2 or 4)
* @param {number} size is the size of the data to push, in bytes (must be > 0 and <= width)
* @param {number} size is the size of the data to push, in bytes (must be 1, 2, or 4, and <= width)
*/
X86CPU.prototype.pushData = function(d, width, size)
{
@ -3800,13 +3819,13 @@ X86CPU.prototype.pushData = function(d, width, size)
switch(size) {
case 1:
this.setByte(regLSP, d);
break
break;
case 2:
this.setShort(regLSP, d);
break
break;
case 4:
this.setLong(regLSP, d);
break
break;
default:
this.assert(false);
break;

View file

@ -554,11 +554,12 @@ X86.fnCALLw = function(dst, src)
X86.fnCALLF = function(off, sel)
{
/*
* Since we always push the return address AFTER calling setCSIP(), and since either push could trigger
* Since we always push the return address AFTER calling setCSIP(), and since either push could trigger a
* fault (eg, segment fault, page fault, etc), we must not only snapshot regLSP into opLSP, but also the
* current CS into opCS, so that fnFault() can always make CALLF restartable.
* current CS into opCS, so that fnFault() can always make CALLF restartable. Ditto for opSS and the SS register.
*/
this.opCS = this.getCS();
this.opSS = this.getSS();
this.opLSP = this.regLSP;
var oldIP = this.getIP();
var oldSize = (I386? this.sizeData : 2);
@ -572,7 +573,7 @@ X86.fnCALLF = function(off, sel)
this.pushData(oldIP, oldSize, oldSize);
}
this.opLSP = X86.ADDR_INVALID;
this.opCS = -1;
this.opCS = this.opSS = -1;
};
/**
@ -3942,8 +3943,7 @@ X86.fnFault = function(nFault, nError, nCycles, fHalt)
* Prior to each new burst of instructions, stepCPU() sets fComplete to true, and the only (normal) way
* for fComplete to become false is through stopCPU(), which isn't ordinarily called, except by the Debugger.
*/
this.resetSizes();
this.setIP(this.opLIP - this.segCS.base);
this.setLIP(this.opLIP);
}
else if (this.model >= X86.MODEL_80186) {
@ -3951,16 +3951,24 @@ X86.fnFault = function(nFault, nError, nCycles, fHalt)
if (this.nFault < 0) {
/*
* Single-fault (error code is passed through, and the responsible instruction is restartable;
* the call to resetSizes() is critical, otherwise setIP() may update IP with the wrong size if
* the current instruction contains an OPERAND size override).
* Single-fault (error code is passed through, and the responsible instruction is restartable.
*/
this.resetSizes();
if (this.opCS != -1) {
/*
* HACK: We must slam 3 into this.segCS.cpl to ensure that loading the original CS segment doesn't
* fail. For example, if we faulted in the middle of a ring transition that loaded CS with a higher
* privilege (lower CPL) code segment, then our attempt here to reload the lower privilege (higher CPL)
* code segment could be viewed as a privilege violation (which it would be outside this context).
*/
this.segCS.cpl = 3;
this.setCS(this.opCS);
this.opCS = -1;
}
this.setIP(this.opLIP - this.segCS.base);
this.setLIP(this.opLIP);
if (this.opSS != -1) {
this.setSS(this.opSS);
this.opSS = -1;
}
if (this.opLSP !== X86.ADDR_INVALID) {
this.setSP((this.regESP & ~this.segSS.maskAddr) | (this.opLSP - this.segSS.base));
this.opLSP = X86.ADDR_INVALID;
@ -3970,22 +3978,24 @@ X86.fnFault = function(nFault, nError, nCycles, fHalt)
/*
* Double-fault (error code is always zero, and the responsible instruction is not restartable)
*/
nError = 0; nFault = X86.EXCEPTION.DF_FAULT;
nError = 0;
nFault = X86.EXCEPTION.DF_FAULT;
}
else {
/*
* Triple-fault (usually referred to in Intel literature as a "shutdown", but at least on the 80286,
* it's actually a "reset")
*/
nFault = -1; nError = 0;
nError = 0;
nFault = -1;
this.resetRegs();
fDispatch = fHalt = false;
}
}
if (X86.fnFaultMessage.call(this, nFault, nError, fHalt)) {
if (X86.fnCheckFault.call(this, nFault, nError, fHalt)) {
/*
* If this is a fault that would normally be dispatched BUT fnFaultMessage() wants us to halt,
* If this is a fault that would normally be dispatched BUT fnCheckFault() wants us to halt,
* then we throw a bogus fault number (-1), simply to interrupt the current instruction in exactly
* the same way that a dispatched fault would interrupt it.
*/
@ -4060,7 +4070,7 @@ X86.fnPageFault = function(addr, fPresent, fWrite)
};
/**
* fnFaultMessage(nFault, nError, fHalt)
* fnCheckFault(nFault, nError, fHalt)
*
* 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.
@ -4076,7 +4086,7 @@ X86.fnPageFault = function(addr, fPresent, fWrite)
* @param {boolean} [fHalt] (true to halt the CPU, false to not, undefined if "it depends")
* @return {boolean|undefined} true to block the fault (often desirable when fHalt is true), otherwise dispatch it
*/
X86.fnFaultMessage = function(nFault, nError, fHalt)
X86.fnCheckFault = function(nFault, nError, fHalt)
{
var bitsMessage = Messages.FAULT;

View file

@ -161,6 +161,11 @@ X86.opLOADALL286 = function()
this.segCS.loadDesc6(0x83C, this.getShort(0x822));
this.segSS.loadDesc6(0x842, this.getShort(0x820));
this.segDS.loadDesc6(0x848, this.getShort(0x81E));
/*
* Unlike LOADALL386, there's no requirement for calling setPS() before loading segment registers;
* in fact, since we're not passing a CPL to setPS(), it may be preferable to have CS (and perhaps SS)
* already loaded, so that setPS() can query the CPL. TODO: Verify that CPL is set correctly.
*/
this.setPS(this.getShort(0x818));
/*
* It's important to call setIP() and setSP() *after* the segCS and segSS loads, so that the CPU's
@ -178,15 +183,15 @@ X86.opLOADALL286 = function()
*/
this.addrGDT = this.getShort(0x84E) | (this.getByte(0x850) << 16);
this.addrGDTLimit = this.addrGDT + this.getShort(0x852);
this.segLDT.loadDesc6(0x854, this.getShort(0x81C));
this.addrIDT = this.getShort(0x85A) | (this.getByte(0x85C) << 16);
this.addrIDTLimit = this.addrIDT + this.getShort(0x85E);
this.segLDT.loadDesc6(0x854, this.getShort(0x81C));
this.segTSS.loadDesc6(0x860, this.getShort(0x816));
/*
* Oddly, the above Intel document gives two contradictory cycle counts for LOADALL: 190 and 195. I go with 195,
* since both the PC Magazine Programmer's Technical Reference and Robert Collins (http://www.rcollins.org/articles/loadall/tspec_a3_doc.html)
* agree.
* Oddly, the above Intel document gives two contradictory cycle counts for LOADALL: 190 and 195.
* I'm going with 195, since both the PC Magazine Programmer's Technical Reference and Robert Collins
* (http://www.rcollins.org/articles/loadall/tspec_a3_doc.html) agree.
*/
this.nStepCycles -= 195;
@ -221,7 +226,8 @@ X86.opCLTS = function()
*
* op=0x0F,0x07 (LOADALL ES:[EDI])
*
* Excerpt from Intel Internal Correspondence on "386 LOADALL Instruction" (undated):
* Excerpt from Intel Internal Correspondence on "386 LOADALL Instruction" (undated), available as part of the
* PCjs Project at http://www.pcjs.org/pubs/pc/reference/intel/80386/loadall/
*
* 1.5. 386 LOADALL Memory Format
*
@ -234,6 +240,7 @@ X86.opCLTS = function()
* be DWORD aligned.
*
* Offset Register
* ------ --------
* 0x00 CR0
* 0x04 EFLAGS
* 0x08 EIP
@ -247,44 +254,44 @@ X86.opCLTS = function()
* 0x28 EAX
* 0x2C DR6
* 0x30 DR7
* 0x34 TR (TSS Selector--Word)
* 0x38 LDTR (LDT Selector--Word)
* 0x34 TSSR(TSSSelector-Word)
* 0x38 LDTR(LDTSelector-Word)
* 0x3C GS
* 0x40 FS
* 0x44 DS
* 0x48 SS
* 0x4C CS
* 0x50 ES
* 0x54 TSS (AR)
* 0x58 TSS (BASE)
* 0x5C TSS (LIMIT)
* 0x60 IDT (AR)
* 0x64 IDT (BASE)
* 0x68 IDT (LIMIT)
* 0x6C GDT (AR)
* 0x70 GDT (BASE)
* 0x74 GDT (LIMIT)
* 0x78 LDT (AR)
* 0x7C LDT (BASE)
* 0x80 LDT (LIMIT)
* 0x84 GS (AR)
* 0x88 GS (BASE)
* 0x8C GS (LIMIT)
* 0x90 FS (AR)
* 0x94 FS (BASE)
* 0x98 FS (LIMIT)
* 0x9C DS (AR)
* 0xA0 DS (BASE)
* 0xA4 DS (LIMIT)
* 0xA8 SS (AR)
* 0xAC SS (BASE)
* 0xB0 SS (LIMIT)
* 0xB4 CS (AR)
* 0xB8 CS (BASE)
* 0xBC CS (LIMIT)
* 0xC0 ES (AR)
* 0xC4 ES (BASE)
* 0xC8 ES (LIMIT)
* 0x54 TSS(AR)
* 0x58 TSS(BASE)
* 0x5C TSS(LIMIT)
* 0x60 IDT(AR)
* 0x64 IDT(BASE)
* 0x68 IDT(LIMIT)
* 0x6C GDT(AR)
* 0x70 GDT(BASE)
* 0x74 GDT(LIMIT)
* 0x78 LDT(AR)
* 0x7C LDT(BASE)
* 0x80 LDT(LIMIT)
* 0x84 GS(AR)
* 0x88 GS(BASE)
* 0x8C GS(LIMIT)
* 0x90 FS(AR)
* 0x94 FS(BASE)
* 0x98 FS(LIMIT)
* 0x9C DS(AR)
* 0xA0 DS(BASE)
* 0xA4 DS(LIMIT)
* 0xA8 SS(AR)
* 0xAC SS(BASE)
* 0xB0 SS(LIMIT)
* 0xB4 CS(AR)
* 0xB8 CS(BASE)
* 0xBC CS(LIMIT)
* 0xC0 ES(AR)
* 0xC4 ES(BASE)
* 0xC8 ES(LIMIT)
*
* Each descriptor entry consists of 3 pieces:
*
@ -292,10 +299,11 @@ X86.opCLTS = function()
* BASE
* LIMIT
*
* The AR part has the same format as the second dword of a segment descriptor except that only the AR byte (bits 8-15)
* and the G and B/D bits (bits 23 and 22) are used. All other bits in the AR field are ignored. The BASE and LIMIT parts
* contain full 32-bit values, fully expanded and unscrambled from the 386 descriptor. In particular, the LIMIT field
* loaded for a page granular segment gives a byte granular limit, so should contain the page limit*4096 plus 4095.
* The AR part has the same format as the second dword of a segment descriptor except that only the AR byte
* (bits 8-15) and the G and B/D bits (bits 23 and 22) are used. All other bits in the AR field are ignored.
* The BASE and LIMIT parts contain full 32-bit values, fully expanded and unscrambled from the 386 descriptor.
* In particular, the LIMIT field loaded for a page granular segment gives a byte granular limit, so should
* contain the page limit*4096 plus 4095.
*
* @this {X86CPU}
*/
@ -308,16 +316,59 @@ X86.opLOADALL386 = function()
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0, 0, true);
return;
}
/*
* TODO: Implement
*/
X86.opUndefined.call(this);
var addr = this.segES.checkRead(this.regEDI & this.maskAddr, 0xCC);
if (addr !== X86.ADDR_INVALID) {
X86.fnLCR0.call(this, this.getLong(addr));
/*
* We need to call setPS() before loading any segment registers, because if the Virtual 8086 Mode (VM)
* bit is set in EFLAGS, the segment registers need to know that.
*/
var accSS = this.getLong(addr + 0xA8);
var cpl = (accSS & X86.DESC.ACC.DPL.MASK) >> X86.DESC.ACC.DPL.SHIFT;
this.setPS(this.getLong(addr + 0x04), cpl);
/*
* TODO: We have no use for the GDT(AR) at offset 0x6C or the IDT(AR) at offset 0x60, because
* we don't manage them as segment registers. Should we?
*/
this.addrGDT = this.getLong(addr + 0x70);
this.addrGDTLimit = this.addrGDT + this.getLong(addr + 0x74);
this.addrIDT = this.getLong(addr + 0x64);
this.addrIDTLimit = this.addrIDT + this.getLong(addr + 0x68);
this.segLDT.loadDesc(this.getLong(addr + 0x38), this.getLong(addr + 0x78), this.getLong(addr + 0x7C), this.getLong(addr + 0x80));
this.segTSS.loadDesc(this.getLong(addr + 0x34), this.getLong(addr + 0x54), this.getLong(addr + 0x58), this.getLong(addr + 0x5C));
this.regEDI = this.getLong(addr + 0x0C);
this.regESI = this.getLong(addr + 0x10);
this.regEBP = this.getLong(addr + 0x14);
this.regEBX = this.getLong(addr + 0x1C);
this.regEDX = this.getLong(addr + 0x20);
this.regECX = this.getLong(addr + 0x24);
this.regEAX = this.getLong(addr + 0x28);
this.segGS.loadDesc(this.getLong(addr + 0x3C), this.getLong(addr + 0x84), this.getLong(addr + 0x88), this.getLong(addr + 0x8C));
this.segFS.loadDesc(this.getLong(addr + 0x40), this.getLong(addr + 0x90), this.getLong(addr + 0x94), this.getLong(addr + 0x98));
this.segDS.loadDesc(this.getLong(addr + 0x44), this.getLong(addr + 0x9C), this.getLong(addr + 0xA0), this.getLong(addr + 0xA4));
this.segSS.loadDesc(this.getLong(addr + 0x48), accSS, this.getLong(addr + 0xAC), this.getLong(addr + 0xB0));
this.segCS.loadDesc(this.getLong(addr + 0x4C), this.getLong(addr + 0xB4), this.getLong(addr + 0xB8), this.getLong(addr + 0xBC));
this.segES.loadDesc(this.getLong(addr + 0x50), this.getLong(addr + 0xC0), this.getLong(addr + 0xC4), this.getLong(addr + 0xC8));
/*
* It's important to call setIP() and setSP() *after* the segCS and segSS loads, so that the CPU's
* linear IP and SP registers (regLIP and regLSP) will be updated properly. Ordinarily that would be
* taken care of by simply using the CPU's setCS() and setSS() functions, but those functions call the
* default descriptor load() functions, and obviously here we must use loadDesc() instead.
*/
this.setIP(this.getLong(addr + 0x08));
this.setSP(this.getLong(addr + 0x18));
/*
* TODO: We need to factor out the code that updates DR6 and DR7 from X86.opMOVdr(), so that we can
* more easily update DR6 and DR7 (which we're simply ignoring for now).
*/
}
/*
* According to Robert Collins (http://www.rcollins.org/articles/loadall/tspec_a3_doc.html), the 80386 LOADALL
* takes 122 cycles.
* takes 122 cycles. Also, according the above-mentioned Intel document, if the memory buffer is not DWORD aligned,
* execution time will DOUBLE.
*/
this.nStepCycles -= 122;
this.nStepCycles -= (122 << ((addr & 0x3)? 1 : 0));
};
/**
@ -547,10 +598,18 @@ X86.opMOVrt = function()
var bModRM = this.getIPByte();
var iSrc = (bModRM & 0x38) >> 3;
/*
* Only TR6 and TR7 are defined, and only for the 80386 and 80486. From the PC Magazine Prog. TechRef, p.64:
*
* "The 80386 provides two 32-bit test registers, TR6 and TR7, as a mechanism for programmers to verify proper
* operation of the Translation Lookaside Buffer (TLB) when power is applied to the chip. The TLB is a cache used
* internally by the 80386 to translate linear addresses to physical addresses."
*/
if (iSrc < 6) {
X86.opUndefined.call(this);
return;
}
this.setReg(bModRM & 0x7, this.regTR[iSrc]);
this.nStepCycles -= 12;
@ -585,6 +644,13 @@ X86.opMOVtr = function()
var bModRM = this.getIPByte();
var iDst = (bModRM & 0x38) >> 3;
/*
* Only TR6 and TR7 are defined, and only for the 80386 and 80486. From the PC Magazine Prog. TechRef, p.64:
*
* "The 80386 provides two 32-bit test registers, TR6 and TR7, as a mechanism for programmers to verify proper
* operation of the Translation Lookaside Buffer (TLB) when power is applied to the chip. The TLB is a cache used
* internally by the 80386 to translate linear addresses to physical addresses."
*/
if (iDst < 6) {
X86.opUndefined.call(this);
return;
@ -1679,7 +1745,7 @@ X86.aOps0F[0xFF] = X86.opInvalid;
if (I386) {
X86.aOps0F386 = [];
X86.aOps0F386[0x05] = X86.opInvalid; // the 80286 LOADALL opcode is invalid on the 80386
X86.aOps0F386[0x05] = X86.opInvalid; // the 80286 LOADALL opcode (LOADALL286) is invalid on the 80386
X86.aOps0F386[0x07] = X86.opLOADALL386;
X86.aOps0F386[0x20] = X86.opMOVrc;
X86.aOps0F386[0x21] = X86.opMOVrd;

View file

@ -4457,7 +4457,7 @@ X86.opInvalid = function()
X86.opUndefined = function()
{
this.setIP(this.opLIP - this.segCS.base);
this.setError("Undefined opcode " + str.toHexByte(this.bus.getByteDirect(this.regLIP)) + " at " + str.toHexLong(this.regLIP));
this.setError("Undefined opcode " + str.toHexByte(this.getByte(this.regLIP)) + " at " + str.toHexLong(this.regLIP));
this.stopCPU();
};

View file

@ -551,10 +551,44 @@ X86Seg.prototype.loadAcc = function(sel, fGDT)
};
*/
/**
* loadDesc(sel, acc, base, limit)
*
* Used to manually load a segment register from the data provided (see LOADALL386).
*
* @this {X86Seg}
* @param {number} sel
* @param {number} acc
* @param {number} base
* @param {number} limit
*/
X86Seg.prototype.loadDesc = function(sel, acc, base, limit)
{
this.sel = sel;
this.base = base;
this.limit = limit;
this.offMax = (limit >>> 0) + 1;
this.acc = acc;
this.type = (acc & X86.DESC.ACC.TYPE.MASK);
this.ext = (acc >> 16) & (X86.DESC.EXT.BIG | X86.DESC.EXT.LIMITPAGES);
var addrDT = (sel & X86.SEL.LDT)? this.cpu.segLDT.base : this.cpu.addrGDT;
this.addrDesc = (addrDT + (sel & X86.SEL.MASK))|0;
/*
* NOTE: This code must take care to leave the mode of the TSS, LDT, and VER segment registers alone;
* in particular, we must not allow a real-mode LOADALL to modify their mode, because the rest of PCjs
* assumes that their mode will never change (they were allocated with fProt set to true).
*/
if (this.id < X86Seg.ID.TSS) this.updateMode(true);
if (DEBUG) this.messageSeg(sel, base, limit, this.type);
};
/**
* loadDesc6(addrDesc, sel)
*
* Used to load a protected-mode selector that refers to a 6-byte "descriptor cache" (aka LOADALL) entry:
* Used to load a protected-mode selector that refers to a 6-byte "descriptor cache" entry (see LOADALL286):
*
* word 0: base address low
* word 1: base address high (0-7), segment type (8-11), descriptor type (12), DPL (13-14), present bit (15)
@ -584,8 +618,7 @@ X86Seg.prototype.loadDesc6 = function(addrDesc, sel)
/*
* NOTE: This code must take care to leave the mode of the TSS, LDT, and VER segment registers alone;
* in particular, we must not allow a real-mode LOADALL to modify their mode, because the rest of PCjs
* assumes that their mode will never change (they were allocated with fProt set to true), so there's
* no code to force them back into protected-mode.
* assumes that their mode will never change (they were allocated with fProt set to true).
*/
if (this.id < X86Seg.ID.TSS) this.updateMode(true);
@ -706,44 +739,48 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe)
var rpl = sel & X86.SEL.RPL;
var dpl = (acc & X86.DESC.ACC.DPL.MASK) >> X86.DESC.ACC.DPL.SHIFT;
var sizeGate, selCode, cplOld, cplNew, fIDT;
var sizeGate = -1, selCode, cplOld, cplNew, fIDT;
var addrTSS, offSP, lenSP, regSPPrev, regSSPrev, regPSClear, regSP;
/*
* TODO: As discussed below for X86Seg.ID.DATA, it's likely that testing the PRESENT bit should
* be performed *after* checking the other, more serious potential problems.
*/
if (selMasked && !(acc & X86.DESC.ACC.PRESENT)) {
if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.NP_FAULT, sel & X86.ERRCODE.SELMASK);
return X86.ADDR_INVALID;
if (!selMasked) {
/*
* selMasked is really the descriptor table offset, and a zero offset is fine for the IDT;
* it MAY even be OK for the LDT. But it's definitely not OK for the GDT; a null selector
* is allowed in any of DS, ES, SS, FS, or GS, but never CS). Since there's no parameter
* that tells us which table we're using, we have to check manually.
*
* If we ARE attempting to load a null selector from the GDT, then we zero type, which ensures
* that sizeGate will remain invalid, triggering a GP_FAULT below.
*/
if (addrDesc >= cpu.addrGDT && addrDesc < cpu.addrGDTLimit) type = 0;
}
/*
* Since we are X86Seg.ID.CODE, we can use this.cpl instead of the more generic cpu.segCS.cpl
*/
if (type >= X86.DESC.ACC.TYPE.CODE_EXECONLY) {
sizeGate = 0;
if (rpl > this.cpl) {
/*
/*.
* If fCall is false, then we must have a RETF to a less privileged segment, which is OK.
*
* Otherwise, we must be dealing with a CALLF or JMPF to a less privileged segment, in which
* case either DPL == CPL *or* the new segment is conforming and DPL <= CPL.
*/
if (fCall !== false && !(dpl == this.cpl || (type & X86.DESC.ACC.TYPE.CONFORMING) && dpl <= this.cpl)) {
return X86.ADDR_INVALID;
sizeGate = -1;
if (fCall === false || dpl == this.cpl || (type & X86.DESC.ACC.TYPE.CONFORMING) && dpl <= this.cpl) {
/*
* It's critical that any stack switch occur with the operand size in effect at the time of
* the current instruction, BEFORE any calls to updateMode() and resetSizes(), otherwise the
* operand size (or operand override) in effect on an instruction like IRETD will be ignored.
*/
regSP = cpu.popWord();
cpu.setSS(cpu.popWord(), true);
cpu.setSP(regSP);
this.fStackSwitch = true;
sizeGate = 0;
}
/*
* It's critical that any stack switch occur with the operand size in effect at the time of
* the current instruction, BEFORE any calls to updateMode() and resetSizes(), otherwise the
* operand size (or operand override) in effect on an instruction like IRETD will be ignored.
*/
regSP = cpu.popWord();
cpu.setSS(cpu.popWord(), true);
cpu.setSP(regSP);
this.fStackSwitch = true;
}
sizeGate = 0;
}
else if (type == X86.DESC.ACC.TYPE.TSS286 || type == X86.DESC.ACC.TYPE.TSS386) {
if (!this.switchTSS(sel, fCall)) {
@ -788,7 +825,9 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe)
return this.base;
}
if (sizeGate) {
if (sizeGate > 0 && !(acc & X86.DESC.ACC.PRESENT)) sizeGate = 0;
if (sizeGate > 0) {
/*
* Note that since GATE_INT/GATE_TRAP descriptors should appear in the IDT only, that means sel
* will actually be nIDT * 8, which means the rpl will always be zero; additionally, the nWords
@ -843,7 +882,7 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe)
offSP = (cplNew << 2) + X86.TSS286.CPL0_SP;
lenSP = 2;
} else {
offSP = (cplNew << 2) + X86.TSS386.CPL0_ESP;
offSP = (cplNew << 3) + X86.TSS386.CPL0_ESP;
lenSP = 4;
}
selStack = cpu.getShort(addrTSS + offSP + lenSP);
@ -952,9 +991,13 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe)
}
}
if (sizeGate !== 0) {
var nError = (sel & X86.ERRCODE.SELMASK) | (fIDT? X86.ERRCODE.IDT : 0);
X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, nError);
if (sizeGate != 0) {
X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, (sel & X86.ERRCODE.SELMASK) | (fIDT? X86.ERRCODE.IDT : 0));
return X86.ADDR_INVALID;
}
if (!(acc & X86.DESC.ACC.PRESENT)) {
X86.fnFault.call(cpu, X86.EXCEPTION.NP_FAULT, (sel & X86.ERRCODE.SELMASK) | (fIDT? X86.ERRCODE.IDT : 0));
return X86.ADDR_INVALID;
}
break;
@ -1011,14 +1054,14 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe)
break;
case X86Seg.ID.STACK:
if (!(acc & X86.DESC.ACC.PRESENT)) {
X86.fnFault.call(cpu, X86.EXCEPTION.SS_FAULT, sel & X86.ERRCODE.SELMASK);
return X86.ADDR_INVALID;
}
if (!selMasked || type < X86.DESC.ACC.TYPE.SEG || (type & (X86.DESC.ACC.TYPE.CODE | X86.DESC.ACC.TYPE.WRITABLE)) != X86.DESC.ACC.TYPE.WRITABLE) {
X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel & X86.ERRCODE.SELMASK);
return X86.ADDR_INVALID;
}
if (!(acc & X86.DESC.ACC.PRESENT)) {
X86.fnFault.call(cpu, X86.EXCEPTION.SS_FAULT, sel & X86.ERRCODE.SELMASK);
return X86.ADDR_INVALID;
}
break;
case X86Seg.ID.TSS:
@ -1270,7 +1313,7 @@ X86Seg.prototype.switchTSS = function switchTSS(selNew, fNest)
offSS = X86.TSS386.TASK_SS;
offSP = X86.TSS386.TASK_ESP;
if (this.cpl < cplOld) {
offSP = (this.cpl << 2) + X86.TSS386.CPL0_ESP;
offSP = (this.cpl << 3) + X86.TSS386.CPL0_ESP;
offSS = offSP + 4;
}
cpu.setSS(cpu.getShort(addrNew + offSS), true);