v1.19.5 checkpoint (includes a fix to the "pop mem" instruction)
This commit is contained in:
parent
8c3384a115
commit
09295a1a27
147 changed files with 2536 additions and 2350 deletions
|
|
@ -729,8 +729,24 @@ Bus.prototype.getLong = function(addr)
|
|||
if (off < this.nBlockLimit - 2) {
|
||||
return this.aMemBlocks[iBlock].readLong(off, addr);
|
||||
}
|
||||
var nShift = (off & 0x3) << 3;
|
||||
return (this.aMemBlocks[iBlock].readLong(off & ~0x3, addr) >>> nShift) | (this.aMemBlocks[(iBlock + 1) & this.nBlockMask].readLong(0, addr + 3) << (32 - nShift));
|
||||
/*
|
||||
* I think the previous version of this function tried to be too clever (ie, reading the last
|
||||
* long in the current block and the first long in the next block and masking/combining the results),
|
||||
* which may have also created some undesirable side-effects for custom memory controllers.
|
||||
* This simpler (and probably more reliable) approach is to simply read the long as individual bytes.
|
||||
*/
|
||||
var l = 0;
|
||||
var cb = 4, nShift = 0;
|
||||
var cbBlock = 4 - (off & 0x3); // (off & 0x3) will be 1, 2 or 3, so cbBlock will be 3, 2, or 1
|
||||
while (cb--) {
|
||||
l |= (this.aMemBlocks[iBlock].readByte(off++, addr++) << nShift);
|
||||
if (!--cbBlock) {
|
||||
iBlock = (iBlock + 1) & this.nBlockMask;
|
||||
off = 0;
|
||||
}
|
||||
nShift += 8;
|
||||
}
|
||||
return l;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -749,8 +765,24 @@ Bus.prototype.getLongDirect = function(addr)
|
|||
if (off < this.nBlockLimit - 2) {
|
||||
return this.aMemBlocks[iBlock].readLongDirect(off, addr);
|
||||
}
|
||||
var nShift = (off & 0x3) << 3;
|
||||
return (this.aMemBlocks[iBlock].readLongDirect(off & ~0x3, addr) >>> nShift) | (this.aMemBlocks[(iBlock + 1) & this.nBlockMask].readLongDirect(0, addr + 3) << (32 - nShift));
|
||||
/*
|
||||
* I think the previous version of this function tried to be too clever (ie, reading the last
|
||||
* long in the current block and the first long in the next block and masking/combining the results),
|
||||
* which may have also created some undesirable side-effects for custom memory controllers.
|
||||
* This simpler (and probably more reliable) approach is to simply read the long as individual bytes.
|
||||
*/
|
||||
var l = 0;
|
||||
var cb = 4, nShift = 0;
|
||||
var cbBlock = 4 - (off & 0x3); // (off & 0x3) will be 1, 2 or 3, so cbBlock will be 3, 2, or 1
|
||||
while (cb--) {
|
||||
l |= (this.aMemBlocks[iBlock].readByteDirect(off++, addr++) << nShift);
|
||||
if (!--cbBlock) {
|
||||
iBlock = (iBlock + 1) & this.nBlockMask;
|
||||
off = 0;
|
||||
}
|
||||
nShift += 8;
|
||||
}
|
||||
return l;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -842,14 +874,22 @@ Bus.prototype.setLong = function(addr, l)
|
|||
this.aMemBlocks[iBlock].writeLong(off, l);
|
||||
return;
|
||||
}
|
||||
var lPrev, nShift = (off & 0x3) << 3;
|
||||
off &= ~0x3;
|
||||
lPrev = this.aMemBlocks[iBlock].readLong(off, addr);
|
||||
this.aMemBlocks[iBlock].writeLong(off, (lPrev & ~((0xffffffff|0) << nShift)) | (l << nShift), addr);
|
||||
iBlock = (iBlock + 1) & this.nBlockMask;
|
||||
addr += 3;
|
||||
lPrev = this.aMemBlocks[iBlock].readLong(0, addr);
|
||||
this.aMemBlocks[iBlock].writeLong(0, (lPrev & ((0xffffffff|0) << nShift)) | (l >>> (32 - nShift)), addr);
|
||||
/*
|
||||
* I think the previous version of this function tried to be too clever (ie, reading and rewriting
|
||||
* the last long in the current block, and then reading and rewriting the first long in the next
|
||||
* block), which may have also created some undesirable side-effects for custom memory controllers.
|
||||
* This simpler (and probably more reliable) approach is to simply write the long as individual bytes.
|
||||
*/
|
||||
var cb = 4;
|
||||
var cbBlock = 4 - (off & 0x3); // (off & 0x3) will be 1, 2 or 3, so cbBlock will be 3, 2, or 1
|
||||
while (cb--) {
|
||||
this.aMemBlocks[iBlock].writeByte(off++, l & 0xff, addr++);
|
||||
if (!--cbBlock) {
|
||||
iBlock = (iBlock + 1) & this.nBlockMask;
|
||||
off = 0;
|
||||
}
|
||||
l >>>= 8;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -870,14 +910,22 @@ Bus.prototype.setLongDirect = function(addr, l)
|
|||
this.aMemBlocks[iBlock].writeLongDirect(off, l, addr);
|
||||
return;
|
||||
}
|
||||
var lPrev, nShift = (off & 0x3) << 3;
|
||||
off &= ~0x3;
|
||||
lPrev = this.aMemBlocks[iBlock].readLongDirect(off, addr);
|
||||
this.aMemBlocks[iBlock].writeLongDirect(off, (lPrev & ~((0xffffffff|0) << nShift)) | (l << nShift), addr);
|
||||
iBlock = (iBlock + 1) & this.nBlockMask;
|
||||
addr += 3;
|
||||
lPrev = this.aMemBlocks[iBlock].readLongDirect(0, addr);
|
||||
this.aMemBlocks[iBlock].writeLongDirect(0, (lPrev & ((0xffffffff|0) << nShift)) | (l >>> (32 - nShift)), addr);
|
||||
/*
|
||||
* I think the previous version of this function tried to be too clever (ie, reading and rewriting
|
||||
* the last long in the current block, and then reading and rewriting the first long in the next
|
||||
* block), which may have also created some undesirable side-effects for custom memory controllers.
|
||||
* This simpler (and probably more reliable) approach is to simply write the long as individual bytes.
|
||||
*/
|
||||
var cb = 4;
|
||||
var cbBlock = 4 - (off & 0x3); // (off & 0x3) will be 1, 2 or 3, so cbBlock will be 3, 2, or 1
|
||||
while (cb--) {
|
||||
this.aMemBlocks[iBlock].writeByteDirect(off++, l & 0xff, addr++);
|
||||
if (!--cbBlock) {
|
||||
iBlock = (iBlock + 1) & this.nBlockMask;
|
||||
off = 0;
|
||||
}
|
||||
l >>>= 8;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1267,11 +1267,11 @@ if (DEBUGGER) {
|
|||
}
|
||||
}
|
||||
|
||||
this.messageDump(Messages.BUS, function onDumpBus(asArgs) { dbg.dumpBus(asArgs); });
|
||||
this.messageDump(Messages.MEM, function onDumpMem(asArgs) { dbg.dumpMem(asArgs); });
|
||||
this.messageDump(Messages.DESC, function onDumpDesc(asArgs) { dbg.dumpDesc(asArgs); });
|
||||
this.messageDump(Messages.TSS, function onDumpTSS(asArgs) { dbg.dumpTSS(asArgs); });
|
||||
this.messageDump(Messages.DOS, function onDumpDOS(asArgs) { dbg.dumpDOS(asArgs); });
|
||||
this.messageDump(Messages.BUS, function onDumpBus(asArgs) { dbg.dumpBus(asArgs); });
|
||||
this.messageDump(Messages.MEM, function onDumpMem(asArgs) { dbg.dumpMem(asArgs); });
|
||||
this.messageDump(Messages.DESC, function onDumpSel(asArgs) { dbg.dumpSel(asArgs); });
|
||||
this.messageDump(Messages.TSS, function onDumpTSS(asArgs) { dbg.dumpTSS(asArgs); });
|
||||
this.messageDump(Messages.DOS, function onDumpDOS(asArgs) { dbg.dumpDOS(asArgs); });
|
||||
|
||||
if (Interrupts.WINDBG.ENABLED || Interrupts.WINDBGRM.ENABLED) {
|
||||
this.fWinDbg = null;
|
||||
|
|
@ -2066,7 +2066,16 @@ if (DEBUGGER) {
|
|||
var seg = this.getSegment(dbgAddr.sel, dbgAddr.type);
|
||||
if (seg) {
|
||||
var off = dbgAddr.off & seg.maskAddr;
|
||||
if ((off >>> 0) >= seg.offMax) return false;
|
||||
if (!seg.fExpDown) {
|
||||
if ((off >>> 0) >= seg.offMax) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((off >>> 0) < seg.offMax) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (fUpdate) {
|
||||
dbgAddr.off = off;
|
||||
dbgAddr.fData32 = (seg.sizeData == 4);
|
||||
|
|
@ -2301,13 +2310,14 @@ if (DEBUGGER) {
|
|||
};
|
||||
|
||||
/**
|
||||
* dumpBlocks(aBlocks, sAddr)
|
||||
* dumpBlocks(aBlocks, sAddr, fLinear)
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {Array} aBlocks
|
||||
* @param {string} [sAddr] (optional block address)
|
||||
* @param {boolean} [fLinear] (true if linear, physical otherwise)
|
||||
*/
|
||||
Debugger.prototype.dumpBlocks = function(aBlocks, sAddr)
|
||||
Debugger.prototype.dumpBlocks = function(aBlocks, sAddr, fLinear)
|
||||
{
|
||||
var i = 0, n = aBlocks.length;
|
||||
|
||||
|
|
@ -2321,13 +2331,27 @@ if (DEBUGGER) {
|
|||
n = 1;
|
||||
}
|
||||
|
||||
this.println("id physaddr blkaddr used size type");
|
||||
this.println("blkid " + (fLinear? "linear " : "physical") + " blkaddr used size type");
|
||||
this.println("-------- --------- -------- ------ ------ ----");
|
||||
|
||||
var typePrev = -1, cPrev = 0;
|
||||
while (n--) {
|
||||
var block = aBlocks[i];
|
||||
if (block.type !== Memory.TYPE.NONE) {
|
||||
this.println(str.toHex(block.id) + " %" + str.toHex(i << this.cpu.nBlockShift) + ": " + str.toHex(block.addr) + " " + str.toHexWord(block.used) + " " + str.toHexWord(block.size) + " " + Memory.TYPE.NAMES[block.type]);
|
||||
if (block.type == typePrev) {
|
||||
if (!cPrev++) this.println("...");
|
||||
} else {
|
||||
typePrev = block.type;
|
||||
var sType = Memory.TYPE.NAMES[typePrev];
|
||||
if (typePrev == Memory.TYPE.PAGED) {
|
||||
block = block.blockPhys;
|
||||
this.assert(block);
|
||||
sType += " -> " + Memory.TYPE.NAMES[block.type];
|
||||
}
|
||||
if (block) {
|
||||
this.println(str.toHex(block.id) + " %" + str.toHex(i << this.cpu.nBlockShift) + ": " + str.toHex(block.addr) + " " + str.toHexWord(block.used) + " " + str.toHexWord(block.size) + " " + sType);
|
||||
}
|
||||
if (typePrev != Memory.TYPE.NONE && typePrev != Memory.TYPE.UNPAGED) typePrev = -1;
|
||||
cPrev = 0;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
|
@ -2346,6 +2370,19 @@ if (DEBUGGER) {
|
|||
this.dumpBlocks(this.cpu.aBusBlocks, asArgs[0]);
|
||||
};
|
||||
|
||||
/**
|
||||
* dumpMem(asArgs)
|
||||
*
|
||||
* Dumps page allocations.
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {Array.<string>} asArgs (asArgs[0] is an optional block address)
|
||||
*/
|
||||
Debugger.prototype.dumpMem = function(asArgs)
|
||||
{
|
||||
this.dumpBlocks(this.cpu.aMemBlocks, asArgs[0], this.cpu.aMemBlocks !== this.cpu.aBusBlocks);
|
||||
};
|
||||
|
||||
/**
|
||||
* dumpInfo(asArgs)
|
||||
*
|
||||
|
|
@ -2380,24 +2417,6 @@ if (DEBUGGER) {
|
|||
return sInfo;
|
||||
};
|
||||
|
||||
/**
|
||||
* dumpMem(asArgs)
|
||||
*
|
||||
* Dumps page allocations.
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {Array.<string>} asArgs (asArgs[0] is an optional block address)
|
||||
*/
|
||||
Debugger.prototype.dumpMem = function(asArgs)
|
||||
{
|
||||
var aBlocks = this.cpu.aMemBlocks;
|
||||
if (aBlocks === this.cpu.aBusBlocks) {
|
||||
this.println("paging not enabled");
|
||||
return;
|
||||
}
|
||||
this.dumpBlocks(aBlocks, asArgs[0]);
|
||||
};
|
||||
|
||||
/*
|
||||
* Table of system (non-segment) descriptors, including indicators of which ones are gates.
|
||||
*/
|
||||
|
|
@ -2417,14 +2436,14 @@ if (DEBUGGER) {
|
|||
};
|
||||
|
||||
/**
|
||||
* dumpDesc(asArgs)
|
||||
* dumpSel(asArgs)
|
||||
*
|
||||
* Dumps a descriptor for the given selector.
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {Array.<string>} asArgs
|
||||
*/
|
||||
Debugger.prototype.dumpDesc = function(asArgs)
|
||||
Debugger.prototype.dumpSel = function(asArgs)
|
||||
{
|
||||
var sSel = asArgs[0];
|
||||
|
||||
|
|
@ -2440,7 +2459,7 @@ if (DEBUGGER) {
|
|||
}
|
||||
|
||||
var seg = this.getSegment(sel, Debugger.ADDR.PROT);
|
||||
this.println("dumpDesc(" + str.toHexWord(seg? seg.sel : sel) + "): %" + str.toHex(seg? seg.addrDesc : null, this.cchAddr));
|
||||
this.println("dumpSel(" + str.toHexWord(seg? seg.sel : sel) + "): %" + str.toHex(seg? seg.addrDesc : null, this.cchAddr));
|
||||
if (!seg) return;
|
||||
|
||||
var sType;
|
||||
|
|
@ -3768,19 +3787,19 @@ if (DEBUGGER) {
|
|||
Debugger.prototype.clearBreakpoints = function()
|
||||
{
|
||||
var i;
|
||||
this.aBreakExec = ["exec"];
|
||||
this.aBreakExec = ["bp"];
|
||||
if (this.aBreakRead !== undefined) {
|
||||
for (i = 1; i < this.aBreakRead.length; i++) {
|
||||
this.bus.removeMemBreak(this.getAddr(this.aBreakRead[i]), false);
|
||||
}
|
||||
}
|
||||
this.aBreakRead = ["read"];
|
||||
this.aBreakRead = ["br"];
|
||||
if (this.aBreakWrite !== undefined) {
|
||||
for (i = 1; i < this.aBreakWrite.length; i++) {
|
||||
this.bus.removeMemBreak(this.getAddr(this.aBreakWrite[i]), true);
|
||||
}
|
||||
}
|
||||
this.aBreakWrite = ["write"];
|
||||
this.aBreakWrite = ["bw"];
|
||||
/*
|
||||
* nSuppressBreaks ensures we can't get into an infinite loop where a breakpoint lookup requires
|
||||
* reading a segment descriptor via getSegment(), and that triggers more memory reads, which triggers
|
||||
|
|
@ -3955,7 +3974,7 @@ if (DEBUGGER) {
|
|||
};
|
||||
|
||||
/**
|
||||
* printBreakpoint(aBreak, i)
|
||||
* printBreakpoint(aBreak, i, sAction)
|
||||
*
|
||||
* TODO: We may need to start printing linear addresses also (if any), because segmented address can be ambiguous.
|
||||
*
|
||||
|
|
@ -3967,7 +3986,7 @@ if (DEBUGGER) {
|
|||
Debugger.prototype.printBreakpoint = function(aBreak, i, sAction)
|
||||
{
|
||||
var dbgAddr = aBreak[i];
|
||||
this.println("breakpoint " + (sAction || "enabled") + ": " + this.hexAddr(dbgAddr) + " (" + aBreak[0] + ')' + (dbgAddr.sCmd? (' "' + dbgAddr.sCmd + '"') : ''));
|
||||
this.println(aBreak[0] + ' ' + this.hexAddr(dbgAddr) + (sAction? (' ' + sAction) : (dbgAddr.sCmd? (' "' + dbgAddr.sCmd + '"') : '')));
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -5650,7 +5669,7 @@ if (DEBUGGER) {
|
|||
}
|
||||
|
||||
/*
|
||||
* Transform a "ds" command into a "d desc" command
|
||||
* Transform a "ds" command into a "d desc" command (simply as shorthand)
|
||||
*/
|
||||
if (sCmd == "ds") {
|
||||
sCmd = 'd';
|
||||
|
|
@ -5659,7 +5678,7 @@ if (DEBUGGER) {
|
|||
|
||||
if (sCmd == 'd') {
|
||||
/*
|
||||
* Transform a "d disk" command into a "l json" command
|
||||
* Transform a "d disk" command into a "l json" command (alternatively, register a dumper for "disk")
|
||||
*/
|
||||
if (sAddr == "disk") {
|
||||
asArgs[0] = "l";
|
||||
|
|
@ -5668,7 +5687,7 @@ if (DEBUGGER) {
|
|||
return;
|
||||
}
|
||||
for (m in Debugger.MESSAGES) {
|
||||
if (sAddr == m) {
|
||||
if (asArgs[1] == m) {
|
||||
var fnDumper = this.afnDumpers[m];
|
||||
if (fnDumper) {
|
||||
asArgs.shift();
|
||||
|
|
@ -5680,7 +5699,7 @@ if (DEBUGGER) {
|
|||
return;
|
||||
}
|
||||
}
|
||||
sCmd = this.sCmdDumpPrev || "db";
|
||||
if (!sAddr) sCmd = this.sCmdDumpPrev || "db";
|
||||
} else {
|
||||
this.sCmdDumpPrev = sCmd;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -474,6 +474,7 @@ var X86 = {
|
|||
INT3: 0xCC, // opINT3()
|
||||
INTN: 0xCD, // opINTn()
|
||||
INTO: 0xCE, // opINTO()
|
||||
IRET: 0xCF, // opIRET()
|
||||
LOOPNZ: 0xE0, // opLOOPNZ()
|
||||
LOOPZ: 0xE1, // opLOOPZ()
|
||||
LOOP: 0xE2, // opLOOP()
|
||||
|
|
|
|||
|
|
@ -786,7 +786,7 @@ X86CPU.prototype.mapPageBlock = function(addr, fWrite, fSuppress)
|
|||
var blockPTE = this.aBusBlocks[(addrPTE & this.nBusMask) >>> this.nBlockShift];
|
||||
var pte = blockPTE.readLong(offPTE);
|
||||
|
||||
if (!(pte & X86.PTE.PRESENT) && !fSuppress) {
|
||||
if (!(pte & X86.PTE.PRESENT)) {
|
||||
if (!fSuppress) X86.fnPageFault.call(this, addr, false, fWrite);
|
||||
return this.memEmpty;
|
||||
}
|
||||
|
|
@ -1293,11 +1293,21 @@ X86CPU.prototype.resetRegs = function()
|
|||
this.resultDst = this.resultSrc = this.resultArith = this.resultLogic = 0;
|
||||
|
||||
/*
|
||||
* This is set by fnFault() and reset (to -1) by resetRegs() and opIRET(); its initial purpose is to
|
||||
* "help" fnFault() determine when a nested fault should be converted into either a double-fault (DF_FAULT)
|
||||
* nFault is set by fnFault() and reset (to -1) by resetRegs() and opIRET(). Its initial purpose is to
|
||||
* help fnFault() determine when a nested fault should be converted into either a double-fault (DF_FAULT)
|
||||
* or a triple-fault (ie, a processor reset).
|
||||
*
|
||||
* It has since evolved into another important role: helping segCS.loadIDT() know when an exception
|
||||
* is occurring, as opposed to a software interrupt (eg, INT3, INT n or INTO). The former must set nFault
|
||||
* to the corresponding fault #, whereas the latter must set it to -1, so that if the IDT contains a gate
|
||||
* whose DPL < CPL, a GP fault will be generated instead.
|
||||
*
|
||||
* The former always call fnFault(), so that happens automatically. The latter call fnINT(), so they must
|
||||
* set nFault manually. There are also intermediate cases, like hardware interrupts, which call fnINT()
|
||||
* after manually setting nFault to the IDT #.
|
||||
*/
|
||||
this.nFault = -1;
|
||||
|
||||
/*
|
||||
* These are used to snapshot regLIP and regLSP, to help make instructions restartable;
|
||||
* currently opLIP is updated prior to every instruction, but opLSP is updated only for instructions
|
||||
|
|
@ -2352,6 +2362,18 @@ X86CPU.prototype.advanceIP = function(inc)
|
|||
{
|
||||
// DEBUG: this.assert(inc > 0);
|
||||
|
||||
/*
|
||||
* TODO: This is a crude work-around to deal with certain instructions that CMP, MOV, etc,
|
||||
* immediate operands to/from memory, because if the memory access triggered a fault, we can't
|
||||
* permit the rest of the instruction decoding to modify IP, because that'll just screw up
|
||||
* the fault dispatch.
|
||||
*
|
||||
* Ultimately, I'm probably going to have to bite the bullet and throw real JavaScript exceptions
|
||||
* to halt instructions in their tracks, but I'm going to stick with this more limited strategy
|
||||
* for now.
|
||||
*/
|
||||
if (this.opFlags & X86.OPFLAG.FAULT) return;
|
||||
|
||||
this.regLIP = (this.regLIP + inc)|0;
|
||||
/*
|
||||
* Properly comparing regLIP to regLIPLimit would normally require coercing both to unsigned
|
||||
|
|
@ -3213,7 +3235,11 @@ X86CPU.prototype.getShort = function getShort(addr)
|
|||
if (off < this.nBlockLimit) {
|
||||
return this.aMemBlocks[iBlock].readShort(off, addr);
|
||||
}
|
||||
return this.aMemBlocks[iBlock].readByte(off, addr) | (this.aMemBlocks[(iBlock + 1) & this.nBlockMask].readByte(0, addr + 1) << 8);
|
||||
var w = this.aMemBlocks[iBlock].readByte(off, addr);
|
||||
if (!(this.opFlags & X86.OPFLAG.FAULT)) {
|
||||
w |= this.aMemBlocks[(iBlock + 1) & this.nBlockMask].readByte(0, addr + 1) << 8;
|
||||
}
|
||||
return w;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -3239,8 +3265,25 @@ X86CPU.prototype.getLong = function getLong(addr)
|
|||
if (off < this.nBlockLimit - 2) {
|
||||
return this.aMemBlocks[iBlock].readLong(off, addr);
|
||||
}
|
||||
var nShift = (off & 0x3) << 3;
|
||||
return (this.aMemBlocks[iBlock].readLong(off & ~0x3, addr) >>> nShift) | (this.aMemBlocks[(iBlock + 1) & this.nBlockMask].readLong(0, addr + 3) << (32 - nShift));
|
||||
/*
|
||||
* I think the previous version of this function tried to be too clever (ie, reading the last
|
||||
* long in the current block and the first long in the next block and masking/combining the results),
|
||||
* which may have also created some undesirable side-effects for custom memory controllers.
|
||||
* This simpler (and probably more reliable) approach is to simply read the long as individual bytes.
|
||||
*/
|
||||
var l = 0;
|
||||
var cb = 4, nShift = 0;
|
||||
var cbBlock = 4 - (off & 0x3); // (off & 0x3) will be 1, 2 or 3, so cbBlock will be 3, 2, or 1
|
||||
while (cb--) {
|
||||
l |= (this.aMemBlocks[iBlock].readByte(off++, addr++) << nShift);
|
||||
if (this.opFlags & X86.OPFLAG.FAULT) break;
|
||||
if (!--cbBlock) {
|
||||
iBlock = (iBlock + 1) & this.nBlockMask;
|
||||
off = 0;
|
||||
}
|
||||
nShift += 8;
|
||||
}
|
||||
return l;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -3288,6 +3331,7 @@ X86CPU.prototype.setShort = function setShort(addr, w)
|
|||
return;
|
||||
}
|
||||
this.aMemBlocks[iBlock++].writeByte(off, w & 0xff, addr);
|
||||
if (this.opFlags & X86.OPFLAG.FAULT) return;
|
||||
this.aMemBlocks[iBlock & this.nBlockMask].writeByte(0, (w >> 8) & 0xff, addr + 1);
|
||||
};
|
||||
|
||||
|
|
@ -3317,14 +3361,23 @@ X86CPU.prototype.setLong = function setLong(addr, l)
|
|||
this.aMemBlocks[iBlock].writeLong(off, l, addr);
|
||||
return;
|
||||
}
|
||||
var lPrev, nShift = (off & 0x3) << 3;
|
||||
off &= ~0x3;
|
||||
lPrev = this.aMemBlocks[iBlock].readLong(off, addr);
|
||||
this.aMemBlocks[iBlock].writeLong(off, (lPrev & ~(-1 << nShift)) | (l << nShift), addr);
|
||||
iBlock = (iBlock + 1) & this.nBlockMask;
|
||||
addr += 3;
|
||||
lPrev = this.aMemBlocks[iBlock].readLong(0, addr);
|
||||
this.aMemBlocks[iBlock].writeLong(0, (lPrev & (-1 << nShift)) | (l >>> (32 - nShift)), addr);
|
||||
/*
|
||||
* I think the previous version of this function tried to be too clever (ie, reading and rewriting
|
||||
* the last long in the current block, and then reading and rewriting the first long in the next
|
||||
* block), which may have also created some undesirable side-effects for custom memory controllers.
|
||||
* This simpler (and probably more reliable) approach is to simply write the long as individual bytes.
|
||||
*/
|
||||
var cb = 4;
|
||||
var cbBlock = 4 - (off & 0x3); // (off & 0x3) will be 1, 2 or 3, so cbBlock will be 3, 2, or 1
|
||||
while (cb--) {
|
||||
this.aMemBlocks[iBlock].writeByte(off++, l & 0xff, addr++);
|
||||
if (this.opFlags & X86.OPFLAG.FAULT) return;
|
||||
if (!--cbBlock) {
|
||||
iBlock = (iBlock + 1) & this.nBlockMask;
|
||||
off = 0;
|
||||
}
|
||||
l >>>= 8;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -4139,7 +4192,7 @@ X86CPU.prototype.checkINTR = function()
|
|||
if (nIDT >= 0) {
|
||||
this.intFlags &= ~X86.INTFLAG.HALT;
|
||||
// if (DEBUG) this.pushRegFrame(); // the corresponding popRegFrame() is in opIRET()
|
||||
X86.fnINT.call(this, nIDT, null, 11);
|
||||
X86.fnINT.call(this, this.nFault = nIDT, null, 11);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -4149,7 +4202,12 @@ X86CPU.prototype.checkINTR = function()
|
|||
if ((this.intFlags & X86.INTFLAG.TRAP)) {
|
||||
this.intFlags &= ~X86.INTFLAG.TRAP;
|
||||
if (I386 && this.model >= X86.MODEL_80386) this.regDR[6] |= X86.DR6.BS;
|
||||
X86.fnINT.call(this, X86.EXCEPTION.DEBUG, null, 11);
|
||||
/*
|
||||
* TODO: Perhaps we should call fnFault() instead; eg:
|
||||
*
|
||||
* X86.fnFault.call(this, X86.EXCEPTION.DEBUG, null, false, 11);
|
||||
*/
|
||||
X86.fnINT.call(this, this.nFault = X86.EXCEPTION.DEBUG, null, 11);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -142,28 +142,6 @@ 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);
|
||||
|
|
@ -205,12 +183,15 @@ X86.fnBOUND = function BOUND(dst, src)
|
|||
this.nStepCycles -= this.cycleCounts.nOpCyclesBound;
|
||||
if (wIndex < wLower || wIndex > wUpper) {
|
||||
/*
|
||||
* The INT 0x05 handler must be called with CS:IP pointing to the BOUND instruction.
|
||||
* The INT 0x05 handler must be called with CS:IP pointing to the BOUND instruction, which
|
||||
* fnFault() takes care of. TODO: Determine whether this should be treated like a fault, or like
|
||||
* a software interrupt, with an explicit call to fnINT() and nFault = -1, like opINT3(), opINTn()
|
||||
* and opINTO().
|
||||
*
|
||||
* TODO: Determine the cycle cost when a BOUND exception is triggered, over and above nCyclesBound.
|
||||
* TODO: Determine the cycle cost when a BOUND exception is triggered, over and above nCyclesBound,
|
||||
* and then call X86.fnFault(X86.EXCEPTION.BOUND_ERR, null, false, nCycles).
|
||||
*/
|
||||
this.setIP(this.opLIP - this.segCS.base);
|
||||
X86.fnINT.call(this, X86.EXCEPTION.BOUND_ERR, null, 0);
|
||||
X86.fnFault.call(this, X86.EXCEPTION.BOUND_ERR);
|
||||
}
|
||||
this.opFlags |= X86.OPFLAG.NOWRITE;
|
||||
return dst;
|
||||
|
|
@ -1841,7 +1822,8 @@ X86.fnLIDT = function LIDT(dst, src)
|
|||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
X86.fnLLDT = function LLDT(dst, src) {
|
||||
X86.fnLLDT = function LLDT(dst, src)
|
||||
{
|
||||
this.opFlags |= X86.OPFLAG.NOWRITE;
|
||||
this.segLDT.load(dst);
|
||||
this.nStepCycles -= (17 + (this.regEA === X86.ADDR_INVALID? 0 : 2));
|
||||
|
|
@ -3844,32 +3826,31 @@ X86.fnGRPUndefined = function GRPUndefined(dst, src)
|
|||
*/
|
||||
X86.fnDIVOverflow = function DIVOverflow()
|
||||
{
|
||||
this.setIP(this.opLIP - this.segCS.base);
|
||||
/*
|
||||
* TODO: Determine the proper cycle cost.
|
||||
*/
|
||||
X86.fnINT.call(this, X86.EXCEPTION.DIV_ERR, null, 2);
|
||||
X86.fnFault.call(this, X86.EXCEPTION.DIV_ERR, null, false, 2);
|
||||
};
|
||||
|
||||
/**
|
||||
* fnSrcCount1()
|
||||
* fnSRCCount1()
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @return {number}
|
||||
*/
|
||||
X86.fnSrcCount1 = function SrcCount1()
|
||||
X86.fnSRCCount1 = function SRCCount1()
|
||||
{
|
||||
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? 2 : this.cycleCounts.nOpCyclesShift1M);
|
||||
return 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* fnSrcCountCL()
|
||||
* fnSRCCountCL()
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @return {number}
|
||||
*/
|
||||
X86.fnSrcCountCL = function SrcCountCL()
|
||||
X86.fnSRCCountCL = function SRCCountCL()
|
||||
{
|
||||
var count = this.regECX & 0xff;
|
||||
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesShiftCR : this.cycleCounts.nOpCyclesShiftCM) + (count << this.cycleCounts.nOpCyclesShiftCS);
|
||||
|
|
@ -3877,12 +3858,12 @@ X86.fnSrcCountCL = function SrcCountCL()
|
|||
};
|
||||
|
||||
/**
|
||||
* fnSrcCountN()
|
||||
* fnSRCCountN()
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @return {number}
|
||||
*/
|
||||
X86.fnSrcCountN = function SrcCountN()
|
||||
X86.fnSRCCountN = function SRCCountN()
|
||||
{
|
||||
var count = this.getIPByte();
|
||||
this.nStepCycles -= (this.regEA === X86.ADDR_INVALID? this.cycleCounts.nOpCyclesShiftCR : this.cycleCounts.nOpCyclesShiftCM) + (count << this.cycleCounts.nOpCyclesShiftCS);
|
||||
|
|
@ -3890,16 +3871,31 @@ X86.fnSrcCountN = function SrcCountN()
|
|||
};
|
||||
|
||||
/**
|
||||
* fnSrcNone()
|
||||
* fnSRCNone()
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @return {number|null}
|
||||
*/
|
||||
X86.fnSrcNone = function SrcNone()
|
||||
X86.fnSRCNone = function SRCNone()
|
||||
{
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* fnSRCxx()
|
||||
*
|
||||
* This is used by opPOPmw(), because the actual pop must occur BEFORE the effective address (EA)
|
||||
* calculation. So opPOPmw() does the pop, saves the popped value in regXX, and this passes src function
|
||||
* to the EA worker.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @return {number} regXX
|
||||
*/
|
||||
X86.fnSRCxx = function SRCxx()
|
||||
{
|
||||
return this.regXX;
|
||||
};
|
||||
|
||||
/**
|
||||
* fnFault(nFault, nError, fHalt, nCycles)
|
||||
*
|
||||
|
|
@ -3907,38 +3903,22 @@ X86.fnSrcNone = function SrcNone()
|
|||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} nFault
|
||||
* @param {number} [nError] (if omitted, no error code will be pushed)
|
||||
* @param {boolean} [fHalt] true to halt the CPU (if the Debugger is loaded), false to not, undefined if "it depends"
|
||||
* @param {number|null} [nError] (if omitted, no error code will be pushed)
|
||||
* @param {boolean} [fHalt] (true to halt the CPU, false to not, undefined if "it depends")
|
||||
* @param {number} [nCycles] cycle count to pass through to fnINT(), if any
|
||||
*/
|
||||
X86.fnFault = function(nFault, nError, fHalt, nCycles)
|
||||
{
|
||||
/*
|
||||
* X86.OPFLAG.FAULT flag is used by selected opcodes to provide an early exit, restore register(s), or whatever is
|
||||
* needed to help ensure instruction restartability; there is currently no general-purpose mechanism for snapping
|
||||
* and restoring all registers for any instruction that might fault, so it's every opcode for themselves.
|
||||
*
|
||||
* X86.EXCEPTION.DEBUG exceptions set their own special flag, X86.OPFLAG.DEBUG, to prevent redundant DEBUG exceptions,
|
||||
* so we don't need to set OPFLAG.FAULT in that case, because a DEBUG exception doesn't actually prevent an instruction
|
||||
* from executing.
|
||||
*
|
||||
* TODO: Review the restartability of all our opcode handlers, starting with those that affect the segment registers
|
||||
* and then moving on to the rest, and determine whether we really need a general-purpose solution instead.
|
||||
*/
|
||||
if (nFault == X86.EXCEPTION.DEBUG) {
|
||||
this.opFlags |= X86.OPFLAG.DEBUG;
|
||||
} else {
|
||||
this.opFlags |= X86.OPFLAG.FAULT;
|
||||
}
|
||||
var fDispatch = null;
|
||||
|
||||
if (!this.aFlags.fComplete) {
|
||||
this.printMessage("Fault " + str.toHexByte(nFault) + " blocked by PCjs", Messages.WARN);
|
||||
/*
|
||||
* 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.setIP(this.opLIP - this.segCS.base);
|
||||
return;
|
||||
}
|
||||
|
||||
var fDispatch = false;
|
||||
if (this.model >= X86.MODEL_80186) {
|
||||
else if (this.model >= X86.MODEL_80186) {
|
||||
if (this.nFault < 0) {
|
||||
/*
|
||||
* Single-fault (error code is passed through, and the responsible instruction is restartable)
|
||||
|
|
@ -3949,21 +3929,22 @@ X86.fnFault = function(nFault, nError, fHalt, nCycles)
|
|||
this.opLSP = X86.ADDR_INVALID;
|
||||
}
|
||||
fDispatch = true;
|
||||
} else if (this.nFault != X86.EXCEPTION.DF_FAULT) {
|
||||
}
|
||||
else if (this.nFault != X86.EXCEPTION.DF_FAULT) {
|
||||
/*
|
||||
* 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;
|
||||
fDispatch = true;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Triple-fault (usually referred to in Intel literature as a "shutdown", but at least on the 80286,
|
||||
* it's actually a "reset")
|
||||
*/
|
||||
X86.fnFaultMessage.call(this, -1, 0, fHalt);
|
||||
nFault = -1; nError = 0;
|
||||
this.resetRegs();
|
||||
return;
|
||||
fHalt = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3972,28 +3953,49 @@ X86.fnFault = function(nFault, nError, fHalt, nCycles)
|
|||
}
|
||||
|
||||
if (fDispatch) {
|
||||
|
||||
this.nFault = nFault;
|
||||
X86.fnINT.call(this, nFault, nError, nCycles || 0);
|
||||
|
||||
/*
|
||||
* REP'eated instructions that rewind regLIP to opLIP used to screw up this dispatch,
|
||||
* so now we slip the new regLIP into opLIP, effectively turning their action into a no-op.
|
||||
*/
|
||||
this.opLIP = this.regLIP;
|
||||
}
|
||||
|
||||
/*
|
||||
* Since this fault is likely being issued in the context of an instruction that hasn't finished
|
||||
* executing, and since we currently don't do anything to interrupt that execution (eg, throw a
|
||||
* JavaScript exception), we should shut off all further reads/writes for the current instruction.
|
||||
*
|
||||
* That's easy for any EA-based memory accesses: simply set both the NOREAD and NOWRITE flags.
|
||||
* However, there are also direct, non-EA-based memory accesses to consider. A perfect example is
|
||||
* 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 redirect.
|
||||
*/
|
||||
this.opFlags |= (X86.OPFLAG.NOREAD | X86.OPFLAG.NOWRITE);
|
||||
/*
|
||||
* X86.OPFLAG.FAULT flag is used by selected opcodes to provide an early exit, restore register(s), or whatever is
|
||||
* needed to help ensure instruction restartability; there is currently no general-purpose mechanism for snapping
|
||||
* and restoring all registers for any instruction that might fault, so it's every opcode for themselves.
|
||||
*
|
||||
* X86.EXCEPTION.DEBUG exceptions set their own special flag, X86.OPFLAG.DEBUG, to prevent redundant DEBUG exceptions,
|
||||
* so we don't need to set OPFLAG.FAULT in that case, because a DEBUG exception doesn't actually prevent an instruction
|
||||
* from executing (and therefore doesn't need to be restarted).
|
||||
*
|
||||
* TODO: Review the restartability of all our opcode handlers, starting with those that affect the segment registers
|
||||
* and then moving on to the rest, and determine whether we really need a general-purpose solution instead.
|
||||
*/
|
||||
if (nFault == X86.EXCEPTION.DEBUG) {
|
||||
this.opFlags |= X86.OPFLAG.DEBUG;
|
||||
} else if (nFault >= 0) {
|
||||
this.opFlags |= X86.OPFLAG.FAULT;
|
||||
}
|
||||
|
||||
/*
|
||||
* Since this fault is likely being issued in the context of an instruction that hasn't finished
|
||||
* executing, and since we currently don't do anything to interrupt that execution (eg, throw a
|
||||
* JavaScript exception), we should shut off all further reads/writes for the current instruction.
|
||||
*
|
||||
* That's easy for any EA-based memory accesses: simply set both the NOREAD and NOWRITE flags.
|
||||
* However, there are also direct, non-EA-based memory accesses to consider. A perfect example is
|
||||
* 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 -- unless the handler for
|
||||
* such an opcode checks this.opFlags for X86.OPFLAG.FAULT after each step of the operation.
|
||||
*
|
||||
* TODO: Throw a special JavaScript exception that cpu.js must intercept and quietly redirect.
|
||||
*/
|
||||
this.opFlags |= (X86.OPFLAG.NOREAD | X86.OPFLAG.NOWRITE);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -4017,7 +4019,7 @@ X86.fnPageFault = function(addr, fPresent, fWrite)
|
|||
};
|
||||
|
||||
/**
|
||||
* fnFaultMessage()
|
||||
* fnFaultMessage(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.
|
||||
|
|
@ -4029,8 +4031,8 @@ X86.fnPageFault = function(addr, fPresent, fWrite)
|
|||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} nFault
|
||||
* @param {number} [nError] (if omitted, no error code will be reported)
|
||||
* @param {boolean} [fHalt] true to halt the CPU (if the Debugger is loaded), false to not, undefined if "it depends"
|
||||
* @param {number|null} [nError] (if omitted, no error code will be reported)
|
||||
* @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)
|
||||
|
|
@ -4073,11 +4075,9 @@ X86.fnFaultMessage = function(nFault, nError, fHalt)
|
|||
fHalt = false;
|
||||
}
|
||||
}
|
||||
// else {
|
||||
// if (nFault == X86.EXCEPTION.PG_FAULT || nFault == X86.EXCEPTION.GP_FAULT && this.model == X86.MODEL_80386 /* || nFault == X86.EXCEPTION.NP_FAULT && bOpcode == 0x8E */) {
|
||||
// fHalt = true;
|
||||
// }
|
||||
// }
|
||||
if (nFault == X86.EXCEPTION.PG_FAULT && bOpcode == X86.OPCODE.IRET) {
|
||||
fHalt = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* If fHalt has been explicitly set to false, we also take that as a cue to disable fault messages
|
||||
|
|
@ -4091,6 +4091,10 @@ X86.fnFaultMessage = function(nFault, nError, fHalt)
|
|||
* 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 the LIP being in the range 0x0F0000 to 0x0FFFFF.
|
||||
*
|
||||
* TODO: Be aware that this test can trigger false positives, such as when a V86-mode ARPL is hit; eg:
|
||||
*
|
||||
* &FD82:22F7 6338 ARPL [BX+SI],DI
|
||||
*/
|
||||
if (this.regLIP >= 0x0F0000 && this.regLIP <= 0x0FFFFF) {
|
||||
fHalt = false;
|
||||
|
|
@ -4105,8 +4109,11 @@ X86.fnFaultMessage = function(nFault, nError, fHalt)
|
|||
}
|
||||
|
||||
if (this.messageEnabled(bitsMessage) || fHalt) {
|
||||
var sMessage = "Fault " + str.toHexByte(nFault) + (nError != null? " (" + str.toHexWord(nError) + ")" : "") + " on opcode " + str.toHexByte(bOpcode);
|
||||
|
||||
var fRunning = this.aFlags.fRunning;
|
||||
var sMessage = "Fault " + str.toHexByte(nFault) + (nError != null? " (" + str.toHexWord(nError) + ")" : "") + " on opcode " + str.toHexByte(bOpcode);
|
||||
if (fHalt && fRunning) sMessage += " (blocked by PCjs Debugger)";
|
||||
|
||||
if (this.printMessage(sMessage, fHalt || bitsMessage, true)) {
|
||||
if (fHalt) {
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ X86.opGRP6 = function GRP6()
|
|||
if ((bModRM & 0x38) < 0x10) { // possible reg values: 0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38
|
||||
this.opFlags |= X86.OPFLAG.NOREAD;
|
||||
}
|
||||
this.aOpModGrpWord[bModRM].call(this, this.aOpGrp6, X86.fnSrcNone);
|
||||
this.aOpModGrpWord[bModRM].call(this, this.aOpGrp6, X86.fnSRCNone);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -61,7 +61,7 @@ X86.opGRP7 = function GRP7()
|
|||
if (!(bModRM & 0x10)) {
|
||||
this.opFlags |= X86.OPFLAG.NOREAD;
|
||||
}
|
||||
this.aOpModGrpWord[bModRM].call(this, X86.aOpGrp7, X86.fnSrcNone);
|
||||
this.aOpModGrpWord[bModRM].call(this, X86.aOpGrp7, X86.fnSRCNone);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1336,6 +1336,31 @@ X86.opBOUND = function BOUND()
|
|||
*/
|
||||
X86.opARPL = function ARPL()
|
||||
{
|
||||
/*
|
||||
* 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 the 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.
|
||||
*
|
||||
* Which is WHY we must perform the CPU mode tests below rather than in the fnARPL() worker; otherwise we could
|
||||
* generate additional (bogus) faults, based on the address of the first operand.
|
||||
*
|
||||
* 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;
|
||||
}
|
||||
this.aOpModMemWord[this.getIPByte()].call(this, X86.fnARPL);
|
||||
};
|
||||
|
||||
|
|
@ -2119,7 +2144,7 @@ X86.opMOVwsr = function MOVwsr()
|
|||
break;
|
||||
}
|
||||
X86.opInvalid.call(this);
|
||||
break;
|
||||
return;
|
||||
case 0x5:
|
||||
if (I386 && this.model >= X86.MODEL_80386) {
|
||||
this.regXX = this.segGS.sel;
|
||||
|
|
@ -2128,7 +2153,7 @@ X86.opMOVwsr = function MOVwsr()
|
|||
/* falls through */
|
||||
default:
|
||||
X86.opInvalid.call(this);
|
||||
break;
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* Like other MOV operations, the destination does not need to be read, just written.
|
||||
|
|
@ -2255,7 +2280,22 @@ X86.opPOPmw = function POPmw()
|
|||
* Like other MOV operations, the destination does not need to be read, just written.
|
||||
*/
|
||||
this.opFlags |= X86.OPFLAG.NOREAD;
|
||||
this.aOpModGrpWord[this.getIPByte()].call(this, X86.aOpGrpPOPw, this.popWord);
|
||||
/*
|
||||
* A "clever" instruction like this:
|
||||
*
|
||||
* #0117:651C 67668F442408 POP DWORD [ESP+08]
|
||||
*
|
||||
* pops the DWORD from the top of the stack and places it at ESP+08, where ESP is the value
|
||||
* AFTER the pop, not before. We used to (incorrectly) pass "popWord" as the fnSrc parameter
|
||||
* below; we now pop the word first, saving it in regXX, and then pass "fnSRCxx" as fnSrc,
|
||||
* which simply returns the contents of regXX.
|
||||
*
|
||||
* Also, in case you're wondering, fnPUSHw() (in aOpGrp4w) is the complement to this instruction,
|
||||
* but it doesn't require a similar work-around, because a push from memory accesses that memory
|
||||
* BEFORE the push, which occurs through our normal ModRM processing.
|
||||
*/
|
||||
this.regXX = this.popWord();
|
||||
this.aOpModGrpWord[this.getIPByte()].call(this, X86.aOpGrpPOPw, X86.fnSRCxx);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -3258,7 +3298,7 @@ X86.opMOVDI = function MOVDI()
|
|||
*/
|
||||
X86.opGRP2bn = function GRP2bn()
|
||||
{
|
||||
this.aOpModGrpByte[this.getIPByte()].call(this, X86.aOpGrp2b, X86.fnSrcCountN);
|
||||
this.aOpModGrpByte[this.getIPByte()].call(this, X86.aOpGrp2b, X86.fnSRCCountN);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -3268,7 +3308,7 @@ X86.opGRP2bn = function GRP2bn()
|
|||
*/
|
||||
X86.opGRP2wn = function GRP2wn()
|
||||
{
|
||||
this.aOpModGrpWord[this.getIPByte()].call(this, this.sizeData == 2? X86.aOpGrp2w : X86.aOpGrp2d, X86.fnSrcCountN);
|
||||
this.aOpModGrpWord[this.getIPByte()].call(this, this.sizeData == 2? X86.aOpGrp2w : X86.aOpGrp2d, X86.fnSRCCountN);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -3461,6 +3501,7 @@ X86.opINT3 = function INT3()
|
|||
* function to stop execution on INT3 whenever both the INT and HALT message bits are set; a simple "g"
|
||||
* command allows you to continue.
|
||||
*/
|
||||
this.nFault = -1;
|
||||
X86.fnINT.call(this, X86.EXCEPTION.BREAKPOINT, null, this.cycleCounts.nOpCyclesInt3D);
|
||||
};
|
||||
|
||||
|
|
@ -3485,6 +3526,7 @@ X86.opINTn = function INTn()
|
|||
* and returns false ONLY if a notification handler returned false (ie, requesting the interrupt be skipped).
|
||||
*/
|
||||
if (this.checkIntNotify(nInt)) {
|
||||
this.nFault = -1;
|
||||
X86.fnINT.call(this, nInt, null, 0);
|
||||
return;
|
||||
}
|
||||
|
|
@ -3507,6 +3549,7 @@ X86.opINTO = function INTO()
|
|||
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
|
||||
return;
|
||||
}
|
||||
this.nFault = -1;
|
||||
X86.fnINT.call(this, X86.EXCEPTION.OVERFLOW, null, this.cycleCounts.nOpCyclesIntOD);
|
||||
return;
|
||||
}
|
||||
|
|
@ -3539,7 +3582,7 @@ X86.opIRET = function IRET()
|
|||
*/
|
||||
X86.opGRP2b1 = function GRP2b1()
|
||||
{
|
||||
this.aOpModGrpByte[this.getIPByte()].call(this, X86.aOpGrp2b, X86.fnSrcCount1);
|
||||
this.aOpModGrpByte[this.getIPByte()].call(this, X86.aOpGrp2b, X86.fnSRCCount1);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -3549,7 +3592,7 @@ X86.opGRP2b1 = function GRP2b1()
|
|||
*/
|
||||
X86.opGRP2w1 = function GRP2w1()
|
||||
{
|
||||
this.aOpModGrpWord[this.getIPByte()].call(this, this.sizeData == 2? X86.aOpGrp2w : X86.aOpGrp2d, X86.fnSrcCount1);
|
||||
this.aOpModGrpWord[this.getIPByte()].call(this, this.sizeData == 2? X86.aOpGrp2w : X86.aOpGrp2d, X86.fnSRCCount1);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -3559,7 +3602,7 @@ X86.opGRP2w1 = function GRP2w1()
|
|||
*/
|
||||
X86.opGRP2bCL = function GRP2bCL()
|
||||
{
|
||||
this.aOpModGrpByte[this.getIPByte()].call(this, X86.aOpGrp2b, X86.fnSrcCountCL);
|
||||
this.aOpModGrpByte[this.getIPByte()].call(this, X86.aOpGrp2b, X86.fnSRCCountCL);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -3569,7 +3612,7 @@ X86.opGRP2bCL = function GRP2bCL()
|
|||
*/
|
||||
X86.opGRP2wCL = function GRP2wCL()
|
||||
{
|
||||
this.aOpModGrpWord[this.getIPByte()].call(this, this.sizeData == 2? X86.aOpGrp2w : X86.aOpGrp2d, X86.fnSrcCountCL);
|
||||
this.aOpModGrpWord[this.getIPByte()].call(this, this.sizeData == 2? X86.aOpGrp2w : X86.aOpGrp2d, X86.fnSRCCountCL);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -4052,7 +4095,7 @@ X86.opCMC = function CMC()
|
|||
X86.opGRP3b = function GRP3b()
|
||||
{
|
||||
this.fMDSet = false;
|
||||
this.aOpModGrpByte[this.getIPByte()].call(this, X86.aOpGrp3b, X86.fnSrcNone);
|
||||
this.aOpModGrpByte[this.getIPByte()].call(this, X86.aOpGrp3b, X86.fnSRCNone);
|
||||
if (this.fMDSet) this.regEAX = (this.regEAX & ~this.maskData) | (this.regMDLo & this.maskData);
|
||||
};
|
||||
|
||||
|
|
@ -4078,7 +4121,7 @@ X86.opGRP3b = function GRP3b()
|
|||
X86.opGRP3w = function GRP3w()
|
||||
{
|
||||
this.fMDSet = false;
|
||||
this.aOpModGrpWord[this.getIPByte()].call(this, X86.aOpGrp3w, X86.fnSrcNone);
|
||||
this.aOpModGrpWord[this.getIPByte()].call(this, X86.aOpGrp3w, X86.fnSRCNone);
|
||||
if (this.fMDSet) {
|
||||
this.regEAX = (this.regEAX & ~this.maskData) | (this.regMDLo & this.maskData);
|
||||
this.regEDX = (this.regEDX & ~this.maskData) | (this.regMDHi & this.maskData);
|
||||
|
|
@ -4177,7 +4220,7 @@ X86.opSTD = function STD()
|
|||
*/
|
||||
X86.opGRP4b = function GRP4b()
|
||||
{
|
||||
this.aOpModGrpByte[this.getIPByte()].call(this, X86.aOpGrp4b, X86.fnSrcNone);
|
||||
this.aOpModGrpByte[this.getIPByte()].call(this, X86.aOpGrp4b, X86.fnSRCNone);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -4187,7 +4230,7 @@ X86.opGRP4b = function GRP4b()
|
|||
*/
|
||||
X86.opGRP4w = function GRP4w()
|
||||
{
|
||||
this.aOpModGrpWord[this.getIPByte()].call(this, X86.aOpGrp4w, X86.fnSrcNone);
|
||||
this.aOpModGrpWord[this.getIPByte()].call(this, X86.aOpGrp4w, X86.fnSRCNone);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -322,12 +322,11 @@ X86Seg.prototype.loadIDTProt = function loadIDTProt(nIDT)
|
|||
var addrDesc = (cpu.addrIDT + nIDT)|0;
|
||||
if (((cpu.addrIDTLimit - addrDesc)|0) >= 7) {
|
||||
this.fCall = true;
|
||||
return this.loadDesc8(addrDesc, nIDT) + this.offIP;
|
||||
var addr = this.loadDesc8(addrDesc, nIDT);
|
||||
if (addr !== X86.ADDR_INVALID) addr += this.offIP;
|
||||
return addr;
|
||||
}
|
||||
/*
|
||||
* TODO: Remove fHalt=true from this fnFault() call once this code path has been tested.
|
||||
*/
|
||||
X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, nIDT | X86.ERRCODE.IDT, true);
|
||||
X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, nIDT | X86.ERRCODE.IDT);
|
||||
return X86.ADDR_INVALID;
|
||||
};
|
||||
|
||||
|
|
@ -484,9 +483,13 @@ X86Seg.prototype.checkReadDebugger = function checkReadDebugger(off, cb)
|
|||
/*
|
||||
* The Debugger doesn't have separate "check" interfaces for real and protected mode,
|
||||
* since it's not performance-critical. If addrDesc is invalid, then we assume real mode.
|
||||
*
|
||||
* TODO: This doesn't actually check the segment for readability.
|
||||
*/
|
||||
if (DEBUGGER) {
|
||||
if (this.addrDesc === X86.ADDR_INVALID || (off >>> 0) + cb <= this.offMax) {
|
||||
if (this.addrDesc === X86.ADDR_INVALID ||
|
||||
this.fExpDown && (off >>> 0) + cb > this.offMax ||
|
||||
!this.fExpDown && (off >>> 0) + cb <= this.offMax) {
|
||||
return (this.base + off)|0;
|
||||
}
|
||||
}
|
||||
|
|
@ -506,9 +509,13 @@ X86Seg.prototype.checkWriteDebugger = function checkWriteDebugger(off, cb)
|
|||
/*
|
||||
* The Debugger doesn't have separate "check" interfaces for real and protected mode,
|
||||
* since it's not performance-critical. If addrDesc is invalid, then we assume real mode.
|
||||
*
|
||||
* TODO: This doesn't actually check the segment for writability.
|
||||
*/
|
||||
if (DEBUGGER) {
|
||||
if (this.addrDesc === X86.ADDR_INVALID || (off >>> 0) + cb <= this.offMax) {
|
||||
if (this.addrDesc === X86.ADDR_INVALID ||
|
||||
this.fExpDown && (off >>> 0) + cb > this.offMax ||
|
||||
!this.fExpDown && (off >>> 0) + cb <= this.offMax) {
|
||||
return (this.base + off)|0;
|
||||
}
|
||||
}
|
||||
|
|
@ -696,7 +703,8 @@ 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, addrTSS, offSP, lenSP, regSPPrev, regSSPrev, regPSClear, regSP;
|
||||
var sizeGate, selCode, cplOld, 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
|
||||
|
|
@ -784,16 +792,21 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe)
|
|||
* portion of ACC should always be zero, but that's really dependent on the descriptor being properly
|
||||
* set (which we assert above).
|
||||
*/
|
||||
if (rpl <= dpl) {
|
||||
/*
|
||||
* TODO: Verify the PRESENT bit of the gate descriptor, and issue NP_FAULT as appropriate.
|
||||
*/
|
||||
cplOld = this.cpl;
|
||||
cplOld = this.cpl;
|
||||
fIDT = (addrDesc == cpu.addrIDT + sel);
|
||||
|
||||
/*
|
||||
* Software interrupts (where fIDT is true and cpu.nFault < 0) require an additional test: if DPL < CPL,
|
||||
* then we must fall into the GP_FAULT code at the end of this case.
|
||||
*/
|
||||
if (rpl <= dpl && (!fIDT || cpu.nFault >= 0 || cplOld <= dpl)) {
|
||||
|
||||
/*
|
||||
* 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
|
||||
* original limit and ext fields.
|
||||
*
|
||||
* TODO: Verify the PRESENT bit of the gate descriptor, and issue NP_FAULT as appropriate.
|
||||
*/
|
||||
selCode = base & 0xffff;
|
||||
if (I386 && (type & X86.DESC.ACC.NONSEG_386)) {
|
||||
|
|
@ -894,12 +907,8 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe)
|
|||
}
|
||||
|
||||
if (sizeGate !== 0) {
|
||||
var nError = sel & X86.ERRCODE.SELMASK;
|
||||
if (addrDesc >= cpu.addrIDT && addrDesc < cpu.addrIDTLimit) nError |= X86.ERRCODE.IDT;
|
||||
/*
|
||||
* TODO: Remove fHalt=true from this fnFault() call once this code path has been tested.
|
||||
*/
|
||||
if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, nError, true);
|
||||
var nError = (sel & X86.ERRCODE.SELMASK) | (fIDT? X86.ERRCODE.IDT : 0);
|
||||
if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, nError);
|
||||
return X86.ADDR_INVALID;
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
Loading…
Reference in a new issue