Debugger changes for improved 32-bit support

This commit is contained in:
Jeff Parsons 2015-02-28 11:55:55 -08:00 committed by jeffpar
commit f50612e7ca
21 changed files with 3581 additions and 2949 deletions

View file

@ -68,20 +68,24 @@
* avoid display problems near the right edge. BASIC will let you choose a width SMALLER than
* 24 but not larger. So, while the video buffer supports a theoretical maximum of 32 rows x 32
* columns, the practical maximum is 25 rows x 24 columns; the last 4 rows of the video buffer
* are never used, and while content DOES scroll through the top 4 lines of the buffer, it should
* are never used, and while content DOES scroll through the top 3 lines of the buffer, it should
* never be assumed that you can see the top 3 lines.
*
* This is partially confirmed by the "C1P Character Graphics Reference Manual", p3, which says
* that the "the visible character field consists of 25 lines of 25 columns" and that the "first
* visible character in the upper left of the screen is accessed via address 53379," or 0xD083.
* However, they were wrong about both the number of columns and the first visible character.
*
* They actually meant 0xD085, because as mentioned earlier, the C1P indents every row by 5
* characters, not 3. Even so, the difference between 0xD365 (where the bottom line starts)
* and 0xD085 is 0x2E0, or 736. And 736 divided by 32 gives 23; add the bottom row, and that
* gives you 24 visible rows, not 25. Since we now have screenshots of a C1P monitor displaying
* 25 rows (courtesy of Stephan Mühlstrasser <stephan.muehlstrasser@web.de>), C1Pjs now assumes
* 25 rows, which means that only the first 3 lines are not visible, which necessarily puts
* the address of the first visible character at 0xD065.
* They probably meant 0xD085, because as mentioned earlier, the C1P indents every row by 5
* characters, not 3. But that's not correct either, because the difference between 0xD365
* (where the bottom line starts) and 0xD085 is 0x2E0, or 736. 736 divided by 32 equals 23;
* add the bottom row, and that would give you 24 visible rows, not 25. Since we now have
* screenshots of a C1P monitor displaying 25 rows (courtesy of Stephan Mühlstrasser), C1Pjs
* now assumes that only the first 3 lines are not visible, and that the address of the first
* visible character is actually 0xD065 (53349), yielding 25 visible rows.
*
* All of this explains why we now use setDimensions(iRowTop=3, nRowsVisible=25) instead of
* setDimensions(iRowTop=4, nRowsVisible=24) for the Model 600.
*
* Model 540 Video Board vs. Model 600 "Superboard II"
* ---------------------------------------------------

File diff suppressed because it is too large Load diff

View file

