CPU function reorg

This commit is contained in:
Jeff Parsons 2015-03-05 14:41:34 -08:00 committed by jeffpar
commit 1dc0aeccd6
16 changed files with 9794 additions and 9015 deletions

View file

@ -43,15 +43,14 @@
"X86": true,
"X86Seg": true,
"X86CPU": true,
"X86Grps": true,
"X86Help": true,
"X86Func": true,
"X86OpXX": true,
"X86Op0F": true,
"X86ModB": true,
"X86ModW": true,
"X86ModB32": true,
"X86ModW32": true,
"X86ModSIB": true,
"X86OpXX": true,
"X86Op0F": true,
"str": true,
"usr": true,
"web": true,

View file

@ -11,7 +11,7 @@ So it's best to refer to these files generically as "modules", and more specific
whenever they implement a specific device (or set of devices, in the case of [*ChipSet*](/docs/pcjs/chipset/)).
Examples of non-device modules include UI modules like [panel.js](panel.js) and [debugger.js](debugger.js),
and sub-modules like [x86opxx.js](x86opxx.js) and [x86help.js](x86help.js) that separate the CPU functionality
and sub-modules like [x86opxx.js](x86opxx.js) and [x86func.js](x86func.js) that separate the CPU functionality
of [x86.js](x86.js) into more manageable pieces.
These modules should always be loaded or compiled in the order listed by the *pcJSFiles* property in
@ -37,8 +37,7 @@ At the time of this writing, the order is:
* [pcjs/x86.js](x86.js)
* [pcjs/x86seg.js](x86seg.js)
* [pcjs/x86cpu.js](x86cpu.js)
* [pcjs/x86grps.js](x86grps.js)
* [pcjs/x86help.js](x86help.js)
* [pcjs/x86func.js](x86func.js)
* [pcjs/x86modb.js](x86modb.js)
* [pcjs/x86modw.js](x86modw.js)
* [pcjs/x86modb16.js](x86modb16.js)

View file