@ -125,6 +125,13 @@ var BUGS_8086 = false;
*/
var I386 = true;
/**
* @define {boolean}
*
* Enables Compaq DeskPro 386 support.
*/
var COMPAQ386 = true;
if (typeof module !== 'undefined') {
global.PCJSCLASS = PCJSCLASS;
global.DEBUGGER = DEBUGGER;
@ -135,6 +142,7 @@ if (typeof module !== 'undefined') {
global.SAMPLER = SAMPLER;
global.BUGS_8086 = BUGS_8086;
global.I386 = I386;
global.COMPAQ386 = COMPAQ386;
/*
* TODO: When we're "required" by Node, should we return anything via module.exports?
*/

View file

@ -218,7 +218,8 @@ Memory.TYPE = {
RAM: 1,
ROM: 2,
VIDEO: 3,
NAMES: ["NONE", "RAM", "ROM", "VIDEO"],
CTRL: 4,
NAMES: ["NONE", "RAM", "ROM", "VIDEO", "H/W"],
COLORS: ["black", "blue", "green", "cyan"]
};

View file

@ -447,17 +447,18 @@ Panel.prototype.updateMouse = function(event, fDown)
this.yMouse = y;
if (MAXDEBUG) this.log("Panel.moveMouse(" + x + "," + y + ")");
this.assert(x >= 0 && x < Panel.LIVECANVAS.CX && y >= 0 && y < Panel.LIVECANVAS.CY);
/*
* Convert the mouse position into the corresponding memory address, assuming it's over the live memory area
*/
var addr = this.findAddress(x, y);
if (addr != X86.ADDR_INVALID) {
addr &= ~0xf;
if (addr != this.addrDumpLast) {
this.dumpMemory(addr, true);
this.addrDumpLast = addr;
if (x >= 0 && x < Panel.LIVECANVAS.CX && y >= 0 && y < Panel.LIVECANVAS.CY) {
/*
* Convert the mouse position into the corresponding memory address, assuming it's over the live memory area
*/
var addr = this.findAddress(x, y);
if (addr != X86.ADDR_INVALID) {
addr &= ~0xf;
if (addr != this.addrDumpLast) {
this.dumpMemory(addr, true);
this.addrDumpLast = addr;
}
}
}
};

View file

@ -54,8 +54,8 @@ if (typeof module !== 'undefined') {
* @extends Component
* @param {Object} parmsRAM
*/
function RAM(parmsRAM) {
function RAM(parmsRAM)
{
Component.call(this, "RAM", parmsRAM, RAM);
this.addrRAM = parmsRAM['addr'];
@ -76,7 +76,8 @@ Component.subclass(Component, RAM);
* @param {X86CPU} cpu
* @param {Debugger} dbg
*/
RAM.prototype.initBus = function(cmp, bus, cpu, dbg) {
RAM.prototype.initBus = function(cmp, bus, cpu, dbg)
{
this.bus = bus;
this.cpu = cpu;
this.chipset = cmp.getComponentByType("ChipSet");
@ -91,7 +92,8 @@ RAM.prototype.initBus = function(cmp, bus, cpu, dbg) {
* @param {boolean} [fRepower]
* @return {boolean} true if successful, false if failure
*/
RAM.prototype.powerUp = function(data, fRepower) {
RAM.prototype.powerUp = function(data, fRepower)
{
if (!fRepower) {
/*
* The Computer powers up the CPU last, at which point the X86 state is restored,
@ -117,7 +119,8 @@ RAM.prototype.powerUp = function(data, fRepower) {
* @param {boolean} fSave
* @return {Object|boolean}
*/
RAM.prototype.powerDown = function(fSave) {
RAM.prototype.powerDown = function(fSave)
{
/*
* The Computer powers down the CPU first, at which point the X86 state is saved,
* which includes the Bus state, and since we use the Bus component to allocate all
@ -148,7 +151,8 @@ RAM.prototype.powerDown = function(fSave) {
*
* @this {RAM}
*/
RAM.prototype.reset = function() {
RAM.prototype.reset = function()
{
if (!this.addrRAM && !this.fInstalled && this.chipset) {
var baseRAM = this.chipset.getSWMemorySize() * 1024;
if (this.sizeRAM && baseRAM != this.sizeRAM) {
@ -168,6 +172,30 @@ RAM.prototype.reset = function() {
* for these components, which the Computer component will display as it "powers up" components.
*/
if (MAXDEBUG && this.fInstalled) this.status("specified size overrides SW1");
/*
* Memory with an ID of "ramCPQ" is reserved for built-in memory located just below the 16Mb
* boundary on Compaq DeskPro 386 machines.
*
* Technically, that memory is part of the first 1Mb of memory that also provides up to 640Kb
* of conventional memory (ie, memory below 1Mb).
*
* However, PCjs doesn't support individual memory allocations that (a) are discontiguous
* or (b) dynamically change location. Components must simulate those features by performing
* a separate allocation for each starting address, and removing/adding memory allocations
* whenever their starting address changes.
*
* Therefore, a DeskPro 386's first 1Mb of physical memory is allocated by PCjs in two pieces,
* and the second piece must have an ID of "ramCPQ", triggering the additional allocation of
* Compaq-specific memory-mapped registers.
*
* See CompaqController for more details.
*/
if (COMPAQ386) {
if (this.idComponent == "ramCPQ") {
this.controller = new CompaqController(this);
this.bus.addMemory(CompaqController.ADDR, 1, Memory.TYPE.CTRL, this.controller);
}
}
}
}
if (this.fAllocated) {
@ -193,7 +221,8 @@ RAM.prototype.reset = function() {
* attribute, invoking the constructor to create a RAM component, and then binding
* any associated HTML controls to the new component.
*/
RAM.init = function() {
RAM.init = function()
{
var aeRAM = Component.getElementsByClass(window.document, PCJSCLASS, "ram");
for (var iRAM = 0; iRAM < aeRAM.length; iRAM++) {
var eRAM = aeRAM[iRAM];
@ -203,6 +232,121 @@ RAM.init = function() {
}
};
/**
* CompaqController(ram)
*
* DeskPro 386 machines came with a minimum of 1Mb of RAM, which could be configured (via jumpers)
* for 256Kb, 512Kb or 640Kb of conventional memory, starting at address 0x00000000, with the
* remainder (768Kb, 512Kb, or 384Kb) accessible only at addresses just below 0x01000000. This
* second chunk of RAM must have an ID of "ramCPQ".
*
* The typical configuration was 640Kb of conventional memory, leaving 384Kb accessible at 0x00FA0000.
* Presumably, the other configurations (256Kb and 512Kb) would leave 768Kb and 512Kb accessible at
* 0x00F40000 and 0x00F80000, respectively.
*
* The DeskPro 386 also contained two memory-mapped registers at 0x80C00000. The first is a write-only
* mapping register that provides the ability to map the 128Kb at 0x00FE0000 to 0x000E0000, replacing
* any ROMs in the range 0x000E0000-0x000FFFFF, and optionally write-protecting that 128Kb. The second
* register is a read-only diagnostics register that indicates jumper configuration and parity errors.
*
* To emulate the memory-mapped registers at 0x80C00000, the RAM component allocates a block at that
* address using this custom controller once it sees an allocation for "ramCPQ".
*
* Later, when the addressibility of "ramCPQ" memory is altered, we record the blocks in all the
* memory slots spanning 0x000E0000-0x000FFFFF, and then update those slots with the blocks from
* 0x00FE0000-0x00FFFFFF. Note that only the top 128Kb of "ramCPQ" addressibility is affected; the
* rest of that memory, ranging anywhere from 256Kb to 640Kb, remains addressible at its original
* location. Compaq's CEMM and VDISK utilities were generally the only software able to access that
* remaining memory.
*
* @constructor
* @param {RAM} ram
*/
function CompaqController(ram)
{
this.ram = ram;
this.bMapping = CompaqController.MAPPING.DEFAULT;
this.bSettings = CompaqController.SETTINGS.BASE_640KB;
}
CompaqController.ADDR = 0x80C00000;
/*
* Bit definitions for the 8-bit write-only memory-mapping register (bMapping)
*/
CompaqController.MAPPING = {
NORELOC: 0x01, // is this bit is CLEAR, the last 128Kb (at 0x00FE0000) is relocated to 0x000E0000
READWRITE: 0x02, // if this bit is CLEAR, the last 128Kb (at 0x00FE0000) is read-only (ie, write-protected)
RESERVED: 0xFC, // the remaining 6 bits are reserved and should always be SET
DEFAULT: 0xFF
};
/*
* Bit definitions for the 8-bit read-only settings/diagnostics register (bSettings)
*/
CompaqController.SETTINGS = {
B0_PARITY: 0x01,
B1_PARITY: 0x02,
B2_PARITY: 0x04,
B3_PARITY: 0x08,
BASE_640KB: 0x00,
BASE_ERROR: 0x10,
BASE_512KB: 0x20,
BASE_256KB: 0x30,
ADDED_1MB: 0x40,
PIGGYBACK: 0x80
};
/**
* readByte(off)
*
* @this {Memory}
* @param {number} off
* @return {number}
*/
CompaqController.readByte = function readCompaqControllerByte(off)
{
return this.controller.bSettings;
};
/**
* writeByte(off, b)
*
* @this {Memory}
* @param {number} off
* @param {number} b (which should already be pre-masked to 8 bits; see Bus.prototype.setByteDirect)
*/
CompaqController.writeByte = function writeCompaqControllerByte(off, b)
{
this.controller.bMapping = b;
};
CompaqController.ACCESS = [CompaqController.readByte, CompaqController.readByte, CompaqController.readByte,
CompaqController.writeByte, CompaqController.writeByte, CompaqController.writeByte];
/**
* getMemoryBuffer(addr)
*
* @this {CompaqController}
* @param {number} addr
* @return {Array} containing the buffer (and an offset within that buffer)
*/
CompaqController.prototype.getMemoryBuffer = function(addr)
{
return [null, 0];
};
/**
* getMemoryAccess()
*
* @this {CompaqController}
* @return {Array.<function()>}
*/
CompaqController.prototype.getMemoryAccess = function()
{
return CompaqController.ACCESS;
};
/*
* Initialize all the RAM modules on the page.
*/

View file

@ -68,7 +68,20 @@ function ROM(parmsROM)
this.abROM = null;
this.addrROM = parmsROM['addr'];
this.sizeROM = parmsROM['size'];
this.addrROMAlias = parmsROM['alias'];
/*
* The new 'alias' property can now be EITHER a single physical address (like 'addr') OR an array of
* physical addresses; eg:
*
* [0xf0000,0xffff0000,0xffff8000]
*
* We could have overloaded 'addr' to accomplish the same thing, but I think it's better to have any
* aliased locations listed under a separate property.
*
* Most ROMs are not aliased, in which case the 'alias' property should have the default value of null.
*/
this.addrAlias = parmsROM['alias'];
this.sFilePath = parmsROM['file'];
this.sFileName = str.getBaseName(this.sFilePath);
this.idNotify = parmsROM['notify'];
@ -274,7 +287,17 @@ ROM.prototype.copyROM = function()
*/
this.setError("ROM size (0x" + str.toHex(this.abROM.length) + ") does not match specified size (0x" + str.toHex(this.sizeROM) + ")");
}
else if (this.addROM(this.addrROM) && this.addROM(this.addrROMAlias)) {
else if (this.addROM(this.addrROM)) {
var aliases = [];
if (typeof this.addrAlias == "number") {
aliases.push(this.addrAlias);
} else if (this.addrAlias != null && this.addrAlias.length) {
aliases = this.addrAlias;
}
for (var i = 0; i < aliases.length; i++) {
this.addROM(aliases[i]);
}
/*
* If there's a component we should notify, notify it now, and give it the internal byte array, so that
* it doesn't have to ask the CPU for the data. Currently, the only component that uses this notification
@ -306,16 +329,12 @@ ROM.prototype.copyROM = function()
/**
* addROM(addr)
*
* If addr is null or undefined, then it's presumably an unused addrROMAlias, which we simply ignore (it's not
* considered a failure condition).
*
* @this {ROM}
* @param {number} addr
* @return {boolean}
*/
ROM.prototype.addROM = function(addr)
{
if (addr == null) return true;
if (this.bus.addMemory(addr, this.sizeROM, Memory.TYPE.ROM)) {
if (DEBUG) this.log("addROM(): copying ROM to 0x" + str.toHex(addr) + " (0x" + str.toHex(this.abROM.length) + " bytes)");
var bto = null;

View file

@ -84,11 +84,12 @@ var X86 = {
* Machine Status Word (MSW) bit definitions
*/
MSW: {
PE: 0x0001, // protected-mode enabled
MP: 0x0002, // monitor processor extension (ie, coprocessor)
EM: 0x0004, // emulate processor extension
TS: 0x0008, // task switch indicator
ON: 0xfff0 // on the 80286, these bits are always on (TODO: Verify)
PE: 0x0001, // protected-mode enabled
MP: 0x0002, // monitor processor extension (ie, coprocessor)
EM: 0x0004, // emulate processor extension
TS: 0x0008, // task switch indicator
ON: 0xfff0, // on the 80286, these bits are always on (TODO: Verify)
MASK: 0xffff // these are the only (MSW) bits that the 80286 can access (within CR0)
},
ET: 0x00000010, // coprocessor type (80287 or 80387); always 1 on post-80386 CPUs
PG: 0x80000000|0 // 0: paging disabled
@ -147,7 +148,7 @@ var X86 = {
SHIFT: 13
},
PRESENT: 0x8000,
INVALID: 0 // use X86.DESC.ACC.INVALID for invalid ACC values
INVALID: 0 // use X86.DESC.ACC.INVALID for invalid ACC values
},
EXT: { // descriptor extension word (reserved on the 80286; "must be zero")
OFFSET: 0x6,

View file

@ -706,9 +706,9 @@ X86CPU.prototype.initProcessor = function()
*/
this.CYCLES = (this.model >= X86.MODEL_80286? X86CPU.CYCLES_80286 : X86CPU.CYCLES_8088);
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 = X86OpXX.aOps;
this.aOpGrp4b = X86Grps.aOpGrp4b;
this.aOpGrp4w = X86Grps.aOpGrp4w;
this.aOpGrp6 = X86Op0F.aOpGrp6Real; // setProtMode() will ensure that aOpGrp6 is switched
if (this.model >= X86.MODEL_80186) {
@ -720,6 +720,9 @@ 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.nShiftCountMask = 0x1f; // on newer processors, all shift counts are MOD 32
this.aOps[0x0F] = X86Help.opHelpInvalid;
this.aOps[X86.OPCODE.PUSHA] = X86OpXX.opPUSHA;
@ -753,16 +756,38 @@ 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;
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;
}
}
}
/*
* The memory dispatch tables; opMem refers to the active set, based on the current OPERAND size (dataSize),
* which is based foremost on segCS.dataSize, but can also be overridden by an OPERAND size instruction prefix.
*/
this.aaOpMem = [];
this.aaOpMem[2] = {
getWord: this.getShort.bind(this),
setWord: this.setShort.bind(this)
};
if (I386) {
this.aaOpMem[4] = {
getWord: this.getLong.bind(this),
setWord: this.setLong.bind(this)
};
}
};
/**
@ -928,35 +953,10 @@ X86CPU.prototype.resetRegs = function()
*/
this.intFlags = X86.INTFLAG.NONE;
/*
* The following contain the (default) OPERAND size (2 for 16 bits, 4 for 32 bits), and the corresponding masks
* for isolating the (src) bits of an OPERAND and clearing the (dst) bits of an OPERAND. These are reset to
* their segCS counterparts at the start of every new instruction, but are also set here for documentation purposes.
*/
this.dataSize = this.segCS.dataSize;
this.dataMask = this.segCS.dataMask;
/*
* Similarly, the following contain the (default) ADDRESS size (2 for 16 bits, 4 for 32 bits), and the corresponding
* masks for isolating the (src) bits of an address and clearing the (dst) bits of an address. Like the OPERAND size
* properties, these are reset to their segCS counterparts at the start of every new instruction.
*/
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 something called STACK size,
* which is based on the BIG bit of the last descriptor loaded into SS; use the following segSS properties:
*
* segSS.addrSize (2 or 4)
* segSS.addrMask (0xffff or 0xffffffff)
*
* As there is no STACK size instruction prefix override, there's no need to propagate these segSS properties
* to separate X86CPU properties, as we do for the OPERAND size and ADDRESS size properties.
*/
this.setCSIP(0, 0xffff); // this should be called before the first setPS() call
if (!I386) this.setSizes();
if (BACKTRACK) {
/*
* Initialize the backtrack indexes for all registers to zero. And while, yes, it IS possible
@ -1029,35 +1029,17 @@ X86CPU.prototype.resetRegs = function()
* Now that all the segment registers have been created, it's safe to set the current addressing mode.
*/
this.setProtMode();
/*
* The memory dispatch tables; opMem refers to the active set, based on the current OPERAND size (dataSize),
* which is based foremost on segCS.dataSize, but can also be overridden by an OPERAND size instruction prefix.
*/
this.aaOpMem = [];
this.aaOpMem[2] = {
getWord: this.getShort.bind(this),
setWord: this.setShort.bind(this)
};
if (I386) {
this.aaOpMem[4] = {
getWord: this.getLong.bind(this),
setWord: this.setLong.bind(this)
};
}
this.opMem = this.aaOpMem[this.segCS.dataSize];
this.setOpMod();
};
/**
* setOpMod()
* setAddrSize()
*
* Select the appropriate ModRM dispatch tables, based on the current ADDRESS size (addrSize), which
* is based foremost on segCS.addrSize, but can also be overridden by an ADDRESS size instruction prefix.
*
* @this {X86CPU}
*/
X86CPU.prototype.setOpMod = function()
X86CPU.prototype.setAddrSize = function()
{
if (!I386) {
this.aOpModRegByte = X86ModB.aOpModReg;
@ -1085,6 +1067,54 @@ X86CPU.prototype.setOpMod = function()
}
};
/**
* setDataSize()
*
* @this {X86CPU}
*/
X86CPU.prototype.setDataSize = function()
{
this.opMem = this.aaOpMem[this.dataSize];
};
/**
* setSizes()
*
* @this {X86CPU}
*/
X86CPU.prototype.setSizes = function()
{
/*
* The following contain the (default) ADDRESS size (2 for 16 bits, 4 for 32 bits), and the corresponding
* masks for isolating the (src) bits of an address and clearing the (dst) bits of an address. Like the
* OPERAND size properties, these are reset to their segCS counterparts at the start of every new instruction.
*/
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:
*
* segSS.addrSize (2 or 4)
* segSS.addrMask (0xffff or 0xffffffff)
*
* As there is no STACK size instruction prefix override, there's no need to propagate these segSS properties
* to separate X86CPU properties, as we do for the OPERAND size and ADDRESS size properties.
*/
this.setAddrSize();
/*
* The following contain the (default) OPERAND size (2 for 16 bits, 4 for 32 bits), and the corresponding masks
* for isolating the (src) bits of an OPERAND and clearing the (dst) bits of an OPERAND. These are reset to
* their segCS counterparts at the start of every new instruction, but are also set here for documentation purposes.
*/
this.dataSize = this.segCS.dataSize;
this.dataMask = this.segCS.dataMask;
this.setDataSize();
};
/**
* getChecksum()
*
@ -1230,6 +1260,10 @@ X86CPU.prototype.setProtMode = function(fProt)
this.segDS.updateMode(fProt);
this.segSS.updateMode(fProt);
this.segES.updateMode(fProt);
if (I386 && this.model >= X86.MODEL_80386) {
this.segFS.updateMode(fProt);
this.segGS.updateMode(fProt);
}
};
/**
@ -1303,8 +1337,7 @@ X86CPU.prototype.save = function()
*/
X86CPU.prototype.restore = function(data)
{
var a;
a = data[0];
var a = data[0];
this.regEAX = a[0];
this.regEBX = a[1];
this.regECX = a[2];
@ -1314,6 +1347,7 @@ X86CPU.prototype.restore = function(data)
this.regESI = a[6];
this.regEDI = a[7];
this.nIOPL = a[8] || 0;
a = data[1];
this.segCS.restore(a[1]);
this.segDS.restore(a[2]);
@ -1321,21 +1355,24 @@ X86CPU.prototype.restore = function(data)
this.segES.restore(a[4]);
this.restoreProtMode(a[5]);
this.setPS(a[6]);
/*
* Since we're not using setCS(), it's important to call setIP() *after* segCS is restored, so that the
* CPU's linear IP register (regLIP) will be updated properly.
* It's important to call setCSIP(), both to ensure that the CPU's linear IP register (regLIP) is updated
* properly AND to ensure the CPU's default ADDRESS and OPERAND sizes are set properly.
*/
this.setIP(a[0]);
this.setCSIP(a[0], this.segCS.sel);
/*
* It's also important to call setSP(), so that the linear SP register (regLSP) will be updated properly;
* we also need to call setSS(), to ensure that the lower and upper stack limits are properly initialized.
*/
this.setSP(regESP);
this.setSS(this.segSS.sel);
if (I386 && this.model >= X86.MODEL_80386) {
this.segFS.restore(a[7]);
this.segGS.restore(a[8]);
}
a = data[2];
this.segData = a[0] != null && this.getSeg(a[0]) || this.segDS;
this.segStack = a[1] != null && this.getSeg(a[1]) || this.segSS;
@ -1344,6 +1381,7 @@ X86CPU.prototype.restore = function(data)
this.intFlags = a[4];
this.regEA = a[5];
this.regEAWrite = a[6]; // save/restore of last EA calculation(s) isn't strictly necessary, but they may be of some interest to, say, the Debugger
a = data[3]; // a[0] was previously nBurstDivisor (no longer used)
this.nTotalCycles = a[1];
this.setSpeed(a[2]); // if we're restoring an old state that doesn't contain a value from getSpeed(), that's OK; setSpeed() checks for an undefined value
@ -1407,6 +1445,7 @@ X86CPU.prototype.setCS = function(sel)
var regEIP = this.getIP();
this.regLIP = this.segCS.load(sel) + regEIP;
this.regLIPLimit = this.segCS.base + this.segCS.limit;
if (I386) this.setSizes();
if (!BUGS_8086) this.opFlags |= this.OPFLAG_NOINTR8086;
if (PREFETCH) this.flushPrefetch(this.regLIP);
};
@ -1593,7 +1632,6 @@ X86CPU.prototype.setIP = function(off)
*/
X86CPU.prototype.setCSIP = function(off, sel, fCall)
{
this.assert((off & this.addrMask) == off);
this.segCS.fCall = fCall;
/*
* We break this operation into the following discrete steps (eg, set IP, load CS, and then update IP) so
@ -1607,6 +1645,7 @@ X86CPU.prototype.setCSIP = function(off, sel, fCall)
if (base != X86.ADDR_INVALID) {
this.regLIP = base + this.regEIP;
this.regLIPLimit = base + this.segCS.limit;
if (I386) this.setSizes();
if (PREFETCH) this.flushPrefetch(this.regLIP);
return this.segCS.fStackSwitch;
}
@ -2983,14 +3022,14 @@ X86CPU.prototype.stepCPU = function(nMinCycles)
var fDebugCheck = this.aFlags.fDebugCheck = (DEBUGGER && this.dbg && this.dbg.checksEnabled());
/*
* fDebugSkip is checked only when fDebugCheck is true, and its sole purpose is to tell the first call
* nDebugState is checked only when fDebugCheck is true, and its sole purpose is to tell the first call
* to checkInstruction() that it can skip breakpoint checks, and that will be true ONLY when fStarting is
* true OR nMinCycles is zero (the latter means the Debugger is single-stepping).
*
* Once we snap fStarting, we clear it, because technically, we've moved beyond "starting" and have
* officially "started" now.
*/
var fDebugSkip = this.aFlags.fStarting || !nMinCycles;
var nDebugState = nMinCycles == 0? -1 : (this.aFlags.fStarting? 0 : 1);
this.aFlags.fStarting = false;
/*
@ -3044,13 +3083,7 @@ X86CPU.prototype.stepCPU = function(nMinCycles)
this.segStack = this.segSS;
this.regEA = this.regEAWrite = X86.ADDR_INVALID;
if (I386) {
this.dataSize = this.segCS.dataSize;
this.dataMask = this.segCS.dataMask;
this.addrSize = this.segCS.addrSize;
this.addrMask = this.segCS.addrMask;
this.opMem = this.aaOpMem[this.dataSize];
}
if (I386) this.setSizes();
this.opPrefixes = this.opFlags & X86.OPFLAG.REPEAT;
if (this.intFlags) {
@ -3088,11 +3121,11 @@ X86CPU.prototype.stepCPU = function(nMinCycles)
}
if (DEBUGGER && fDebugCheck) {
if (this.dbg.checkInstruction(this.regLIP, fDebugSkip)) {
if (this.dbg.checkInstruction(this.regLIP, nDebugState)) {
this.stopCPU();
break;
}
fDebugSkip = false;
nDebugState = 1;
}
if (SAMPLER) {

View file

@ -54,7 +54,7 @@ var X86Help = {
* @param {number} src (new value)
* @return {number} dst (src is overridden, replaced with regMD16, as specified by opMOVwsr())
*/
opHelpMOVSegSrc: function(dst, src) {
opHelpMOVMD16: function(dst, src) {
return X86Help.opHelpMOV.call(this, dst, this.regMD16);
},
/**
@ -468,8 +468,8 @@ var X86Help = {
* 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);
this.regCR0 = (this.regCR0 & X86.CR0.MSW.ON) | (w & ~X86.CR0.MSW.ON);
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()
@ -478,6 +478,20 @@ var X86Help = {
*/
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)
*

View file

@ -40,9 +40,9 @@ if (typeof module !== 'undefined') {
var X86Op0F = {
/**
* @this {X86CPU}
* op=0x0F,0x00 (GRP6 rm)
*
* op=0x0F,0x00 (grp6 rm)
* @this {X86CPU}
*/
opGrp6: function() {
var bModRM = this.getIPByte();
@ -52,9 +52,9 @@ var X86Op0F = {
this.aOpModGrpWord[bModRM].call(this, this.aOpGrp6, X86Grps.opGrpNoSrc);
},
/**
* @this {X86CPU}
* op=0x0F,0x01 (GRP7 rm)
*
* op=0x0F,0x01 (grp7 rm)
* @this {X86CPU}
*/
opGrp7: function() {
var bModRM = this.getIPByte();
@ -64,17 +64,21 @@ var X86Op0F = {
this.aOpModGrpWord[bModRM].call(this, X86Op0F.aOpGrp7, X86Grps.opGrpNoSrc);
},
/**
* @this {X86CPU}
* opLAR()
*
* op=0x0F,0x02 (lar reg,rm)
* op=0x0F,0x02 (LAR reg,rm)
*
* @this {X86CPU}
*/
opLAR: function() {
this.aOpModRegWord[this.getIPByte()].call(this, X86Help.opHelpLAR);
},
/**
* @this {X86CPU}
* opLSL()
*
* op=0x0F,0x03 (lsl reg,rm)
* op=0x0F,0x03 (LSL reg,rm)
*
* @this {X86CPU}
*/
opLSL: function() {
this.aOpModRegWord[this.getIPByte()].call(this, X86Help.opHelpLSL);
@ -82,6 +86,8 @@ var X86Op0F = {
/**
* opLOADALL()
*
* op=0x0F,0x05 (LOADALL)
*
* From the "Undocumented iAPX 286 Test Instruction" document at http://www.pcjs.org/pubs/pc/reference/intel/80286/loadall/:
*
* Physical Address (Hex) Associated CPU Register
@ -117,8 +123,6 @@ var X86Op0F = {
* no particular reason.
*
* @this {X86CPU}
*
* op=0x0F,0x05 (loadall)
*/
opLOADALL: function() {
if (this.segCS.cpl) {
@ -168,9 +172,11 @@ var X86Op0F = {
if (DEBUG && DEBUGGER && (this.regCR0 & X86.CR0.MSW.PE)) this.stopCPU();
},
/**
* @this {X86CPU}
* opCLTS()
*
* op=0x0F,0x06 (clts)
* op=0x0F,0x06 (CLTS)
*
* @this {X86CPU}
*/
opCLTS: function() {
if (this.segCS.cpl) {
@ -181,6 +187,10 @@ var X86Op0F = {
this.nStepCycles -= 2;
},
/**
* opSLDT(dst, src)
*
* op=0x0F,0x00,reg=0x0 (GRP6:SLDT)
*
* @this {X86CPU}
* @param {number} dst
* @param {number} src (null)
@ -191,6 +201,10 @@ var X86Op0F = {
return this.segLDT.sel;
},
/**
* opSTR(dst, src)
*
* op=0x0F,0x00,reg=0x1 (GRP6:STR)
*
* @this {X86CPU}
* @param {number} dst
* @param {number} src (null)
@ -201,6 +215,10 @@ var X86Op0F = {
return this.segTSS.sel;
},
/**
* opLLDT(dst, src)
*
* op=0x0F,0x00,reg=0x2 (GRP6:LLDT)
*
* @this {X86CPU}
* @param {number} dst
* @param {number} src (null)
@ -213,6 +231,10 @@ var X86Op0F = {
return dst;
},
/**
* opLTR(dst, src)
*
* op=0x0F,0x00,reg=0x3 (GRP6:LTR)
*
* @this {X86CPU}
* @param {number} dst
* @param {number} src (null)
@ -228,6 +250,10 @@ var X86Op0F = {
return dst;
},
/**
* opVERR(dst, src)
*
* op=0x0F,0x00,reg=0x4 (GRP6:VERR)
*
* @this {X86CPU}
* @param {number} dst
* @param {number} src (null)
@ -264,6 +290,10 @@ var X86Op0F = {
return dst;
},
/**
* opVERW(dst, src)
*
* op=0x0F,0x00,reg=0x5 (GRP6:VERW)
*
* @this {X86CPU}
* @param {number} dst
* @param {number} src (null)
@ -295,6 +325,10 @@ var X86Op0F = {
return dst;
},
/**
* opSGDT(dst, src)
*
* op=0x0F,0x01,reg=0x0 (GRP7:SGDT)
*
* @this {X86CPU}
* @param {number} dst
* @param {number} src (null)
@ -349,6 +383,10 @@ var X86Op0F = {
return dst;
},
/**
* opSIDT(dst, src)
*
* op=0x0F,0x01,reg=0x1 (GRP7:SIDT)
*
* @this {X86CPU}
* @param {number} dst
* @param {number} src (null)
@ -378,6 +416,8 @@ var X86Op0F = {
/**
* opLGDT(dst, src)
*
* op=0x0F,0x01,reg=0x2 (GRP7:LGDT)
*
* The 80286 LGDT instruction expects a 40-bit operand: a 16-bit limit, followed by a 24-bit address;
* the ModRM decoder has already supplied the first word of the operand (in dst), which corresponds to the
* limit, so we must fetch the remaining 24 bits ourselves.
@ -401,6 +441,8 @@ var X86Op0F = {
/**
* opLIDT(dst, src)
*
* op=0x0F,0x01,reg=0x3 (GRP7:LIDT)
*
* The 80286 LIDT instruction expects a 40-bit operand: a 16-bit limit, followed by a 24-bit address;
* the ModRM decoder has already supplied the first word of the operand (in dst), which corresponds to the
* limit, so we must fetch the remaining 24 bits ourselves.
@ -422,6 +464,10 @@ var X86Op0F = {
return dst;
},
/**
* opSMSW(dst, src)
*
* op=0x0F,0x01,reg=0x4 (GRP7:SMSW)
*
* @this {X86CPU}
* @param {number} dst
* @param {number} src (null)
@ -432,6 +478,10 @@ var X86Op0F = {
return this.regCR0;
},
/**
* opLMSW(dst, src)
*
* op=0x0F,0x01,reg=0x6 (GRP7:LMSW)
*
* @this {X86CPU}
* @param {number} dst
* @param {number} src (null)
@ -442,6 +492,118 @@ var X86Op0F = {
this.nStepCycles -= (this.regEA < 0? 3 : 6);
this.opFlags |= X86.OPFLAG.NOWRITE;
return dst;
},
/**
* opMOVrcr()
*
* op=0x0F,0x20 (MOV reg,cr)
*
* NOTE: Since the ModRM decoders deal only with general-purpose registers, we must move
* the appropriate control register into a special variable (regMD16), which our helper function
* (opHelpMOVMD16) will use to replace the decoder's src operand.
*
* @this {X86CPU}
*/
opMOVrcr: function() {
var bModRM = this.getIPByte() | 0xc0;
/*
* Unlike, say, opcode 0x8C (MOV word,sr), this opcode supports only registers, not memory;
* however, the 80386 apparently ignores the mod bits, treating any combination as if it was 0xc0.
*
if ((bModRM & 0xc0) != 0xc0) {
X86Help.opHelpInvalid.call(this);
return;
}
*/
var reg = (bModRM & 0x38) >> 3;
switch (reg) {
case 0x0:
this.regMD16 = this.regCR0;
break;
case 0x1:
this.regMD16 = this.regCR1;
break;
case 0x2:
this.regMD16 = this.regCR2;
break;
case 0x3:
this.regMD16 = this.regCR3;
break;
default:
X86Help.opHelpUndefined.call(this);
return;
}
/*
* Like other MOV operations, the destination does not need to be read, just written;
* however, it's moot, because we've already restricted this opcode to registers only.
*
* this.opFlags |= X86.OPFLAG.NOREAD;
*/
this.aOpModRegWord[bModRM].call(this, X86Help.opHelpMOVMD16);
},
/**
* opMOVcrr()
*
* op=0x0F,0x22 (MOV cr,reg)
*
* NOTE: Since the ModRM decoders deal only with general-purpose registers, we have to
* make a note of which general-purpose register will be overwritten, so that we can restore it
* after moving the modified value to the correct control register.
*
* @this {X86CPU}
*/
opMOVcrr: function() {
var temp;
var bModRM = this.getIPByte() | 0xc0;
/*
* Unlike, say, opcode 0x8E (MOV sr,word), this opcode supports only registers, not memory;
* however, the 80386 apparently ignores the mod bits, treating any combination as if it was 0xc0.
*
if ((bModRM & 0xc0) != 0xc0) {
X86Help.opHelpInvalid.call(this);
return;
}
*/
var reg = (bModRM & 0x38) >> 3;
switch(reg) {
case 0x0:
temp = this.regEAX;
break;
case 0x1:
temp = this.regECX; // TODO: Is setting CR1 actually allowed on an 80386?
break;
case 0x2:
temp = this.regEDX;
break;
case 0x3:
temp = this.regEBX;
break;
default:
X86Help.opHelpInvalid.call(this);
return;
}
this.aOpModRegWord[bModRM].call(this, X86Help.opHelpMOV);
switch (reg) {
case 0x0:
reg = this.regEAX;
this.regEAX = temp;
X86Help.opHelpLCR0.call(this, reg);
break;
case 0x1:
this.regCR1 = this.regECX;
this.regECX = temp;
break;
case 0x2:
this.regCR2 = this.regEDX;
this.regEDX = temp;
break;
case 0x3:
this.regCR3 = this.regEBX;
this.regEBX = temp;
break;
default:
break; // there IS no other case, but JavaScript inspections don't know that
}
}
};

View file

@ -218,7 +218,7 @@ var X86OpXX = {
* @this {X86CPU}
*/
op0F: function() {
X86Op0F.aOps0F[this.getIPByte()].call(this);
this.aOps0F[this.getIPByte()].call(this);
},
/**
* op=0x10 (ADC byte,reg)
@ -1327,6 +1327,36 @@ var X86OpXX = {
opARPL: function() {
this.aOpModMemWord[this.getIPByte()].call(this, X86Help.opHelpARPL);
},
/**
* op=0x64 (FS:)
*
* @this {X86CPU}
*/
opFS: function() {
/*
* NOTE: The fact that we're setting NOINTR along with SEG is really just for documentation purposes;
* the way stepCPU() is written, the presence of any prefix bypasses normal interrupt processing anyway.
*/
this.opFlags |= X86.OPFLAG.SEG | X86.OPFLAG.NOINTR;
this.segData = this.segStack = this.segFS;
this.nStepCycles -= this.CYCLES.nOpCyclesPrefix;
this.stopCPU();
},
/**
* op=0x65 (GS:)
*
* @this {X86CPU}
*/
opGS: function() {
/*
* NOTE: The fact that we're setting NOINTR along with SEG is really just for documentation purposes;
* the way stepCPU() is written, the presence of any prefix bypasses normal interrupt processing anyway.
*/
this.opFlags |= X86.OPFLAG.SEG | X86.OPFLAG.NOINTR;
this.segData = this.segStack = this.segGS;
this.nStepCycles -= this.CYCLES.nOpCyclesPrefix;
this.stopCPU();
},
/**
* op=0x66 (OS:) (80386 and up)
*
@ -1339,8 +1369,9 @@ var X86OpXX = {
this.opFlags |= X86.OPFLAG.SEG;
this.dataSize ^= 0x6; // that which is 2 shall become 4, and vice versa
this.dataMask ^= (0xffff0000|0); // that which is 0x0000ffff shall become 0xffffffff, and vice versa
this.opMem = this.aaOpMem[this.dataSize];
this.setDataSize();
this.nStepCycles -= this.CYCLES.nOpCyclesPrefix;
this.stopCPU();
}
},
/**
@ -1355,8 +1386,9 @@ var X86OpXX = {
this.opFlags |= X86.OPFLAG.SEG;
this.addrSize ^= 0x06; // that which is 2 shall become 4, and vice versa
this.addrMask ^= (0xffff0000|0); // that which is 0x0000ffff shall become 0xffffffff, and vice versa
this.setOpMod();
this.setAddrSize();
this.nStepCycles -= this.CYCLES.nOpCyclesPrefix;
this.stopCPU();
}
},
/**
@ -1929,7 +1961,7 @@ var X86OpXX = {
*
* NOTE: Since the ModRM decoders deal only with general-purpose registers, we must move
* the appropriate segment register into a special variable (regMD16), which our helper function
* (opHelpMOVSegSrc) will use to replace the decoder's src operand.
* (opHelpMOVMD16) will use to replace the decoder's src operand.
*
* @this {X86CPU}
*/
@ -1957,7 +1989,7 @@ var X86OpXX = {
* Like other MOV operations, the destination does not need to be read, just written.
*/
this.opFlags |= X86.OPFLAG.NOREAD;
this.aOpModMemWord[bModRM].call(this, X86Help.opHelpMOVSegSrc);
this.aOpModMemWord[bModRM].call(this, X86Help.opHelpMOVMD16);
},
/**
* op=0x8D (LEA reg,word)

View file

@ -569,6 +569,12 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fSuppress)
var ext = cpu.getShort(addrDesc + X86.DESC.EXT.OFFSET);
var selMasked = sel & X86.SEL.MASK;
if (I386 && cpu.model >= X86.MODEL_80386) {
base |= (ext & X86.DESC.EXT.BASE2431) << 16;
limit |= (ext & X86.DESC.EXT.LIMIT1619) << 16;
if (ext & X86.DESC.EXT.GRANULARITY) limit = (limit << 12) | 0xfff;
}
while (true) {
var selCode, cplPrev, addrTSS, offSP, offSS, regSPPrev, regSSPrev;
@ -863,11 +869,11 @@ X86Seg.prototype.updateMode = function(fProt)
this.cpl = this.sel & X86.SEL.RPL;
this.dpl = (this.acc & X86.DESC.ACC.DPL.MASK) >> X86.DESC.ACC.DPL.SHIFT;
if (this.cpu.model < X86.MODEL_80386 || !(this.ext & X86.DESC.EXT.BIG)) {
this.dataSize = 2;
this.dataMask = 0xffff;
this.addrSize = 2;
this.addrMask = 0xffff;
} else {
this.dataSize = 4;
this.dataMask = (0xffffffff|0);
this.addrSize = 4;
this.addrMask = (0xffffffff|0);
}
} else {
this.load = X86Seg.loadReal;
@ -877,11 +883,11 @@ X86Seg.prototype.updateMode = function(fProt)
this.limit = 0xffff;
this.cpl = this.dpl = 0;
this.addrDesc = X86.ADDR_INVALID;
this.dataSize = 2;
this.dataMask = 0xffff;
this.addrSize = 2;
this.addrMask = 0xffff;
}
this.addrSize = this.dataSize;
this.addrMask = this.dataMask;
this.dataSize = this.addrSize;
this.dataMask = this.addrMask;
return fProt;
};
@ -904,7 +910,7 @@ X86Seg.prototype.messageSeg = function(sel, base, limit, acc, ext)
if (this.id == X86Seg.ID.CODE) sDPL += " cpl=" + this.cpl;
this.dbg.message("loadSeg(" + this.sName + "):" + ch + "sel=" + str.toHexWord(sel) + " base=" + str.toHex(base) + " limit=" + str.toHexWord(limit) + " acc=" + str.toHexWord(acc) + sDPL);
}
this.cpu.assert(/* base != X86.ADDR_INVALID && */ (!ext || ext == X86.DESC.EXT.AVAIL));
this.cpu.assert(/* base != X86.ADDR_INVALID && */ (this.cpu.model >= X86.MODEL_80386 || !ext || ext == X86.DESC.EXT.AVAIL));
}
};

View file

@ -88,7 +88,7 @@ str.parseInt = function(s, base)
}
var v;
if (str.isValidInt(s, base) && !isNaN(v = parseInt(s, base))) {
value = v;
value = v|0;
}
}
return value;