@ -2541,9 +2541,7 @@ if (DEBUGGER) {
* getByte(aAddr, inc)
*
* getByte() should be used for all Debugger memory reads (eg, doDump, doUnassemble), to ensure
* all notification handlers are bypassed for physical addresses; for segmented addresses, we must
* use the CPU's X86Seg load() logic, but we don't call the CPU's getSOByte() or getByte() functions,
* to avoid triggering any memory read notifications.
* all notification handlers are bypassed for physical addresses.
*
* @this {Debugger}
* @param {Array} aAddr
@ -2689,7 +2687,7 @@ if (DEBUGGER) {
* aAddr[2] = this.getAddr(aAddr);
*
* The way to create a real-mode breakpoint that will break regardless of mode is to
* use the physical address of the real-mode memory location.
* use the physical address of the real-mode memory location instead.
*/
aAddr[3] = fTemp;
aBreak.push(aAddr);
@ -4670,7 +4668,7 @@ if (DEBUGGER) {
fUnknown = false;
switch(sRegMatch){
case "MS":
X86Help.opHelpLMSW.call(this.cpu, w);
this.cpu.setMSW(w);
break;
case "TR":
this.cpu.segTSS.load(w);

View file

@ -240,8 +240,8 @@ var X86 = {
MASK: 0xfff8 // index of corresponding entry in GDT, LDT or IDT
},
RESULT: {
SIZE_BYTE: 0x00100, // mask for byte arithmetic instructions (after subtracting 1)
SIZE_WORD: 0x10000, // mask for word arithmetic instructions (after subtracting 1)
SIZE_BYTE: 0x00100,
SIZE_WORD: 0x10000,
AUXOVF_AF: 0x00010,
AUXOVF_OF: 0x08080,
AUXOVF_CF: 0x10100

View file

@ -42,12 +42,11 @@ if (typeof module !== 'undefined') {
var CPU = require("./cpu");
var X86 = require("./x86");
var X86Seg = require("./x86seg");
var X86Grps = require("./x86grps");
var X86Help = require("./x86help");
var X86ModB = require("./x86modb");
var X86ModW = require("./x86modw");
var X86Func = require("./x86func");
var X86OpXX = require("./x86opxx");
var X86Op0F = require("./x86op0f");
var X86ModB = require("./x86modb");
var X86ModW = require("./x86modw");
}
if (I386) {
@ -482,7 +481,7 @@ X86CPU.PREFETCH = {
* regLIP has been set, so flushPrefetch() expects to receive that address.
*
* If the prefetch queue does not contain any (or enough) bytes to satisfy a getBytePrefetch()
* or getWordPrefetch() request, we force the queue to be filled with the necessary number
* or getShortPrefetch() request, we force the queue to be filled with the necessary number
* of bytes first.
*
* @this {X86CPU}
@ -706,10 +705,10 @@ X86CPU.prototype.initProcessor = function()
*/
this.CYCLES = (this.model >= X86.MODEL_80286? X86CPU.CYCLES_80286 : X86CPU.CYCLES_8088);
this.aOps = X86OpXX.aOps;
this.aOpGrp4b = X86Grps.aOpGrp4b;
this.aOpGrp4w = X86Grps.aOpGrp4w;
this.aOpGrp6 = X86Op0F.aOpGrp6Real; // setProtMode() will ensure that aOpGrp6 is switched
this.aOps = X86.aOps;
this.aOpGrp4b = X86.aOpGrp4b;
this.aOpGrp4w = X86.aOpGrp4w;
this.aOpGrp6 = X86.aOpGrp6Real; // setProtMode() will ensure that aOpGrp6 is switched
if (this.model >= X86.MODEL_80186) {
/*
@ -720,34 +719,34 @@ X86CPU.prototype.initProcessor = function()
* Instruction handlers that contain "hard-coded" 80286 cycle times include: opINSb, opINSw, opOUTSb,
* opOUTSw, opENTER, and opLEAVE.
*/
this.aOps = X86OpXX.aOps.slice(); // make copies of aOps and others before modifying them
this.aOpGrp4b = X86Grps.aOpGrp4b.slice();
this.aOpGrp4w = X86Grps.aOpGrp4w.slice();
this.aOps = X86.aOps.slice(); // make copies of aOps and others before modifying them
this.aOpGrp4b = X86.aOpGrp4b.slice();
this.aOpGrp4w = X86.aOpGrp4w.slice();
this.nShiftCountMask = 0x1f; // on newer processors, all shift counts are MOD 32
this.aOps[0x0F] = X86Help.opHelpInvalid;
this.aOps[X86.OPCODE.PUSHA] = X86OpXX.opPUSHA;
this.aOps[X86.OPCODE.POPA] = X86OpXX.opPOPA;
this.aOps[X86.OPCODE.BOUND] = X86OpXX.opBOUND;
this.aOps[X86.OPCODE.ARPL] = X86Help.opHelpInvalid;
this.aOps[X86.OPCODE.FS] = X86Help.opHelpInvalid;
this.aOps[X86.OPCODE.GS] = X86Help.opHelpInvalid;
this.aOps[X86.OPCODE.OS] = X86Help.opHelpInvalid;
this.aOps[X86.OPCODE.AS] = X86Help.opHelpInvalid;
this.aOps[X86.OPCODE.PUSH16] = X86OpXX.opPUSH16;
this.aOps[X86.OPCODE.IMUL16] = X86OpXX.opIMUL16;
this.aOps[X86.OPCODE.PUSH8] = X86OpXX.opPUSH8;
this.aOps[X86.OPCODE.IMUL8] = X86OpXX.opIMUL8;
this.aOps[X86.OPCODE.INSB] = X86OpXX.opINSb;
this.aOps[X86.OPCODE.INSW] = X86OpXX.opINSw;
this.aOps[X86.OPCODE.OUTSB] = X86OpXX.opOUTSb;
this.aOps[X86.OPCODE.OUTSW] = X86OpXX.opOUTSw;
this.aOps[0xC0] = X86OpXX.opGrp2bi;
this.aOps[0xC1] = X86OpXX.opGrp2wi;
this.aOps[X86.OPCODE.ENTER] = X86OpXX.opENTER;
this.aOps[X86.OPCODE.LEAVE] = X86OpXX.opLEAVE;
this.aOps[0xF1] = X86OpXX.opINT1;
this.aOpGrp4b[0x07] = X86Grps.opGrpInvalid;
this.aOpGrp4w[0x07] = X86Grps.opGrpInvalid;
this.aOps[0x0F] = X86.opInvalid;
this.aOps[X86.OPCODE.PUSHA] = X86.opPUSHA;
this.aOps[X86.OPCODE.POPA] = X86.opPOPA;
this.aOps[X86.OPCODE.BOUND] = X86.opBOUND;
this.aOps[X86.OPCODE.ARPL] = X86.opInvalid;
this.aOps[X86.OPCODE.FS] = X86.opInvalid;
this.aOps[X86.OPCODE.GS] = X86.opInvalid;
this.aOps[X86.OPCODE.OS] = X86.opInvalid;
this.aOps[X86.OPCODE.AS] = X86.opInvalid;
this.aOps[X86.OPCODE.PUSH16] = X86.opPUSH16;
this.aOps[X86.OPCODE.IMUL16] = X86.opIMUL16;
this.aOps[X86.OPCODE.PUSH8] = X86.opPUSH8;
this.aOps[X86.OPCODE.IMUL8] = X86.opIMUL8;
this.aOps[X86.OPCODE.INSB] = X86.opINSb;
this.aOps[X86.OPCODE.INSW] = X86.opINSw;
this.aOps[X86.OPCODE.OUTSB] = X86.opOUTSb;
this.aOps[X86.OPCODE.OUTSW] = X86.opOUTSw;
this.aOps[0xC0] = X86.opGrp2bi;
this.aOps[0xC1] = X86.opGrp2wi;
this.aOps[X86.OPCODE.ENTER] = X86.opENTER;
this.aOps[X86.OPCODE.LEAVE] = X86.opLEAVE;
this.aOps[0xF1] = X86.opINT1;
this.aOpGrp4b[0x07] = X86.fnGRPInvalid;
this.aOpGrp4w[0x07] = X86.fnGRPInvalid;
if (this.model >= X86.MODEL_80286) {
@ -756,19 +755,23 @@ X86CPU.prototype.initProcessor = function()
this.OPFLAG_NOINTR8086 = 0; // used with instructions that should *not* set NOINTR on an 80286 (eg, non-SS segment loads)
this.aOps0F = X86Op0F.aOps0F;
this.aOps[0x0F] = X86OpXX.op0F;
this.aOps[X86.OPCODE.ARPL] = X86OpXX.opARPL;
this.aOps[X86.OPCODE.PUSHSP] = X86OpXX.opPUSHSP;
this.aOps0F = X86.aOps0F;
this.aOps[0x0F] = X86.op0F;
this.aOps[X86.OPCODE.ARPL] = X86.opARPL;
this.aOps[X86.OPCODE.PUSHSP] = X86.opPUSHSP;
if (I386 && this.model >= X86.MODEL_80386) {
this.aOps[X86.OPCODE.FS] = X86OpXX.opFS;
this.aOps[X86.OPCODE.GS] = X86OpXX.opGS;
this.aOps[X86.OPCODE.OS] = X86OpXX.opOS;
this.aOps[X86.OPCODE.AS] = X86OpXX.opAS;
this.aOps0F = X86Op0F.aOps0F.slice();
this.aOps0F[0x20] = X86Op0F.opMOVrcr;
this.aOps0F[0x22] = X86Op0F.opMOVcrr;
this.aOps[X86.OPCODE.FS] = X86.opFS;
this.aOps[X86.OPCODE.GS] = X86.opGS;
this.aOps[X86.OPCODE.OS] = X86.opOS;
this.aOps[X86.OPCODE.AS] = X86.opAS;
this.aOps0F = X86.aOps0F.slice();
this.aOps0F[0x20] = X86.opMOVrcr;
this.aOps0F[0x22] = X86.opMOVcrr;
this.aOps = this.aOps.concat(this.aOps);
for (var bOpcode in X86.aOpsD) {
this.aOps[parseInt(bOpcode, 10) + 256] = X86.aOpsD[bOpcode];
}
}
}
}
@ -896,7 +899,7 @@ X86CPU.prototype.resetRegs = function()
/*
* NOTE: Even though the 8086 doesn't have CR0 (aka MSW) and IDTR, we initialize them for ALL CPUs, so
* that functions like X86Help.opHelpINT() can use the same code for both. The 8086/8088 have no direct
* that functions like X86.fnINT() can use the same code for both. The 8086/8088 have no direct
* way of accessing or changing them, so this internal change should be perfectly safe for those processors.
*/
this.regCR0 = X86.CR0.MSW.ON;
@ -1075,6 +1078,7 @@ X86CPU.prototype.setAddrSize = function()
X86CPU.prototype.setDataSize = function()
{
this.opMem = this.aaOpMem[this.dataSize];
this.bOpcodeBias = (this.dataSize == 4? 256 : 0);
};
/**
@ -1091,6 +1095,7 @@ X86CPU.prototype.setSizes = function()
*/
this.addrSize = this.segCS.addrSize;
this.addrMask = this.segCS.addrMask;
/*
* It's also worth noting that instructions that implicitly use the stack also rely on STACK size,
* which is based on the BIG bit of the last descriptor loaded into SS; use the following segSS properties:
@ -1255,7 +1260,7 @@ X86CPU.prototype.setProtMode = function(fProt)
if (!fProt) {
this.printMessage("returning to real-mode");
}
this.aOpGrp6 = (fProt? X86Op0F.aOpGrp6Prot : X86Op0F.aOpGrp6Real);
this.aOpGrp6 = (fProt? X86.aOpGrp6Prot : X86.aOpGrp6Real);
this.segCS.updateMode(fProt);
this.segDS.updateMode(fProt);
this.segSS.updateMode(fProt);
@ -1718,6 +1723,38 @@ X86CPU.prototype.setSP = function(off)
/**
* getCF()
*
* Notes regarding carry following a 32-bit addition:
*
* The following table summarizes bit 31 of dst, src, and result, along with the expected carry bit:
*
* dst src res carry
* --- --- --- -----
* 0 0 0 0 no
* 0 0 1 0 no (there must have been a carry out of bit 30, but it was "absorbed")
* 0 1 0 1 yes (there must have been a carry out of bit 30, but it was NOT "absorbed")
* 0 1 1 0 no
* 1 0 0 1 yes (same as the preceding "yes" case)
* 1 0 1 0 no
* 1 1 0 1 yes (since the addition of two ones must always produce a carry)
* 1 1 1 1 yes (since the addition of two ones must always produce a carry)
*
* So, we could use (dst ^ ((dst ^ src) & (src ^ res))) >>> 15 to shift the calculated carry bit (bit 31)
* into the conventional SIZE_WORD position (bit 16); eg:
*
* resultZeroCarry = ((resultZeroCarry >>> 16) | (resultZeroCarry & 0xffff)) | (((dst ^ ((dst ^ src) & (src ^ resultZeroCarry))) >>> 15) & SIZE_WORD);
*
* Essentially, wed be cramming all 32 result bits into the low 16 bits (which would effectively represent the
* zero flag), and then setting bit 16 to the effective carry flag. This transforms the zero and carry conditions
* for a DWORD computation into the corresponding conditions for a WORD computation. This would slow down 32-bit
* addition, but it would allow 8-bit and 16-bit addition to remain fast. Languages that support 64-bit values in
* conjunction with bit-wise operators can omit that one-line transformation, allowing us to set SIZE_WORD to a
* 33-bit value, but sadly, we cannot do that in JavaScript.
*
* Alternatively, we could store the src and dst operands into their own result variables (eg, resultSrc and resultDst)
* and compute carry lazily, but that would affect MUCH more existing code (eg, all code that currently inspects carry
* with a single bit test). I think the DWORD-to-WORD flag conversion for 32-bit instructions that modify zero
* and/or carry) is a more reasonable first step.
*
* @this {X86CPU}
* @return {number}
*/
@ -2013,6 +2050,32 @@ X86CPU.prototype.getPS = function()
return (this.regPS & ~X86.PS.INDIRECT) | (this.getCF() | this.getPF() | this.getAF() | this.getZF() | this.getSF() | this.getOF());
};
/**
* setMSW(w)
*
* Factored out of x86op0f.js, since both opLMSW and opLOADALL are capable of setting a new MSW.
* The caller is responsible for assessing the appropriate cycle cost.
*
* @this {X86CPU}
* @param {number} w
*/
X86CPU.prototype.setMSW = function(w)
{
/*
* This instruction is always allowed to set MSW.PE, but it cannot clear MSW.PE once set;
* therefore, we always OR the previous value of MSW.PE into the new value before loading.
*/
w |= (this.regCR0 & X86.CR0.MSW.PE) | X86.CR0.MSW.ON;
this.regCR0 = (this.regCR0 & ~X86.CR0.MSW.MASK) | (w & X86.CR0.MSW.MASK);
/*
* Since the 80286 cannot return to real-mode via this instruction, the only transition we
* must worry about is to protected-mode. And don't worry, there's no harm calling setProtMode()
* if the CPU is already in protected-mode (we could certainly optimize the call out in that
* case, but this instruction isn't used frequently enough to warrant it).
*/
if (this.regCR0 & X86.CR0.MSW.PE) this.setProtMode(true);
};
/**
* setPS(regPS)
*
@ -2598,9 +2661,9 @@ X86CPU.prototype.getBytePrefetch = function(addr)
};
/**
* getWordPrefetch(addr)
* getShortPrefetch(addr)
*
* Return the next word from the prefetch queue. There are 3 cases to consider:
* Return the next short from the prefetch queue. There are 3 cases to consider:
*
* 1) Both bytes have been prefetched; no bytes need be fetched from memory
* 2) Only the low byte has been prefetched; the high byte must be fetched from memory
@ -2611,11 +2674,38 @@ X86CPU.prototype.getBytePrefetch = function(addr)
*
* @this {X86CPU}
* @param {number} addr is a physical (non-segmented) address
* @return {number} word (16-bit) value at that address
* @return {number} short (16-bit) value at that address
*/
X86CPU.prototype.getShortPrefetch = function(addr)
{
return this.getBytePrefetch(addr) | (this.getBytePrefetch(addr + 1) << 8);
};
/**
* getLongPrefetch(addr)
*
* Return the next long from the prefetch queue. Similar to getShortPrefetch(), we take the
* easy way out and call getShortPrefetch() twice.
*
* @this {X86CPU}
* @param {number} addr is a physical (non-segmented) address
* @return {number} long (32-bit) value at that address
*/
X86CPU.prototype.getLongPrefetch = function(addr)
{
return this.getShortPrefetch(addr) | (this.getShortPrefetch(addr + 2) << 16);
};
/**
* getWordPrefetch(addr)
*
* @this {X86CPU}
* @param {number} addr is a physical (non-segmented) address
* @return {number} short (16-bit) or long (32-bit value as appropriate
*/
X86CPU.prototype.getWordPrefetch = function(addr)
{
return this.getBytePrefetch(addr) | (this.getBytePrefetch(addr + 1) << 8);
return (I386 && this.addrSize == 4? this.getLongPrefetch(addr) : this.getShortPrefetch(addr));
};
/**
@ -2716,6 +2806,46 @@ X86CPU.prototype.getIPDisp = function()
return w & (I386? this.addrMask : 0xffff);
};
/**
* getIPShort()
*
* @this {X86CPU}
* @return {number} short at the current IP; IP advanced by 2
*/
X86CPU.prototype.getIPShort = function()
{
var w = (PREFETCH? this.getShortPrefetch(this.regLIP) : this.getShort(this.regLIP));
if (BACKTRACK) {
this.bus.updateBackTrackCode(this.regLIP, this.backTrack.btiMemLo);
this.bus.updateBackTrackCode(this.regLIP + 1, this.backTrack.btiMemHi);
}
this.regLIP += 2;
if (this.regLIP > this.regLIPLimit) {
this.setIP(this.regLIP - this.segCS.base);
}
return w;
};
/**
* getIPLong()
*
* @this {X86CPU}
* @return {number} long at the current IP; IP advanced by 4
*/
X86CPU.prototype.getIPLong = function()
{
var l = (PREFETCH? this.getLongPrefetch(this.regLIP) : this.getLong(this.regLIP));
if (BACKTRACK) {
this.bus.updateBackTrackCode(this.regLIP, this.backTrack.btiMemLo);
this.bus.updateBackTrackCode(this.regLIP + 1, this.backTrack.btiMemHi);
}
this.regLIP += 4;
if (this.regLIP > this.regLIPLimit) {
this.setIP(this.regLIP - this.segCS.base);
}
return l;
};
/**
* getIPWord()
*
@ -2868,14 +2998,14 @@ X86CPU.prototype.checkINTR = function()
this.intFlags &= ~X86.INTFLAG.INTR;
if (nIDT >= 0) {
this.intFlags &= ~X86.INTFLAG.HALT;
X86Help.opHelpINT.call(this, nIDT, null, 11);
X86.fnINT.call(this, nIDT, null, 11);
return true;
}
}
}
else if ((this.intFlags & X86.INTFLAG.TRAP)) {
this.intFlags &= ~X86.INTFLAG.TRAP;
X86Help.opHelpINT.call(this, X86.EXCEPTION.TRAP, null, 11);
X86.fnINT.call(this, X86.EXCEPTION.TRAP, null, 11);
return true;
}
}
@ -3162,7 +3292,7 @@ X86CPU.prototype.stepCPU = function(nMinCycles)
this.nSnapCycles = this.nStepCycles;
}
this.aOps[this.getIPByte()].call(this);
this.aOps[this.getIPByte() + (I386? this.bOpcodeBias : 0)].call(this);
if (PREFETCH) {
var nSpareCycles = (this.nSnapCycles - this.nStepCycles) - this.nBusCycles;
@ -3217,10 +3347,6 @@ if (typeof APP_PCJS !== 'undefined') {
APP_PCJS.X86 = X86;
APP_PCJS.X86.X86CPU = X86CPU;
APP_PCJS.X86.X86Seg = X86Seg;
APP_PCJS.X86.X86Grps = X86Grps;
APP_PCJS.X86.X86Help = X86Help;
APP_PCJS.X86.X86Op0F = X86Op0F;
APP_PCJS.X86.X86OpXX = X86OpXX;
}
if (typeof module !== 'undefined') module.exports = X86CPU;

2444
modules/pcjs/lib/x86func.js Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,789 +0,0 @@
/**
* @fileoverview Implements PCjs 8086 opcode helpers.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2012-Sep-05
*
* Copyright © 2012-2015 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <http://pcjs.org/>.
*
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some PCjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* PCjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
if (typeof module !== 'undefined') {
var X86 = require("./x86");
var Messages = require("./messages");
}
var X86Help = {
/**
* @this {X86CPU}
* @param {number} dst (current value, ignored)
* @param {number} src (new value)
* @return {number} dst (updated value, from src)
*/
opHelpMOV: function(dst, src) {
this.nStepCycles -= (this.regEAWrite < 0? (this.regEA < 0? this.CYCLES.nOpCyclesMovRR : this.CYCLES.nOpCyclesMovRM) : this.CYCLES.nOpCyclesMovMR);
return src;
},
/**
* @this {X86CPU}
* @param {number} dst (current value, ignored)
* @param {number} src (new value)
* @return {number} dst (src is overridden, replaced with regMD16, as specified by opMOVwsr())
*/
opHelpMOVMD16: function(dst, src) {
return X86Help.opHelpMOV.call(this, dst, this.regMD16);
},
/**
* @this {X86CPU}
* @param {number} dst
* @param {number} src
* @return {number}
*/
opHelpTESTb: function(dst, src) {
this.resultZeroCarry = this.resultParitySign = this.resultAuxOverflow = dst & src;
this.resultSize = X86.RESULT.SIZE_BYTE;
this.nStepCycles -= (this.regEAWrite < 0? (this.regEA < 0? this.CYCLES.nOpCyclesTestRR : this.CYCLES.nOpCyclesTestRM) : this.CYCLES.nOpCyclesTestRM);
this.opFlags |= X86.OPFLAG.NOWRITE;
return dst;
},
/**
* @this {X86CPU}
* @param {number} dst
* @param {number} src
* @return {number}
*/
opHelpTESTw: function(dst, src) {
this.resultZeroCarry = this.resultParitySign = this.resultAuxOverflow = dst & src;
this.resultSize = X86.RESULT.SIZE_WORD;
this.nStepCycles -= (this.regEAWrite < 0? (this.regEA < 0? this.CYCLES.nOpCyclesTestRR : this.CYCLES.nOpCyclesTestRM) : this.CYCLES.nOpCyclesTestRM);
this.opFlags |= X86.OPFLAG.NOWRITE;
return dst;
},
/**
* @this {X86CPU}
* @param {number} dst
* @param {number} src
* @return {number}
*
* 80286_and_80287_Programmers_Reference_Manual_1987.pdf, p.B-44 (p.254) notes that:
*
* "The low 16 bits of the product of a 16-bit signed multiply are the same as those of an
* unsigned multiply. The three operand IMUL instruction can be used for unsigned operands as well."
*
* However, we still sign-extend the operands before multiplying, making it easier to range-check the result.
*
* (80186/80188 and up)
*/
opHelpIMUL8: function(dst, src) {
var result = ((src << 16) >> 16) * ((this.getIPByte() << 24) >> 24);
this.resultZeroCarry = this.resultAuxOverflow = this.resultParitySign = result;
this.resultSize = X86.RESULT.SIZE_BYTE;
/*
* TODO: Look into a more efficient way of setting/synchronizing CF and OF; this code works,
* but it somewhat defeats the purpose of the indirect result variables that we've set above.
*/
if (result > 32767 || result < -32768) {
this.setCF(); this.setOF();
} else {
this.clearCF(); this.clearOF();
}
result &= 0xffff;
if (DEBUG && DEBUGGER) this.traceLog('IMUL8', dst, src, null, this.getPS(), result);
/*
* NOTE: These are the cycle counts for the 80286; the 80186/80188 have slightly different values (ranges):
* 22-25 and 29-32 instead of 21 and 24, respectively. However, accurate cycle counts for the 80186/80188 is
* not super-critical. TODO: Fix this someday.
*/
this.nStepCycles -= (this.regEA < 0? 21 : 24);
return result;
},
/**
* @this {X86CPU}
* @param {number} dst
* @param {number} src
* @return {number}
*
* 80286_and_80287_Programmers_Reference_Manual_1987.pdf, p.B-44 (p.254) notes that:
*
* "The low 16 bits of the product of a 16-bit signed multiply are the same as those of an
* unsigned multiply. The three operand IMUL instruction can be used for unsigned operands as well."
*
* However, we still sign-extend the operands before multiplying, making it easier to range-check the result.
*
* (80186/80188 and up)
*/
opHelpIMUL16: function(dst, src) {
var result = ((src << 16) >> 16) * ((this.getIPWord() << 16) >> 16);
this.resultZeroCarry = this.resultAuxOverflow = this.resultParitySign = result;
this.resultSize = X86.RESULT.SIZE_WORD;
/*
* TODO: Look into a more efficient way of setting/synchronizing CF and OF; this code works,
* but it somewhat defeats the purpose of the indirect result variables that we've set above.
*/
if (result > 32767 || result < -32768) {
this.setCF(); this.setOF();
} else {
this.clearCF(); this.clearOF();
}
result &= 0xffff;
if (DEBUG && DEBUGGER) this.traceLog('IMUL16', dst, src, null, this.getPS(), result);
/*
* NOTE: These are the cycle counts for the 80286; the 80186/80188 have slightly different values (ranges):
* 22-25 and 29-32 instead of 21 and 24, respectively. However, accurate cycle counts for the 80186/80188 is
* not super-critical. TODO: Fix this someday.
*/
this.nStepCycles -= (this.regEA < 0? 21 : 24);
return result;
},
/**
* @this {X86CPU}
* @param {number} dst
* @param {number} src
* @return {number} dst unchanged
*/
opHelpESC: function(dst, src) {
return dst;
},
/**
* @this {X86CPU}
* @param {number} dst
* @param {number} src
* @return {number}
*/
opHelpLEA: function(dst, src) {
if (this.regEA < 0) {
/*
* TODO: After reading http://www.os2museum.com/wp/undocumented-8086-opcodes/, it seems that this
* form of LEA (eg, "LEA AX,DX") simply returns the last calculated EA. Since we always reset regEA
* at the start of a new instruction, we would need to preserve the previous EA if we want to mimic
* that (undocumented) behavior.
*
* And for completeness, we would have to extend EA tracking beyond the usual ModRM instructions
* (eg, XLAT, instructions that modify the stack pointer, and string instructions). Anything else?
*/
X86Help.opHelpUndefined.call(this);
return dst;
}
this.nStepCycles -= this.CYCLES.nOpCyclesLEA;
return this.regEA;
},
/**
* @this {X86CPU}
* @param {number} dst
* @param {number} src
* @return {number}
*/
opHelpLDS: function(dst, src) {
if (this.regEA < 0) {
X86Help.opHelpUndefined.call(this);
return dst;
}
this.setDS(this.getShort(this.regEA + 2));
this.nStepCycles -= this.CYCLES.nOpCyclesLS;
return src;
},
/**
* @this {X86CPU}
* @param {number} dst
* @param {number} src
* @return {number}
*/
opHelpLES: function(dst, src) {
if (this.regEA < 0) {
X86Help.opHelpUndefined.call(this);
return dst;
}
this.setES(this.getShort(this.regEA + 2));
this.nStepCycles -= this.CYCLES.nOpCyclesLS;
return src;
},
/**
* @this {X86CPU}
* @param {number} dst
* @param {number} src
* @return {number}
*/
opHelpBOUND: function(dst, src) {
if (this.regEA < 0) {
/*
* Generate UD_FAULT (INT 0x06: Invalid Opcode) if src is not a memory operand.
*/
X86Help.opHelpInvalid.call(this);
return dst;
}
/*
* Note that BOUND performs signed comparisons, so we must transform all arguments into signed values.
*/
var wIndex = (dst << 16) >> 16;
var wLower = (this.getShort(this.regEA) << 16) >> 16;
var wUpper = (this.getShort(this.regEA + 2) << 16) >> 16;
this.nStepCycles -= this.CYCLES.nOpCyclesBound;
if (wIndex < wLower || wIndex > wUpper) {
/*
* The INT 0x05 handler must be called with CS:IP pointing to the BOUND instruction.
*
* TODO: Determine the cycle cost when a BOUND exception is triggered, over and above nOpCyclesBound.
*/
this.setIP(this.opLIP - this.segCS.base);
X86Help.opHelpINT.call(this, X86.EXCEPTION.BOUND_ERR, null, 0);
}
this.opFlags |= X86.OPFLAG.NOWRITE;
return dst;
},
/**
* @this {X86CPU}
* @param {number} dst
* @param {number} src
* @return {number}
*/
opHelpARPL: function(dst, src) {
this.nStepCycles -= (10 + (this.regEA < 0? 0 : 1));
if ((dst & X86.SEL.RPL) < (src & X86.SEL.RPL)) {
dst = (dst & ~X86.SEL.RPL) | (src & X86.SEL.RPL);
this.setZF();
return dst;
}
this.clearZF();
return dst;
},
/**
* @this {X86CPU}
* @param {number} dst
* @param {number} src
* @return {number}
*/
opHelpLAR: function(dst, src) {
this.nStepCycles -= (14 + (this.regEA < 0? 0 : 2));
/*
* Currently, segVER.load() will return an error only if the selector is beyond the bounds of the
* descriptor table or the descriptor is not for a segment.
*
* TODO: This instruction's 80286 documentation does not discuss conforming code segments; determine
* if we need a special check for them.
*/
if (this.segVER.load(src, true) != X86.ADDR_INVALID) {
if (this.segVER.dpl >= this.segCS.cpl && this.segVER.dpl >= (src & X86.SEL.RPL)) {
this.setZF();
return this.segVER.acc & X86.DESC.ACC.MASK;
}
}
this.clearZF();
return dst;
},
/**
* @this {X86CPU}
* @param {number} dst
* @param {number} src (the selector)
* @return {number}
*/
opHelpLSL: function(dst, src) {
/*
* TODO: Is this an invalid operation if regEAWrite is set? dst is required to be a register.
*/
this.nStepCycles -= (14 + (this.regEA < 0? 0 : 2));
/*
* Currently, segVER.load() will return an error only if the selector is beyond the bounds of the
* descriptor table or the descriptor is not for a segment.
*
* TODO: LSL is explicitly documented as ALSO requiring a non-null selector, so we check X86.SEL.MASK;
* are there any other instructions that were, um, less explicit but also require a non-null selector?
*/
if ((src & X86.SEL.MASK) && this.segVER.load(src, true) != X86.ADDR_INVALID) {
var fConforming = ((this.segVER.acc & X86.DESC.ACC.TYPE.CODE_CONFORMING) == X86.DESC.ACC.TYPE.CODE_CONFORMING);
if ((fConforming || this.segVER.dpl >= this.segCS.cpl) && this.segVER.dpl >= (src & X86.SEL.RPL)) {
this.setZF();
return this.segVER.limit;
}
}
this.clearZF();
return dst;
},
/**
* opHelpXCHGrb(dst, src)
*
* If an instruction like "XCHG AL,AH" was a traditional "op dst,src" instruction, dst would contain AL,
* src would contain AH, and we would return src, which the caller would then store in AL, and we'd be done.
*
* However, that's only half of what XCHG does, so THIS function must perform the other half; in the previous
* example, that entails storing AL (dst) into AH (src).
*
* BACKTRACK support is incomplete without also passing bti values as parameters, because the caller will
* store btiAH in btiAL, but the original btiAL will be lost. Similarly, if src is a memory operand, the
* caller will store btiEALo in btiAL, but again, the original btiAL will be lost.
*
* BACKTRACK support for memory operands could be fixed by decoding the dst register in order to determine the
* corresponding bti and then temporarily storing it in btiEALo around the setEAByte() call below. Register-only
* XCHGs would require a more extensive hack. For now, I'm going to live with one-way BACKTRACK support here.
*
* TODO: Implement full BACKTRACK support for XCHG instructions.
*
* @this {X86CPU}
* @param {number} dst
* @param {number} src
* @return {number}
*/
opHelpXCHGrb: function(dst, src) {
if (this.regEA < 0) {
/*
* Decode which register was src
*/
switch (this.bModRM & 0x7) {
case 0x0: // AL
this.regEAX = (this.regEAX & ~0xff) | dst;
break;
case 0x1: // CL
this.regECX = (this.regECX & ~0xff) | dst;
break;
case 0x2: // DL
this.regEDX = (this.regEDX & ~0xff) | dst;
break;
case 0x3: // BL
this.regEBX = (this.regEBX & ~0xff) | dst;
break;
case 0x4: // AH
this.regEAX = (this.regEAX & 0xff) | (dst << 8);
break;
case 0x5: // CH
this.regECX = (this.regECX & 0xff) | (dst << 8);
break;
case 0x6: // DH
this.regEDX = (this.regEDX & 0xff) | (dst << 8);
break;
case 0x7: // BH
this.regEBX = (this.regEBX & 0xff) | (dst << 8);
break;
default:
break; // there IS no other case, but JavaScript inspections don't know that
}
this.nStepCycles -= this.CYCLES.nOpCyclesXchgRR;
} else {
/*
* This is a case where the ModRM decoder that's calling us didn't know it should have called modEAByte()
* instead of getEAByte(), so we compensate by updating regEAWrite. However, setEAByte() has since been
* changed to revalidate the write using segEA:offEA, so updating regEAWrite here isn't strictly necessary.
*/
this.regEAWrite = this.regEA;
this.setEAByte(dst);
this.nStepCycles -= this.CYCLES.nOpCyclesXchgRM;
}
return src;
},
/**
* opHelpXCHGrw(dst, src)
*
* If an instruction like "XCHG AX,DX" was a traditional "op dst,src" instruction, dst would contain AX,
* src would contain DX, and we would return src, which the caller would then store in AX, and we'd be done.
*
* However, that's only half of what XCHG does, so THIS function must perform the other half; in the previous
* example, that entails storing AX (dst) into DX (src).
*
* TODO: Implement full BACKTRACK support for XCHG instructions (see opHelpXCHGrb comments).
*
* @this {X86CPU}
* @param {number} dst
* @param {number} src
* @return {number}
*/
opHelpXCHGrw: function(dst, src) {
if (this.regEA < 0) {
/*
* Decode which register was src
*/
switch (this.bModRM & 0x7) {
case 0x0: // AX
this.regEAX = dst;
break;
case 0x1: // CX
this.regECX = dst;
break;
case 0x2: // DX
this.regEDX = dst;
break;
case 0x3: // BX
this.regEBX = dst;
break;
case 0x4: // SP
this.setSP(dst);
break;
case 0x5: // BP
this.regEBP = dst;
break;
case 0x6: // SI
this.regESI = dst;
break;
case 0x7: // DI
this.regEDI = dst;
break;
default:
break; // there IS no other case, but JavaScript inspections don't know that
}
this.nStepCycles -= this.CYCLES.nOpCyclesXchgRR;
} else {
/*
* This is a case where the ModRM decoder that's calling us didn't know it should have called modEAWord()
* instead of getEAWord(), so we compensate by updating regEAWrite. However, setEAWord() has since been
* changed to revalidate the write using segEA:offEA, so updating regEAWrite here isn't strictly necessary.
*/
this.regEAWrite = this.regEA;
this.setEAWord(dst);
this.nStepCycles -= this.CYCLES.nOpCyclesXchgRM;
}
return src;
},
/**
* opHelpLMSW(w)
*
* Factored out of x86op0f.js, since both opLMSW and opLOADALL are capable of loading a new MSW.
* The caller is responsible for assessing the appropriate cycle cost.
*
* @this {X86CPU}
* @param {number} w
*/
opHelpLMSW: function(w) {
/*
* This instruction is always allowed to set MSW.PE, but it cannot clear MSW.PE once set;
* therefore, we always OR the previous value of MSW.PE into the new value before loading.
*/
w |= (this.regCR0 & X86.CR0.MSW.PE) | X86.CR0.MSW.ON;
this.regCR0 = (this.regCR0 & ~X86.CR0.MSW.MASK) | (w & X86.CR0.MSW.MASK);
/*
* Since the 80286 cannot return to real-mode via this instruction, the only transition we
* must worry about is to protected-mode. And don't worry, there's no harm calling setProtMode()
* if the CPU is already in protected-mode (we could certainly optimize the call out in that
* case, but this instruction isn't used frequently enough to warrant it).
*/
if (this.regCR0 & X86.CR0.MSW.PE) this.setProtMode(true);
},
/**
* opHelpLCR0(l)
*
* This called on behalf of 80386 opcodes only (ie, MOV CR0,reg).
*
* TODO: Determine which CR0 bits, if any, cannot be modified by MOV CR0,reg.
*
* @this {X86CPU}
* @param {number} l
*/
opHelpLCR0: function(l) {
this.regCR0 = l;
this.setProtMode(!!(this.regCR0 & X86.CR0.MSW.PE));
},
/**
* opHelpCALLF(off, sel)
*
* For protected-mode, this function must attempt to load the new code segment first, because if the new segment
* requires a change in privilege level, the return address must be pushed on the NEW stack, not the current stack.
*
* @this {X86CPU}
* @param {number} off
* @param {number} sel
*/
opHelpCALLF: function(off, sel) {
var regCS = this.getCS();
var regEIP = this.getIP();
if (this.setCSIP(off, sel, true) != null) {
this.pushWord(regCS);
this.pushWord(regEIP);
}
},
/**
* opHelpRETF(n)
*
* For protected-mode, this function must be prepared to pop any arguments off the current stack AND
* whatever stack we may have switched to (setCSIP() returns true only when a stack switch has occurred).
*
* @this {X86CPU}
* @param {number} n
*/
opHelpRETF: function(n) {
var regEIP = this.popWord();
var regCS = this.popWord();
n <<= (this.dataSize >> 2);
if (n) this.setSP(this.getSP() + n); // TODO: optimize
if (this.setCSIP(regEIP, regCS, false)) {
if (n) this.setSP(this.getSP() + n); // TODO: optimize
/*
* As per Intel documentation: "If any of [the DS or ES] registers refer to segments whose DPL is
* less than the new CPL (excluding conforming code segments), the segment register is loaded with
* the null selector."
*
* TODO: I'm not clear on whether a conforming code segment must also be marked readable, so I'm playing
* it safe and using CODE_CONFORMING instead of CODE_CONFORMING_READABLE. Also, for the record, I've not
* seen this situation occur in OS/2 1.0 yet.
*/
if ((this.segDS.sel & X86.SEL.MASK) && this.segDS.dpl < this.segCS.cpl && (this.segDS.acc & X86.DESC.ACC.TYPE.CODE_CONFORMING) != X86.DESC.ACC.TYPE.CODE_CONFORMING) {
this.assert(false); // I'm not asserting this is bad, I just want to see it in action
this.segDS.load(0);
}
if ((this.segES.sel & X86.SEL.MASK) && this.segES.dpl < this.segCS.cpl && (this.segES.acc & X86.DESC.ACC.TYPE.CODE_CONFORMING) != X86.DESC.ACC.TYPE.CODE_CONFORMING) {
this.assert(false); // I'm not asserting this is bad, I just want to see it in action
this.segES.load(0);
}
}
if (n == 2 && this.cIntReturn) this.checkIntReturn(this.regLIP);
},
/**
* opHelpINT(nIDT, nError, nCycles)
*
* NOTE: We no longer use setCSIP(), because it always loads the new CS using segCS.load(), which
* only knows how to load GDT and LDT descriptors, whereas interrupts must use setCS.loadIDT(), which
* deals exclusively with IDT descriptors.
*
* This means we must take care to replicate critical features of setCSIP(); eg, setting segCS.fCall before
* calling loadIDT(), updating LIP, and flushing the prefetch queue.
*
* @this {X86CPU}
* @param {number} nIDT
* @param {number|null|undefined} nError
* @param {number} nCycles (in addition to the default of nOpCyclesInt)
*/
opHelpINT: function(nIDT, nError, nCycles) {
/*
* TODO: We assess the cycle cost up front, because otherwise, if loadIDT() fails, no cost may be assessed.
*/
this.nStepCycles -= this.CYCLES.nOpCyclesInt + nCycles;
this.segCS.fCall = true;
var regPS = this.getPS();
var regCS = this.getCS();
var regEIP = this.getIP();
var addr = this.segCS.loadIDT(nIDT);
if (addr != X86.ADDR_INVALID) {
this.regLIP = addr;
if (PREFETCH) this.flushPrefetch(this.regLIP);
this.pushWord(regPS);
this.pushWord(regCS);
this.pushWord(regEIP);
if (nError != null) this.pushWord(nError);
this.nFault = -1;
}
},
/**
* opHelpIRET()
*
* @this {X86CPU}
*/
opHelpIRET: function() {
/*
* TODO: We assess a fixed cycle cost up front, because at the moment, switchTSS() doesn't assess anything.
*/
this.nStepCycles -= this.CYCLES.nOpCyclesIRet;
if (this.regCR0 & X86.CR0.MSW.PE) {
if (this.regPS & X86.PS.NT) {
var addrNew = this.segTSS.base;
var sel = this.getShort(addrNew + X86.TSS.PREV_TSS);
X86Seg.switchTSS.call(this.segCS, sel, false);
return;
}
}
var cpl = this.segCS.cpl;
var regEIP = this.popWord();
var regCS = this.popWord();
var regPS = this.popWord();
if (this.setCSIP(regEIP, regCS, false) != null) {
this.setPS(regPS, cpl);
if (this.cIntReturn) this.checkIntReturn(this.regLIP);
}
},
/**
* opHelpDIVOverflow()
*
* @this {X86CPU}
*/
opHelpDIVOverflow: function() {
this.setIP(this.opLIP - this.segCS.base);
/*
* TODO: Determine the proper cycle cost.
*/
X86Help.opHelpINT.call(this, X86.EXCEPTION.DIV_ERR, null, 2);
},
/**
* opHelpFault(nFault, nError, fHalt)
*
* Helper to dispatch faults.
*
* @this {X86CPU}
* @param {number} nFault
* @param {number} [nError]
* @param {boolean} [fHalt] will halt the CPU if true *and* a Debugger is loaded
*/
opHelpFault: function(nFault, nError, fHalt)
{
if (!this.aFlags.fComplete) {
this.printMessage("Fault " + str.toHexByte(nFault) + " blocked by Debugger", Messages.WARN);
this.setIP(this.opLIP - this.segCS.base);
return;
}
var fDispatch = false;
if (this.model >= X86.MODEL_80186) {
if (this.nFault < 0) {
/*
* Single-fault (error code is passed through, and the responsible instruction is restartable)
*/
this.setIP(this.opLIP - this.segCS.base);
fDispatch = true;
} 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;
fDispatch = true;
} else {
/*
* Triple-fault (usually referred to in Intel literature as a "shutdown", but at least on the 80286,
* it's actually a "reset")
*/
X86Help.opHelpFaultMessage.call(this, -1, 0, fHalt);
this.resetRegs();
return;
}
}
if (X86Help.opHelpFaultMessage.call(this, nFault, nError, fHalt)) {
fDispatch = false;
}
if (fDispatch) X86Help.opHelpINT.call(this, this.nFault = nFault, nError, 0);
/*
* 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 ignore.
*/
this.opFlags &= ~(X86.OPFLAG.NOREAD | X86.OPFLAG.NOWRITE);
},
/**
* opHelpFaultMessage()
*
* Aside from giving the Debugger an opportunity to report every fault, this also gives us the ability to
* halt exception processing in tracks: return true to prevent the fault handler from being dispatched.
*
* At the moment, the only Debugger control you have over fault interception is setting MESSAGE.FAULT, which
* will display faults as they occur, and MESSAGE.HALT, which will halt after any Debugger message, including
* MESSAGE.FAULT. If you want execution to continue after halting, clear MESSAGE.FAULT and/or MESSAGE.HALT,
* or single-step over the offending instruction, which will allow the fault to be dispatched.
*
* @this {X86CPU}
* @param {number} nFault
* @param {number} [nError]
* @param {boolean} [fHalt] true if the CPU should always be halted, false if "it depends"
* @return {boolean|undefined} true to block the fault (often desirable when fHalt is true), otherwise dispatch it
*/
opHelpFaultMessage: function(nFault, nError, fHalt)
{
var bitsMessage = Messages.FAULT;
var bOpcode = this.bus.getByteDirect(this.regLIP);
/*
* OS/2 1.0 uses an INT3 (0xCC) opcode in conjunction with an invalid IDT to trigger a triple-fault
* reset and return to real-mode, and these resets happen quite frequently during boot; for example,
* OS/2 startup messages are displayed using a series of INT 0x10 BIOS calls for each character, and
* each series of BIOS calls requires a round-trip mode switch.
*
* Since we really only want to halt on "bad" faults, not "good" (ie, intentional) faults, we take
* advantage of the fact that all 3 faults comprising the triple-fault point to an INT3 (0xCC) opcode,
* and so whenever we see that opcode, we ignore the caller's fHalt flag, and suppress FAULT messages
* unless CPU messages are also enabled.
*
* When a triple fault shows up, nFault is -1; it displays as "ff" because we display nFault as a byte.
*/
if (bOpcode == X86.OPCODE.INT3) {
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
* detect those faults by virtue of the LIP being in the range %0F0000 to %0FFFFF.
*/
if (this.regLIP >= 0x0F0000 && this.regLIP <= 0x0FFFFF) {
fHalt = false;
}
/*
* However, the foregoing notwithstanding, if MESSAGE.HALT is enabled along with all the other required
* MESSAGE bits, then we want to halt regardless.
*/
if (this.messageEnabled(bitsMessage | Messages.HALT)) {
fHalt = true;
}
if (this.messageEnabled(bitsMessage) || fHalt) {
var sMessage = (fHalt? '\n' : '') + "Fault " + str.toHexByte(nFault) + (nError != null? " (" + str.toHexWord(nError) + ")" : "") + " on opcode 0x" + str.toHexByte(bOpcode) + " at " + this.dbg.hexOffset(this.getIP(), this.getCS()) + " (%" + str.toHex(this.regLIP, 6) + ")";
var fRunning = this.aFlags.fRunning;
if (this.printMessage(sMessage, bitsMessage)) {
if (fHalt) {
/*
* By setting fHalt to fRunning (which is true while running but false while single-stepping),
* this allows a fault to be dispatched when you single-step over a faulting instruction; you can
* then continue single-stepping into the fault handler, or start running again.
*
* Note that we had to capture fRunning before calling printMessage(), because if MESSAGE.HALT
* is set, printMessage() will have already halted the CPU.
*/
fHalt = fRunning;
this.dbg.stopCPU();
}
} else {
/*
* If printMessage() returned false, then messageEnabled() must have returned false as well, which
* means that fHalt must be true. Which means we should shut the machine down.
*/
this.assert(fHalt);
this.notice(sMessage);
this.stopCPU();
}
}
return fHalt;
},
/**
* @this {X86CPU}
*/
opHelpInvalid: function() {
X86Help.opHelpFault.call(this, X86.EXCEPTION.UD_FAULT);
this.stopCPU();
},
/**
* @this {X86CPU}
*/
opHelpUndefined: function() {
this.setIP(this.opLIP - this.segCS.base);
this.setError("Undefined opcode 0x" + str.toHexByte(this.bus.getByteDirect(this.regLIP)) + " at 0x" + str.toHex(this.regLIP));
this.stopCPU();
}
};
if (typeof module !== 'undefined') module.exports = X86Help;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -36,7 +36,6 @@ if (typeof module !== 'undefined') {
var str = require("../../shared/lib/strlib");
var Messages = require("./messages");
var X86 = require("./x86");
var X86Help = require("./x86help");
}
/**
@ -190,7 +189,7 @@ X86Seg.loadProt = function loadProt(sel, fSuppress)
return this.loadDesc8(addrDesc, sel, fSuppress);
}
if (!fSuppress) {
X86Help.opHelpFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel);
X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel);
}
}
return X86.ADDR_INVALID;
@ -237,14 +236,14 @@ X86Seg.loadIDTProt = function loadIDTProt(nIDT)
if (addrDesc + 7 <= cpu.addrIDTLimit) {
return this.loadDesc8(addrDesc, nIDT) + cpu.regEIP;
}
X86Help.opHelpFault.call(cpu, X86.EXCEPTION.GP_FAULT, nIDT | X86.ERRCODE.IDT | X86.ERRCODE.EXT, true);
X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, nIDT | X86.ERRCODE.IDT | X86.ERRCODE.EXT, true);
return X86.ADDR_INVALID;
};
/**
* checkReadReal(off, cb, fSuppress)
*
* TODO: Invoke X86Help.opHelpFault.call(this.cpu, X86.EXCEPTION.GP_FAULT) if off is 0xffff and cb is 1;
* TODO: Invoke X86.fnFault.call(this.cpu, X86.EXCEPTION.GP_FAULT) if off is 0xffff and cb is 1;
* also, whether or not the opHelpFault() call should include an error code, since this is happening in real-mode.
*
* @this {X86Seg}
@ -261,7 +260,7 @@ X86Seg.checkReadReal = function checkReadReal(off, cb, fSuppress)
/**
* checkWriteReal(off, cb, fSuppress)
*
* TODO: Invoke X86Help.opHelpFault.call(this.cpu, X86.EXCEPTION.GP_FAULT) if off is 0xffff and cb is 1;
* TODO: Invoke X86.fnFault.call(this.cpu, X86.EXCEPTION.GP_FAULT) if off is 0xffff and cb is 1;
* also, whether or not the opHelpFault() call should include an error code, since this is happening in real-mode.
*
* @this {X86Seg}
@ -321,7 +320,7 @@ X86Seg.checkReadProtDown = function checkReadProtDown(off, cb, fSuppress)
X86Seg.checkReadProtDisallowed = function checkReadProtDisallowed(off, cb, fSuppress)
{
if (!fSuppress) {
X86Help.opHelpFault.call(this.cpu, X86.EXCEPTION.GP_FAULT, 0);
X86.fnFault.call(this.cpu, X86.EXCEPTION.GP_FAULT, 0);
}
return X86.ADDR_INVALID;
};
@ -372,7 +371,7 @@ X86Seg.checkWriteProtDown = function checkWriteProtDown(off, cb, fSuppress)
X86Seg.checkWriteProtDisallowed = function checkWriteProtDisallowed(off, cb, fSuppress)
{
if (!fSuppress) {
X86Help.opHelpFault.call(this.cpu, X86.EXCEPTION.GP_FAULT, 0);
X86.fnFault.call(this.cpu, X86.EXCEPTION.GP_FAULT, 0);
}
return X86.ADDR_INVALID;
};
@ -411,7 +410,7 @@ X86Seg.switchTSS = function switchTSS(selNew, fNest)
var selOld = cpu.segTSS.sel;
if (!fNest) {
if (cpu.segTSS.type != X86.DESC.ACC.TYPE.TSS_BUSY) {
X86Help.opHelpFault.call(cpu, X86.EXCEPTION.TS_FAULT, selNew, true);
X86.fnFault.call(cpu, X86.EXCEPTION.TS_FAULT, selNew, true);
return false;
}
cpu.setShort(cpu.segTSS.addrDesc + X86.DESC.ACC.OFFSET, (cpu.segTSS.acc & ~X86.DESC.ACC.TYPE.TSS_BUSY) | X86.DESC.ACC.TYPE.TSS);
@ -425,7 +424,7 @@ X86Seg.switchTSS = function switchTSS(selNew, fNest)
}
if (fNest) {
if (cpu.segTSS.type == X86.DESC.ACC.TYPE.TSS_BUSY) {
X86Help.opHelpFault.call(cpu, X86.EXCEPTION.GP_FAULT, selNew, true);
X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, selNew, true);
return false;
}
cpu.setShort(cpu.segTSS.addrDesc + X86.DESC.ACC.OFFSET, cpu.segTSS.acc |= X86.DESC.ACC.TYPE.TSS_BUSY);
@ -502,7 +501,7 @@ X86Seg.prototype.loadAcc = function(sel, fGDT)
return cpu.getShort(addrDesc + X86.DESC.ACC.OFFSET);
}
}
X86Help.opHelpFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel);
X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel);
return X86.DESC.ACC.INVALID;
};
@ -684,13 +683,13 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fSuppress)
return this.base;
}
cpu.assert(false);
if (!fSuppress) X86Help.opHelpFault.call(cpu, X86.EXCEPTION.GP_FAULT, nFaultError, true);
if (!fSuppress) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, nFaultError, true);
base = X86.ADDR_INVALID;
break;
}
else if (fGate !== false) {
cpu.assert(false);
if (!fSuppress) X86Help.opHelpFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel, true);
if (!fSuppress) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel, true);
base = X86.ADDR_INVALID;
break;
}
@ -719,7 +718,7 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fSuppress)
*
* Anyway, because of this, if acc is zero, we won't set fHalt on this GP_FAULT.
*/
if (!fSuppress) X86Help.opHelpFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel, !!acc);
if (!fSuppress) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel, !!acc);
base = X86.ADDR_INVALID;
break;
}
@ -727,14 +726,14 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fSuppress)
}
else if (this.id == X86Seg.ID.STACK) {
if (!selMasked || type < X86.DESC.ACC.TYPE.DATA_READONLY || (type & (X86.DESC.ACC.TYPE.CODE | X86.DESC.ACC.TYPE.READABLE)) == X86.DESC.ACC.TYPE.CODE) {
if (!fSuppress) X86Help.opHelpFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel, true);
if (!fSuppress) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel, true);
base = X86.ADDR_INVALID;
break;
}
}
else if (this.id == X86Seg.ID.TSS) {
if (!selMasked || type != X86.DESC.ACC.TYPE.TSS && type != X86.DESC.ACC.TYPE.TSS_BUSY) {
if (!fSuppress) X86Help.opHelpFault.call(cpu, X86.EXCEPTION.TS_FAULT, sel, true);
if (!fSuppress) X86.fnFault.call(cpu, X86.EXCEPTION.TS_FAULT, sel, true);
base = X86.ADDR_INVALID;
break;
}