diff --git a/modules/pcjs/lib/bus.js b/modules/pcjs/lib/bus.js index 5d829d3e3..ac890a182 100644 --- a/modules/pcjs/lib/bus.js +++ b/modules/pcjs/lib/bus.js @@ -290,7 +290,7 @@ Bus.prototype.getA20 = function() */ Bus.prototype.setA20 = function(fEnable) { - Component.assert(fEnable !== undefined); + if (DEBUG) this.assert(fEnable !== undefined); if (fEnable !== undefined) { if (this.nBusWidth > 20) { var addrMask = (this.addrMask & ~0x100000) | (fEnable? 0x100000 : 0); diff --git a/modules/pcjs/lib/chipset.js b/modules/pcjs/lib/chipset.js index be891515f..f6d3c9c17 100644 --- a/modules/pcjs/lib/chipset.js +++ b/modules/pcjs/lib/chipset.js @@ -1201,7 +1201,7 @@ ChipSet.prototype.initRTCDate = function(sDate) */ ChipSet.prototype.getRTCByte = function(iRTC) { - Component.assert(iRTC >= 0 && iRTC <= ChipSet.CMOS.ADDR.RTC_STATUSD); + if (DEBUG) this.assert(iRTC >= 0 && iRTC <= ChipSet.CMOS.ADDR.RTC_STATUSD); var b = this.abCMOSData[iRTC]; @@ -1253,7 +1253,7 @@ ChipSet.prototype.getRTCByte = function(iRTC) */ ChipSet.prototype.setRTCByte = function(iRTC, b) { - Component.assert(iRTC >= 0 && iRTC <= ChipSet.CMOS.ADDR.RTC_STATUSD); + if (DEBUG) this.assert(iRTC >= 0 && iRTC <= ChipSet.CMOS.ADDR.RTC_STATUSD); if (iRTC < ChipSet.CMOS.ADDR.RTC_STATUSA) { var fBCD = false; @@ -1272,7 +1272,7 @@ ChipSet.prototype.setRTCByte = function(iRTC, b) * transformed it to 0x51-0x5C, so we must add 0x30. */ if (b > 12) { - Component.assert(b >= 0x51 && b <= 0x5c); + if (DEBUG) this.assert(b >= 0x51 && b <= 0x5c); b += 0x30; } } @@ -1306,7 +1306,7 @@ ChipSet.prototype.updateRTCDate = function() */ if (this.nCyclesCMOSLastUpdate >= 0) { nCyclesDelta = nCyclesUpdate - this.nCyclesCMOSLastUpdate; - Component.assert(nCyclesDelta >= 0); + if (DEBUG) this.assert(nCyclesDelta >= 0); var nSecondsDelta = Math.floor(nCyclesDelta / nCyclesPerSecond); /* * We trust that updateRTCDate() is being called as part of updateAllTimers(), and is therefore @@ -1315,7 +1315,7 @@ ChipSet.prototype.updateRTCDate = function() * (nCyclesDelta % nCyclesPerSecond) back into nCyclesCMOSLastUpdate, so that we will eventually * see a one-second delta. */ - Component.assert(nSecondsDelta <= 1); + if (DEBUG) this.assert(nSecondsDelta <= 1); if (nSecondsDelta) { if (++this.abCMOSData[ChipSet.CMOS.ADDR.RTC_SEC] >= 60) { this.abCMOSData[ChipSet.CMOS.ADDR.RTC_SEC] = 0; @@ -1397,7 +1397,7 @@ ChipSet.prototype.initCMOSData = function() ChipSet.prototype.setCMOSByte = function(iCMOS, b) { if (this.abCMOSData) { - Component.assert(iCMOS >= ChipSet.CMOS.ADDR.FDRIVE && iCMOS < ChipSet.CMOS.ADDR.CHKSUM_HI); + if (DEBUG) this.assert(iCMOS >= ChipSet.CMOS.ADDR.FDRIVE && iCMOS < ChipSet.CMOS.ADDR.CHKSUM_HI); this.abCMOSData[iCMOS] = b; this.updateCMOSChecksum(); return true; @@ -1449,7 +1449,7 @@ ChipSet.prototype.setCMOSDriveType = function(iDrive, bType) { if (this.abCMOSData) { var b = this.abCMOSData[ChipSet.CMOS.ADDR.HDRIVE]; - Component.assert(bType > 0 && bType < 0xf); + if (DEBUG) this.assert(bType > 0 && bType < 0xf); if (iDrive) { b = (b & ChipSet.CMOS.HDRIVE.D0_MASK) | bType; } else { @@ -1549,7 +1549,7 @@ ChipSet.prototype.restore = function(data) a = data[5]; if (a) { - Component.assert(this.model >= ChipSet.MODEL_5170); + if (DEBUG) this.assert(this.model >= ChipSet.MODEL_5170); this.b8042Status = a[0]; this.b8042InBuff = a[1]; this.b8042CmdData = a[2]; @@ -1560,7 +1560,7 @@ ChipSet.prototype.restore = function(data) a = data[6]; if (a) { - Component.assert(this.model >= ChipSet.MODEL_5170); + if (DEBUG) this.assert(this.model >= ChipSet.MODEL_5170); this.bMFGData = a[0]; this.abDMAPageSpare = a[1]; this.bCMOSAddr = a[2]; @@ -1592,7 +1592,7 @@ ChipSet.prototype.initDMAController = function(iDMAC, aState) { var controller = this.aDMACs[iDMAC]; if (!controller) { - Component.assert(!aState); + if (DEBUG) this.assert(!aState); controller = { aChannels: new Array(4) }; @@ -1623,7 +1623,7 @@ ChipSet.prototype.initDMAChannel = function(controller, iChannel, aState) { var channel = controller.aChannels[iChannel]; if (!channel) { - Component.assert(!aState); + if (DEBUG) this.assert(!aState); channel = { addrInit: [0,0], countInit: [0,0], @@ -1899,7 +1899,7 @@ ChipSet.prototype.getSWFloppyDriveType = function(iDrive) return ChipSet.CMOS.FDRIVE.FD1440; } } - Component.assert(false); // we should never get here (else something is out of out sync) + if (DEBUG) this.assert(false); // we should never get here (else something is out of out sync) } return ChipSet.CMOS.FDRIVE.NONE; }; @@ -1920,7 +1920,7 @@ ChipSet.prototype.getSWFloppyDriveSize = function(iDrive) if (iDrive < this.aFloppyDrives.length) { return this.aFloppyDrives[iDrive]; } - Component.assert(false); // we should never get here (else something is out of out sync) + if (DEBUG) this.assert(false); // we should never get here (else something is out of out sync) } return 0; }; @@ -4020,7 +4020,7 @@ ChipSet.prototype.in8042Status = function(port, addrFrom) ChipSet.prototype.out8042InBuffCmd = function(port, bOut, addrFrom) { this.messagePort(port, bOut, addrFrom, "8042_INBUFF.CMD", Debugger.MESSAGE.C8042); - Component.assert(!(this.b8042Status & ChipSet.KBC.STATUS.INBUFF_FULL)); + if (DEBUG) this.assert(!(this.b8042Status & ChipSet.KBC.STATUS.INBUFF_FULL)); this.b8042InBuff = bOut; this.b8042Status |= ChipSet.KBC.STATUS.CMD_FLAG; @@ -4103,7 +4103,7 @@ ChipSet.prototype.set8042CmdData = function(b) { var bClockWasEnabled = !(this.b8042CmdData & ChipSet.KBC.DATA.CMD.NO_CLOCK); this.b8042CmdData = b; - Component.assert(ChipSet.KBC.DATA.CMD.SYS_FLAG === ChipSet.KBC.STATUS.SYS_FLAG); + if (DEBUG) this.assert(ChipSet.KBC.DATA.CMD.SYS_FLAG === ChipSet.KBC.STATUS.SYS_FLAG); this.b8042Status = (this.b8042Status & ~ChipSet.KBC.STATUS.SYS_FLAG) | (b & ChipSet.KBC.DATA.CMD.SYS_FLAG); if (this.kbd) { /* diff --git a/modules/pcjs/lib/cpu.js b/modules/pcjs/lib/cpu.js index 8a3ed0eda..63be0abc5 100644 --- a/modules/pcjs/lib/cpu.js +++ b/modules/pcjs/lib/cpu.js @@ -883,7 +883,7 @@ CPU.prototype.calcStartTime = function() * in case, I make absolutely sure it cannot happen, since doing so could result in negative * speed calculations. */ - Component.assert(this.aCounts.msStartRun <= this.aCounts.msStartThisRun); + if (DEBUG) this.assert(this.aCounts.msStartRun <= this.aCounts.msStartThisRun); if (this.aCounts.msStartRun > this.aCounts.msStartThisRun) { this.aCounts.msStartRun = this.aCounts.msStartThisRun; } @@ -1049,7 +1049,7 @@ CPU.prototype.runCPU = function(fOnClick) /** * startCPU(fSetFocus) * - * WARNING: Other components must use runCPU() to get the CPU running; this is a runCPU() helper function only. + * WARNING: Other components must use runCPU() to get the CPU running; This is a runCPU() helper function only; o * * @param {boolean} [fSetFocus] */ @@ -1145,10 +1145,10 @@ CPU.prototype.yieldCPU = function() this.nStepCycles = 0; // this will break us out of stepCPU() /* * The Debugger calls yieldCPU() after every message() to ensure browser responsiveness, but it looks - * odd for those messages to show CPU state changes but for the CPU's own status display to not, so I've - * added this call to try to keep things looking synchronized. + * odd for those messages to show CPU state changes but for the CPU's own status display to not (ditto + * for the Video display), so I've added this call to try to keep things looking synchronized. */ - this.displayStatus(); + this.updateCPU(); }; if (typeof APP_PCJS !== 'undefined') APP_PCJS.CPU = CPU; diff --git a/modules/pcjs/lib/debugger.js b/modules/pcjs/lib/debugger.js index c485e7ba2..44882198a 100644 --- a/modules/pcjs/lib/debugger.js +++ b/modules/pcjs/lib/debugger.js @@ -1364,6 +1364,7 @@ if (DEBUGGER) { { this.afnDumpers = []; this.bitsMessageEnabled = 0; + this.sMessagePrev = null; var aEnable = this.parseCommand(sEnable); if (aEnable.length) { for (var m in Debugger.MESSAGES) { @@ -1544,8 +1545,12 @@ if (DEBUGGER) { */ Debugger.prototype.message = function(sMessage) { + if (this.sMessagePrev && sMessage == this.sMessagePrev) return; + this.println(sMessage); // + " (" + this.cpu.getCycles() + " cycles)" + this.sMessagePrev = sMessage; + if (this.cpu) { if (this.bitsMessageEnabled & Debugger.MESSAGE.HALT) { this.cpu.stopCPU(); @@ -1727,6 +1732,18 @@ if (DEBUGGER) { return (this.nCycles > 0); }; + /** + * stopCPU(s) + * + * @this {Debugger} + * @param {string} [s] + */ + Debugger.prototype.stopCPU = function(s) + { + if (s) this.message(s); + this.cpu.stopCPU(); + }; + /** * updateStatus(fRegs, fCompact) * @@ -1993,7 +2010,7 @@ if (DEBUGGER) { * Assert that general-purpose register contents remain within their respective ranges; * this isn't intended to be complete, just a spot-check. */ - Component.assert(!(this.cpu.regAX & ~0xffff) && !(this.cpu.regBX & ~0xffff) && !(this.cpu.regCX & ~0xffff) && !(this.cpu.regDX & ~0xffff), "register out of bounds"); + if (DEBUG) this.assert(!(this.cpu.regAX & ~0xffff) && !(this.cpu.regBX & ~0xffff) && !(this.cpu.regCX & ~0xffff) && !(this.cpu.regDX & ~0xffff), "register out of bounds"); if (!fSkipBP && this.checkBreakpoint(addr, this.aBreakExec)) { return true; @@ -2112,7 +2129,8 @@ if (DEBUGGER) { * If the selector matches that of any of the CPU segment registers, then return the CPU's segment * register, instead of creating our own dummy segment register. This makes it possible for us to * see what the CPU is seeing at certain critical junctures, such as after an LMSW instruction has - * switched the processor from real to protected mode. + * switched the processor from real to protected mode. Actually loading the selector from the GDT/LDT + * should be done only as a last resort. * * @param {number} sel * @return {X86Seg} seg @@ -2183,7 +2201,7 @@ if (DEBUGGER) { var addr = this.getAddr(aAddr, false, 0); if (addr >= 0) { b = this.bus.getByteDirect(addr); - Component.assert((b == (b & 0xff)), "invalid byte (" + b + ") at address: " + this.hexAddr(aAddr)); + if (DEBUG) this.assert((b == (b & 0xff)), "invalid byte (" + b + ") at address: " + this.hexAddr(aAddr)); if (inc !== undefined) this.incAddr(aAddr, inc); } return b; @@ -2203,7 +2221,7 @@ if (DEBUGGER) { var addr = this.getAddr(aAddr, false, 1); if (addr >= 0) { w = this.bus.getWordDirect(addr); - Component.assert((w == (w & 0xffff)), "invalid word (" + w + ") at address: " + this.hexAddr(aAddr)); + if (DEBUG) this.assert((w == (w & 0xffff)), "invalid word (" + w + ") at address: " + this.hexAddr(aAddr)); if (inc !== undefined) this.incAddr(aAddr, inc); } return w; @@ -3515,7 +3533,7 @@ if (DEBUGGER) { s += (seg.acc & X86.DESC.ACC.TYPE.CONFORMING)? "conforming," : "nonconforming,"; } else { s += "data:"; - s += (seg.acc & X86.DESC.ACC.TYPE.WRITEABLE)? "writeable," : "readonly,"; + s += (seg.acc & X86.DESC.ACC.TYPE.WRITABLE)? "writable," : "readonly,"; s += (seg.acc & X86.DESC.ACC.TYPE.EXPDOWN)? "expand down," : "expand up,"; } s += (seg.acc & X86.DESC.ACC.TYPE.ACCESSED)? "accessed" : "not accessed"; diff --git a/modules/pcjs/lib/disk.js b/modules/pcjs/lib/disk.js index 6a719e8b7..8a7ce7c47 100644 --- a/modules/pcjs/lib/disk.js +++ b/modules/pcjs/lib/disk.js @@ -811,7 +811,7 @@ Disk.prototype.doneLoad = function(sDiskFile, sDiskData, nErrorCode, sDiskPath) * pattern, if any, into a dword pattern. */ adw = []; - Component.assert((dwPattern & 0xff) == dwPattern); + if (DEBUG) this.assert((dwPattern & 0xff) == dwPattern); dwPattern = sector['pattern'] = (dwPattern | (dwPattern << 8) | (dwPattern << 16) | (dwPattern << 24)); sector['data'] = adw; } else { @@ -1152,7 +1152,7 @@ Disk.prototype.findDirtySectors = function(fAsync) var sectorNext = this.aDiskData[iCylinder][iHead][i]; if (!sectorNext.fDirty) break; var j = this.aDirtySectors.indexOf(sectorNext); - Component.assert(j >= 0, "dirty sector (" + iCylinder + ":" + iHead + ":" + sectorNext['sector'] + ") missing from aDirtySectors"); + if (DEBUG) this.assert(j >= 0, "dirty sector (" + iCylinder + ":" + iHead + ":" + sectorNext['sector'] + ") missing from aDirtySectors"); if (DEBUG) this.messageDebugger("Disk.findDirtySectors(" + iCylinder + ":" + iHead + ":" + sectorNext['sector'] + ")"); this.aDirtySectors.splice(j, 1); this.aDirtyTimestamps.splice(j, 1); @@ -1160,7 +1160,7 @@ Disk.prototype.findDirtySectors = function(fAsync) sectorNext.fDirty = false; nSectors++; } - Component.assert(!!abSectors.length, "no data for dirty sector (" + iCylinder + ":" + iHead + ":" + sector['sector'] + ")"); + if (DEBUG) this.assert(!!abSectors.length, "no data for dirty sector (" + iCylinder + ":" + iHead + ":" + sector['sector'] + ")"); var response = this.writeRemoteSectors(iCylinder, iHead, iSector, nSectors, abSectors, fAsync); return fAsync || response; } diff --git a/modules/pcjs/lib/fdc.js b/modules/pcjs/lib/fdc.js index d007caab3..2f42adc3c 100644 --- a/modules/pcjs/lib/fdc.js +++ b/modules/pcjs/lib/fdc.js @@ -1523,7 +1523,7 @@ FDC.prototype.unloadAllDrives = function(fDiscard) FDC.prototype.addDiskHistory = function(sDisketteName, sDiskettePath, disk) { var i; - Component.assert(!!sDiskettePath); + if (DEBUG) this.assert(!!sDiskettePath); for (i = 0; i < this.aDiskHistory.length; i++) { if (this.aDiskHistory[i][1] == sDiskettePath) { var nChanges = disk.restore(this.aDiskHistory[i][2]); @@ -1796,7 +1796,7 @@ FDC.prototype.doCmd = function() * Controller docs say that H should always match HD, so I assert that, but what if someone * made a mistake and didn't program them identically -- what would happen? Which should we honor? */ - Component.assert(h == bHead); + if (DEBUG) this.assert(h == bHead); r = drive.bSector = this.popCmd(FDC.TERMS.R); // R n = this.popCmd(FDC.TERMS.N); // N drive.nBytes = 128 << n; // 0 => 128, 1 => 256, 2 => 512, 3 => 1024 @@ -2005,7 +2005,7 @@ FDC.prototype.pushResults = function(drive, bCmd, bHead, c, h, r, n) */ FDC.prototype.popCmd = function(name) { - Component.assert((!this.regDataIndex || name !== undefined) && this.regDataIndex < this.regDataTotal); + if (DEBUG) this.assert((!this.regDataIndex || name !== undefined) && this.regDataIndex < this.regDataTotal); var bCmd = this.regDataArray[this.regDataIndex]; if (DEBUG && DEBUGGER && this.dbg && this.dbg.messageEnabled(Debugger.MESSAGE.FDC)) { var bCmdMasked = bCmd & FDC.REG_DATA.CMD.MASK; @@ -2372,7 +2372,7 @@ FDC.prototype.writeByte = function(drive, b) */ FDC.prototype.advanceSector = function(drive) { - Component.assert(drive.bCylinder < drive.nDiskCylinders); + if (DEBUG) this.assert(drive.bCylinder < drive.nDiskCylinders); drive.bSector++; var bSectorStart = 1; if (drive.bSector >= drive.nDiskSectors + bSectorStart) { diff --git a/modules/pcjs/lib/hdc.js b/modules/pcjs/lib/hdc.js index 5768c6942..50f3e4381 100644 --- a/modules/pcjs/lib/hdc.js +++ b/modules/pcjs/lib/hdc.js @@ -1399,7 +1399,7 @@ HDC.prototype.inATCData = function(port, addrFrom) * well, then the caller will get 0xff. */ bIn = this.readByte(this.drive); - Component.assert(bIn >= 0); + if (DEBUG) this.assert(bIn >= 0); /* * Now that we've supplied a full sector of data, see if the caller's expecting additional sectors; @@ -1420,7 +1420,7 @@ HDC.prototype.inATCData = function(port, addrFrom) /* * I shouldn't have to set BUSY (or DATA_REQ) again, because it should still be set, no? */ - Component.assert(!!(hdc.regStatus & HDC.ATC.STATUS.BUSY)); + if (DEBUG) this.assert(!!(hdc.regStatus & HDC.ATC.STATUS.BUSY)); } else { /* * TODO: It would be nice to be a bit more specific about the error (if any) that just occurred. @@ -1432,7 +1432,7 @@ HDC.prototype.inATCData = function(port, addrFrom) } }, false); } else { - Component.assert(this.drive.nBytes == 0); + if (DEBUG) this.assert(this.drive.nBytes == 0); this.regStatus = HDC.ATC.STATUS.READY | HDC.ATC.STATUS.SEEK_OK; } } @@ -1475,9 +1475,9 @@ HDC.prototype.outATCData = function(port, bOut, addrFrom) /* * I shouldn't have to set BUSY (or DATA_REQ) again, because it should still be set, no? */ - Component.assert(!!(this.regStatus & HDC.ATC.STATUS.BUSY)); + if (DEBUG) this.assert(!!(this.regStatus & HDC.ATC.STATUS.BUSY)); } else { - Component.assert(this.drive.nBytes == 0); + if (DEBUG) this.assert(this.drive.nBytes == 0); this.regStatus = HDC.ATC.STATUS.READY | HDC.ATC.STATUS.SEEK_OK; } } @@ -1868,8 +1868,8 @@ HDC.prototype.doATCommand = function() * The importance of SECCNT (nSectors) and DRVHD (nHeads) is controlling how multi-sector operations * advance to the next sector; see advanceSector(). */ - Component.assert(drive.nHeads == nHead + 1); - Component.assert(drive.nSectors == nSectors); + if (DEBUG) this.assert(drive.nHeads == nHead + 1); + if (DEBUG) this.assert(drive.nSectors == nSectors); drive.nHeads = nHead + 1; drive.nSectors = nSectors; fInterrupt = true; @@ -2525,7 +2525,7 @@ HDC.prototype.writeByte = function(drive, b) */ HDC.prototype.advanceSector = function(drive) { - Component.assert(drive.wCylinder < drive.nCylinders); + if (DEBUG) this.assert(drive.wCylinder < drive.nCylinders); drive.bSector++; var bSectorStart = (1 - drive.bSectorBias); if (drive.bSector >= drive.nSectors + bSectorStart) { diff --git a/modules/pcjs/lib/keyboard.js b/modules/pcjs/lib/keyboard.js index 315689d17..c1231218b 100644 --- a/modules/pcjs/lib/keyboard.js +++ b/modules/pcjs/lib/keyboard.js @@ -1332,8 +1332,10 @@ Keyboard.prototype.calcReleaseDelay = function(fRepeat) Keyboard.prototype.autoClear = function(notKeyCode) { if (this.prevCharDown && (notKeyCode === undefined || notKeyCode != this.prevCharDown)) { - if (DEBUG) this.messageDebugger("autoClear(" + this.prevCharDown + ")"); - Component.assert(this.aKeyTimers[this.prevCharDown]); + if (DEBUG) { + this.messageDebugger("autoClear(" + this.prevCharDown + ")"); + this.assert(this.aKeyTimers[this.prevCharDown]); + } clearTimeout(this.aKeyTimers[this.prevCharDown]); this.keySimulateUpOrDown(this.prevCharDown, false, Keyboard.SIMCODE.AUTOCLEAR); } diff --git a/modules/pcjs/lib/mem.js b/modules/pcjs/lib/mem.js index 572aa790e..081ebfba9 100644 --- a/modules/pcjs/lib/mem.js +++ b/modules/pcjs/lib/mem.js @@ -77,10 +77,6 @@ if (typeof module !== 'undefined') { * as many new BLOCK_SIZE Memory objects as the ranges require. Partial Memory blocks could be * supported in theory, but in practice, they're not. * - * NOTE: Since Memory blocks are low-level objects that have no UI requirements, they do not - * inherit from the Component class; so, if you want to use println(), for example, you must - * use the methods in the Debugger class. - * * Because Memory blocks now allow us to have a "sparse" address space, we could choose to * take the memory hit of allocating 4K arrays per block, where each element stores only one byte, * instead of the more frugal but slightly slower approach of allocating arrays of 32-bit dwords @@ -92,6 +88,10 @@ if (typeof module !== 'undefined') { * probably best, although not all JavaScript implementations support them (IE9 is probably the * only real outlier: it lacks typed arrays but otherwise has all the necessary HTML5 support). * + * WARNING: Since Memory blocks are low-level objects that have no UI requirements, + * they do not inherit from the Component class, so you should only use class methods + * of Component, such as Component.assert(), or Debugger methods if the Debugger is available. + * * @constructor * @param {number} addr of block (must be some multiple of bus.blockSize) * @param {number} [size] of block's buffer in bytes (0 for none); must be a multiple of 4 @@ -565,14 +565,14 @@ Memory.prototype = { this.resetReadAccess(); if (DEBUG) this.dbg.println("all read breakpoints removed from memory block " + str.toHex(this.addr)); } - Component.assert(this.cReadBreakpoints >= 0); + this.dbg.assert(this.cReadBreakpoints >= 0); } else { if (--this.cWriteBreakpoints === 0) { this.resetWriteAccess(); if (DEBUG) this.dbg.println("all write breakpoints removed from memory block " + str.toHex(this.addr)); } - Component.assert(this.cWriteBreakpoints >= 0); + this.dbg.assert(this.cWriteBreakpoints >= 0); } } } diff --git a/modules/pcjs/lib/state.js b/modules/pcjs/lib/state.js index e2e424a71..09ca096a2 100644 --- a/modules/pcjs/lib/state.js +++ b/modules/pcjs/lib/state.js @@ -40,11 +40,6 @@ if (typeof module !== 'undefined') { /** * State(component, sVersion, sSuffix) * - * @constructor - * @param {Component} component - * @param {string} [sVersion] is used to append a major version number to the key - * @param {string} [sSuffix] is used to append any additional suffixes to the key - * * State objects are used by components to save/restore their state. * * During a save operation, components add data to a State object via set(), @@ -52,6 +47,15 @@ if (typeof module !== 'undefined') { * * During a restore operation, the Computer component passes the results of each * data() call back to the originating component. + * + * WARNING: Since State objects are low-level objects that have no UI requirements, + * they do not inherit from the Component class, so you should only use class methods + * of Component, such as Component.assert(), or Debugger methods if the Debugger is available. + * + * @constructor + * @param {Component} component + * @param {string} [sVersion] is used to append a major version number to the key + * @param {string} [sSuffix] is used to append any additional suffixes to the key */ function State(component, sVersion, sSuffix) { this.id = component.id; @@ -226,10 +230,10 @@ State.prototype = { /** * value() * + * Use this instead of data() if you haven't called parse() yet. + * * @this {State} * @return {string} - * - * Use this instead of data() if you haven't called parse() yet. */ value: function() { return this[this.id]; @@ -246,12 +250,12 @@ State.prototype = { /** * load(s) * + * WARNING: Make sure you follow this call with either a call to parse() or unload(), + * because any stringified data that we've loaded isn't usable until it's been parsed. + * * @this {State} * @param {Object|string|null} [s] * @return {boolean} true if state exists in localStorage, false if not - * - * WARNING: Make sure you follow this call with either a call to parse() or unload(), - * because any stringified data that we've loaded isn't usable until it's been parsed. */ load: function(s) { if (s) { @@ -279,12 +283,12 @@ State.prototype = { /** * parse() * - * @this {State} - * @return {boolean} true if successful, false if error - * * This completes the load() operation, by parsing what was loaded, on the assumption there * might be some benefit to deferring parsing until we've given the user a chance to confirm. * Otherwise, load() could have just as easily done this, too. + * + * @this {State} + * @return {boolean} true if successful, false if error */ parse: function() { var fSuccess = true; @@ -337,12 +341,12 @@ State.prototype = { /** * unload(parms) * - * @this {State} - * @param {Object} [parms] - * * This discards any data saved via set() or loaded via load(), creating an empty State object. * Note that you have to follow this call with an explicit call to store() if you want to remove * the state from localStorage as well. + * + * @this {State} + * @param {Object} [parms] */ unload: function(parms) { this[this.id] = {}; @@ -352,11 +356,11 @@ State.prototype = { /** * clear(fAll) * - * @this {State} - * @param {boolean} [fAll] true to unconditionally clear ALL localStorage for the current domain - * * This unloads the current state, and then clears ALL localStorage for the current machine, * independent of version, to reduce the chance of orphaned states wasting part of our limited allocation. + * + * @this {State} + * @param {boolean} [fAll] true to unconditionally clear ALL localStorage for the current domain */ clear: function(fAll) { this.unload(); @@ -374,10 +378,10 @@ State.prototype = { /** * messageDebugger(sMessage) * + * This is a combination of the Debugger's messageEnabled(MESSAGE_STATE) and message() functions, for convenience. + * * @this {State} * @param {string} sMessage is any caller-defined message string - * - * This is a combination of the Debugger's messageEnabled(MESSAGE_STATE) and message() functions, for convenience. */ messageDebugger: function(sMessage) { if (DEBUGGER && this.dbg) { diff --git a/modules/pcjs/lib/video.js b/modules/pcjs/lib/video.js index 9ae523a38..86c6db8f6 100644 --- a/modules/pcjs/lib/video.js +++ b/modules/pcjs/lib/video.js @@ -478,8 +478,7 @@ Video.aEGAMonitorSwitches = { * to enter a "mode" that has no BIOS counterpart (eg, non-standard combinations of frame buffer address, * memory access modes, fonts, display regions, etc). Our hardware emulation routines will cope with those * situations as best they can (and when they don't, it should be considered a bug if some application is - * broken as a result), but realistically, this is never going to be a completely accurate hardware emulation - * of any video card. + * broken as a result), but realistically, this is never going to be a 100% accurate hardware emulation. */ Video.MODES = {}; Video.MODES.CGA_40X25_BW = 0; @@ -721,7 +720,9 @@ Video.aEGADWToByte[0x80808080] = 0xf; * Creates an object representing an initial video card state; * can also restore a video card from state data created by saveCard(). * - * See new Card(). + * WARNING: Since Card objects are low-level objects that have no UI requirements, + * they do not inherit from the Component class, so you should only use class methods + * of Component, such as Component.assert(), or Debugger methods if the Debugger is available. * * @constructor * @param {Video} [video] @@ -2694,6 +2695,7 @@ Video.prototype.onROMLoad = function(abROM) /** * getCardColors(nBitsPerPixel) * + * @this {Video} * @param {number} [nBitsPerPixel] * @returns {Array} */ @@ -2746,7 +2748,7 @@ Video.prototype.getCardColors = function(nBitsPerPixel) return Video.aCGAColors; } - Component.assert(this.cardColor === this.cardEGA); + if (DEBUG) this.assert(this.cardColor === this.cardEGA); var aRegs = (this.cardEGA.aATCRegs[15] != null? this.cardEGA.aATCRegs : Video.aEGAPalDef); for (var i = 0; i < this.aRGB.length; i++) { @@ -4062,7 +4064,7 @@ Video.prototype.updateScreenText = function(addrScreen, addrScreenLimit, iCell, if (iCell == this.iCellCursor) { data |= ((this.cBlinks & 0x1)? (Video.ATTRS.DRAW_CURSOR << 8) : 0); } - Component.assert(iCell < this.aCellCache.length); + if (DEBUG) this.assert(iCell < this.aCellCache.length); dataCache = this.aCellCache[iCell]; if (dataCache != data) { var col = iCell % this.nCols; @@ -4103,7 +4105,7 @@ Video.prototype.updateScreenGraphicsCGA = function(addrScreen, addrScreenLimit) var xDirty = this.nCols, xMaxDirty = 0, yDirty = this.nRows, yMaxDirty = 0; while (addr < addrScreenLimit) { data = this.bus.getWordDirect(addr); - Component.assert(iCell < this.aCellCache.length); + if (DEBUG) this.assert(iCell < this.aCellCache.length); dataCache = this.aCellCache[iCell]; if (dataCache === data) { x += nPixelsPerCell; @@ -4181,9 +4183,9 @@ Video.prototype.updateScreenGraphicsEGA = function(addrScreen, addrScreenLimit) var xDirty = this.nCols, xMaxDirty = 0, yDirty = this.nRows, yMaxDirty = 0; while (addr < addrScreenLimit) { var idw = addr++ - this.addrBuffer; - Component.assert(idw >= 0 && idw < adwMemory.length); + if (DEBUG) this.assert(idw >= 0 && idw < adwMemory.length); data = adwMemory[idw]; - Component.assert(iCell < this.aCellCache.length); + if (DEBUG) this.assert(iCell < this.aCellCache.length); dataCache = this.aCellCache[iCell]; if (dataCache === data) { x += nPixelsPerCell; @@ -4206,10 +4208,10 @@ Video.prototype.updateScreenGraphicsEGA = function(addrScreen, addrScreenLimit) */ if (dwPixel < 0) dwPixel += 0x100000000; /* - * Since assertions don't fix problems (only catch them, and only in DEBUG builds), I'm also insuring + * Since assertions don't fix problems (only catch them, and only in DEBUG builds), I'm also ensuring * that bPixel will always default to 0 if an undefined value ever slips through again. */ - Component.assert(Video.aEGADWToByte[dwPixel] !== undefined); + if (DEBUG) this.assert(Video.aEGADWToByte[dwPixel] !== undefined); var bPixel = Video.aEGADWToByte[dwPixel] || 0; this.setPixel(this.imageScreenBuffer, x++, y, aPixelColors[bPixel]); data <<= 1; @@ -4948,7 +4950,7 @@ Video.prototype.inCardStatus = function(card, addrFrom) * monitors the STATUS1 diagnostic bits, waiting for those palette bits to show up. It turns out, however, * that we can easily fool the EGA BIOS by simply toggling the diagnostic bits. So we take the easy way out. * - * TODO: Faithful emulation of these bits is certainly doable, so consider doing it at some point. + * TODO: Faithful emulation of these bits is certainly doable, so consider doing that at some point. */ b |= ((card.statusReg & Card.STATUS1.DIAGNOSTIC) ^ Card.STATUS1.DIAGNOSTIC); diff --git a/modules/pcjs/lib/x86.js b/modules/pcjs/lib/x86.js index a731d4ed4..6d543d9cc 100644 --- a/modules/pcjs/lib/x86.js +++ b/modules/pcjs/lib/x86.js @@ -81,15 +81,15 @@ var X86 = { }, DESC: { // Descriptor Table Entry LIMIT: { - OFFSET: 0x0 + OFFSET: 0x0 }, BASE: { - OFFSET: 0x2 + OFFSET: 0x2 }, ACC: { // bit definitions for the access word (offset 0x4) - OFFSET: 0x4, - BASE1623: 0x00ff, - MASK: 0xff00, + OFFSET: 0x4, + BASE1623: 0x00ff, + MASK: 0xff00, TYPE: { MASK: 0x1f00, SEG: 0x1000, @@ -97,12 +97,12 @@ var X86 = { /* * The following bits apply only when SEG is set */ - CODE: 0x0800, // set for CODE, clear for DATA - ACCESSED: 0x0100, // set if accessed, clear if not accessed - READABLE: 0x0200, // CODE: set if readable, clear if exec-only - WRITEABLE: 0x0200, // DATA: set if writable, clear if read-only - CONFORMING: 0x0400, // CODE: set if conforming, clear if not - EXPDOWN: 0x0400, // DATA: set if expand-down, clear if not + CODE: 0x0800, // set for CODE, clear for DATA + ACCESSED: 0x0100, // set if accessed, clear if not accessed + READABLE: 0x0200, // CODE: set if readable, clear if exec-only + WRITABLE: 0x0200, // DATA: set if writable, clear if read-only + CONFORMING: 0x0400, // CODE: set if conforming, clear if not + EXPDOWN: 0x0400, // DATA: set if expand-down, clear if not /* * The following are all the possible (valid) types (well, except for the variations * of DATA and CODE where the ACCESSED bit (0x0100) may also be set) @@ -116,23 +116,27 @@ var X86 = { GATE_INT: 0x0600, GATE_TRAP: 0x0700, DATA_READONLY: 0x1000, - DATA_WRITEABLE: 0x1200, + DATA_WRITABLE: 0x1200, DATA_EXPDOWN_READONLY: 0x1400, - DATA_EXPDOWN_WRITEABLE: 0x1600, + DATA_EXPDOWN_WRITABLE: 0x1600, CODE_EXECONLY: 0x1800, CODE_READABLE: 0x1a00, CODE_CONFORMING_EXECONLY: 0x1c00, CODE_CONFORMING_READABLE: 0x1e00 }, DPL: { - MASK: 0x6000, - SHIFT: 13 + MASK: 0x6000, + SHIFT: 13 }, - PRESENT: 0x8000 + PRESENT: 0x8000 }, EXT: { // descriptor extension word (reserved on the 80286; "must be zero") - OFFSET: 0x6, - MASK: 0xffff + OFFSET: 0x6, + LIMIT1619: 0x000f, + AVAIL: 0x0010, // NOTE: set in various descriptors in OS/2 + DEFSIZE: 0x0040, // clear if default operand/address size is 16-bit, set if 32-bit + GRANULARITY: 0x0080, // clear if limit is bytes, set if limit is 4Kb pages + BASE2431: 0xff00 } }, /* diff --git a/modules/pcjs/lib/x86cpu.js b/modules/pcjs/lib/x86cpu.js index 41df2df77..a7f8a7384 100644 --- a/modules/pcjs/lib/x86cpu.js +++ b/modules/pcjs/lib/x86cpu.js @@ -686,15 +686,15 @@ X86CPU.prototype.initProcessor = function() * opOUTSw, opENTER, and opLEAVE. */ this.nShiftCountMask = 0x1f; // on newer processors, all shift counts are MOD 32 - this.aOps[0x0F] = X86Help.opInvalid; + this.aOps[0x0F] = X86OpXX.opInvalid; this.aOps[X86.OPCODE.PUSHA] = X86OpXX.opPUSHA; this.aOps[X86.OPCODE.POPA] = X86OpXX.opPOPA; this.aOps[X86.OPCODE.BOUND] = X86OpXX.opBOUND; - this.aOps[0x63] = X86Help.opInvalid; - this.aOps[0x64] = X86Help.opInvalid; - this.aOps[0x65] = X86Help.opInvalid; - this.aOps[0x66] = X86Help.opInvalid; - this.aOps[0x67] = X86Help.opInvalid; + this.aOps[0x63] = X86OpXX.opInvalid; + this.aOps[0x64] = X86OpXX.opInvalid; + this.aOps[0x65] = X86OpXX.opInvalid; + this.aOps[0x66] = X86OpXX.opInvalid; + this.aOps[0x67] = X86OpXX.opInvalid; this.aOps[X86.OPCODE.PUSH16]= X86OpXX.opPUSH16; this.aOps[X86.OPCODE.IMUL16]= X86OpXX.opIMUL16; this.aOps[X86.OPCODE.PUSH8] = X86OpXX.opPUSH8; @@ -787,6 +787,13 @@ X86CPU.prototype.resetRegs = function() this.descIDT = {off: 0, sel: 0, acc: 0, maskPS: -1}; this.nIOPL = 0; // this should be set before the first setPS() call + /* + * This is set by opHelpFault() and reset (to -1) by resetRegs() and opIRET(); its initial purpose is to + * "help" opHelpFault() determine when a nested fault should be converted into either a double-fault + * (DF_FAULT) or a triple-fault (ie, a processor reset). + */ + this.nFault = -1; + /* * Segment registers used to be defined as separate variables (eg, regCS and regCS0 stored the * segment number and base physical address, respectively), but all segment registers are now defined @@ -807,6 +814,10 @@ X86CPU.prototype.resetRegs = function() * * while the LDTR and TR are stored as special segment registers: segLDT and segTSS. * + * So, yes, our GDTR and IDTR "registers" differ from other segment registers in that we do NOT record + * the 16-bit limit specified by the LGDT or LIDT instructions; instead, we immediately calculate the limiting + * address and record that instead. + * * In addition to different CS:IP reset values, the CS base address must be set to the top of the 16Mb * address space rather than the top of the first 1Mb (which is why the MODEL_5170 ROM must be addressable * at both 0x0F0000 and 0xFF0000; see the ROM component's "alias" parameter). @@ -1147,9 +1158,9 @@ X86CPU.prototype.setMemoryEnabled = function() */ X86CPU.prototype.verifyMemoryEnabled = function() { - Component.assert(!(this.regAX & 0xffff0000) && !(this.regBX & 0xffff0000) && !(this.regCX & 0xffff0000) && !(this.regDX & 0xffff0000)); - Component.assert(!(this.regSI & 0xffff0000) && !(this.regDI & 0xffff0000) && !(this.regBP & 0xffff0000) && !(this.regSP & 0xffff0000)); - Component.assert((this.getEAByte == this.getEAByteEnabled && this.getEAWord == this.getEAWordEnabled && this.modEAByte == this.modEAByteEnabled && this.modEAWord == this.modEAWordEnabled && this.setEAByte == this.setEAByteEnabled && this.setEAWord == this.setEAWordEnabled), "verifyMemoryEnabled() failed"); + this.assert(!(this.regAX & 0xffff0000) && !(this.regBX & 0xffff0000) && !(this.regCX & 0xffff0000) && !(this.regDX & 0xffff0000)); + this.assert(!(this.regSI & 0xffff0000) && !(this.regDI & 0xffff0000) && !(this.regBP & 0xffff0000) && !(this.regSP & 0xffff0000)); + this.assert((this.getEAByte == this.getEAByteEnabled && this.getEAWord == this.getEAWordEnabled && this.modEAByte == this.modEAByteEnabled && this.modEAWord == this.modEAWordEnabled && this.setEAByte == this.setEAByteEnabled && this.setEAWord == this.setEAWordEnabled), "verifyMemoryEnabled() failed"); }; /** @@ -1176,7 +1187,7 @@ X86CPU.prototype.getSeg = function(sName) * HACK: We return a fake segment register object in which only the base physical address is valid, * because that's all the caller provided (ie, we must be restoring from an older state). */ - Component.assert(typeof sName == "number"); + if (DEBUG) this.assert(typeof sName == "number"); return [0, sName, 0, 0, ""]; } }; @@ -1193,12 +1204,14 @@ X86CPU.prototype.getSeg = function(sName) * * @this {X86CPU} * @param {number} nIDT - * @return {boolean} true if successful, false if not + * @return {boolean} true if successful, false if not (all failure cases currently limited to protected mode) */ X86CPU.prototype.loadIDTEntry = function(nIDT) { - Component.assert(nIDT >= 0 && nIDT < 256); var offIDT; + + if (DEBUG) this.assert(nIDT >= 0 && nIDT < 256); + if (this.regMSW & X86.MSW.PE) { offIDT = this.addrIDT + (nIDT << 3); if (offIDT + 7 <= this.addrIDTLimit) { @@ -1213,20 +1226,26 @@ X86CPU.prototype.loadIDTEntry = function(nIDT) this.descIDT.maskPS = ~(X86.PS.NT | X86.PS.TF); break; default: + if (DEBUG) this.assert(false); return false; } return true; } - } else { - offIDT = this.addrIDT + (nIDT << 2); - if (offIDT + 7 <= this.addrIDTLimit) { - this.descIDT.off = this.getWord(offIDT); - this.descIDT.sel = this.getWord(offIDT + 2); - this.descIDT.maskPS = ~(X86.PS.TF | X86.PS.IF); - return true; - } + return false; } - return false; + if (DEBUG) this.assert(!this.addrIDT && this.addrIDTLimit == 0x03FF); + /* + * Intel documentation for INT/INTO under "REAL ADDRESS MODE EXCEPTIONS" says: + * + * "[T]he 80286 will shut down if the SP = 1, 3, or 5 before executing the INT or INTO instruction--due to lack of stack space" + * + * Huh? Why would real-mode care? See http://localhost:8088/pubs/pc/reference/intel/80286/progref/#page-260 + */ + offIDT = this.addrIDT + (nIDT << 2); + this.descIDT.off = this.getWord(offIDT); + this.descIDT.sel = this.getWord(offIDT + 2); + this.descIDT.maskPS = ~(X86.PS.TF | X86.PS.IF); + return true; }; /** @@ -1323,7 +1342,7 @@ X86CPU.prototype.setIP = function(off) */ X86CPU.prototype.setCSIP = function(off, sel) { - Component.assert((off & 0xffff) == off); + if (DEBUG) this.assert((off & 0xffff) == off); this.regEIP = this.segCS.load(sel) + (this.regIP = off); if (PREFETCH) this.flushPrefetch(this.regEIP); }; @@ -1655,7 +1674,7 @@ X86CPU.prototype.setPS = function(regPS) /* * Assert that all requested flag bits now agree with our simulated (PS_INDIRECT) bits */ - Component.assert((regPS & X86.PS.INDIRECT) == (this.getPS() & X86.PS.INDIRECT)); + if (DEBUG) this.assert((regPS & X86.PS.INDIRECT) == (this.getPS() & X86.PS.INDIRECT)); if (this.regPS & X86.PS.TF) { this.intFlags |= X86.INTFLAG.TRAP; @@ -2043,8 +2062,8 @@ X86CPU.prototype.getBytePrefetch = function(addr) if (!EAFUNCS && (this.opFlags & X86.OPFLAG.NOREAD)) return 0; var b; if (!this.cbPrefetchQueued) { - if (MAXDEBUG) Component.assert(addr == this.addrPrefetchHead, "X86CPU.getBytePrefetch(" + str.toHex(addr) + "): invalid head address (" + str.toHex(this.addrPrefetchHead) + ")"); - if (MAXDEBUG) Component.assert(this.iPrefetchTail == this.iPrefetchHead, "X86CPU.getBytePrefetch(" + str.toHex(addr) + "): head (" + this.iPrefetchHead + ") does not match tail (" + this.iPrefetchTail + ")"); + if (MAXDEBUG) this.assert(addr == this.addrPrefetchHead, "X86CPU.getBytePrefetch(" + str.toHex(addr) + "): invalid head address (" + str.toHex(this.addrPrefetchHead) + ")"); + if (MAXDEBUG) this.assert(this.iPrefetchTail == this.iPrefetchHead, "X86CPU.getBytePrefetch(" + str.toHex(addr) + "): head (" + this.iPrefetchHead + ") does not match tail (" + this.iPrefetchTail + ")"); this.fillPrefetch(1); this.nBusCycles += 4; /* @@ -2061,7 +2080,7 @@ X86CPU.prototype.getBytePrefetch = function(addr) } b = this.aPrefetch[this.iPrefetchTail] & 0xff; if (MAXDEBUG) this.messageDebugger(" getBytePrefetch[" + this.iPrefetchTail + "]: " + str.toHex(addr) + ":" + str.toHexByte(b)); - if (MAXDEBUG) Component.assert(addr == (this.aPrefetch[this.iPrefetchTail] >> 8), "X86CPU.getBytePrefetch(" + str.toHex(addr) + "): invalid tail address (" + str.toHex(this.aPrefetch[this.iPrefetchTail] >> 8) + ")"); + if (MAXDEBUG) this.assert(addr == (this.aPrefetch[this.iPrefetchTail] >> 8), "X86CPU.getBytePrefetch(" + str.toHex(addr) + "): invalid tail address (" + str.toHex(this.aPrefetch[this.iPrefetchTail] >> 8) + ")"); this.iPrefetchTail = (this.iPrefetchTail + 1) & X86CPU.PREFETCH.MASK; this.cbPrefetchQueued--; return b; @@ -2220,7 +2239,7 @@ X86CPU.prototype.popWord = function() */ X86CPU.prototype.pushWord = function(w) { - Component.assert((w & 0xffff) == w); + if (DEBUG) this.assert((w & 0xffff) == w); this.setSOWord(this.segSS, (this.regSP = (this.regSP - 2) & 0xffff), w); }; @@ -2295,7 +2314,7 @@ X86CPU.prototype.pushWord = function(w) */ X86CPU.prototype.checkINTR = function() { - Component.assert(this.intFlags); + if (DEBUG) this.assert(this.intFlags); if (!(this.opFlags & X86.OPFLAG.NOINTR)) { if ((this.intFlags & X86.INTFLAG.INTR) && (this.regPS & X86.PS.IF)) { var nIDT = this.chipset.getIRRVector(); diff --git a/modules/pcjs/lib/x86grps.js b/modules/pcjs/lib/x86grps.js index db5581db4..14ea3ab1c 100644 --- a/modules/pcjs/lib/x86grps.js +++ b/modules/pcjs/lib/x86grps.js @@ -35,6 +35,7 @@ if (typeof module !== 'undefined') { var X86 = require("./x86"); var X86Help = require("./x86help"); + var X86OpXX = require("./x86opxx"); var Debugger = require("./debugger"); } @@ -1239,7 +1240,7 @@ var X86Grps = { * @return {number} */ opGrpInvalid: function(dst, src) { - X86Help.opInvalid.call(this); + X86OpXX.opInvalid.call(this); return dst; }, /** @@ -1249,7 +1250,7 @@ var X86Grps = { * @return {number} */ opGrpUndefined: function(dst, src) { - X86Help.opUndefined.call(this); + X86OpXX.opUndefined.call(this); return dst; } }; diff --git a/modules/pcjs/lib/x86help.js b/modules/pcjs/lib/x86help.js index 2520ac387..b786c5777 100644 --- a/modules/pcjs/lib/x86help.js +++ b/modules/pcjs/lib/x86help.js @@ -34,6 +34,7 @@ if (typeof module !== 'undefined') { var X86 = require("./x86"); + var X86OpXX = require("./x86opxx"); var Debugger = require("./debugger"); } @@ -176,7 +177,7 @@ var X86Help = { */ opHelpLEA: function(dst, src) { if (this.regEA < 0) { - X86Help.opUndefined.call(this); + X86OpXX.opUndefined.call(this); return dst; } this.nStepCycles -= this.CYCLES.nOpCyclesLEA; @@ -190,7 +191,7 @@ var X86Help = { */ opHelpLDS: function(dst, src) { if (this.regEA < 0) { - X86Help.opUndefined.call(this); + X86OpXX.opUndefined.call(this); return dst; } this.setDS(this.getWord(this.regEA + 2)); @@ -205,7 +206,7 @@ var X86Help = { */ opHelpLES: function(dst, src) { if (this.regEA < 0) { - X86Help.opUndefined.call(this); + X86OpXX.opUndefined.call(this); return dst; } this.setES(this.getWord(this.regEA + 2)); @@ -223,7 +224,7 @@ var X86Help = { /* * Generate a #UD fault (INT 0x06: Undefined Opcode) if src is not a memory operand. */ - X86Help.opInvalid.call(this); + X86OpXX.opInvalid.call(this); return dst; } /* @@ -237,7 +238,7 @@ var X86Help = { /* * The INT 0x05 handler must be called with CS:IP pointing to the BOUND instruction. * - * TODO: Determine the cycle impact when a BOUND exception is triggered, over and above nOpCyclesBound. + * TODO: Determine the cycle cost when a BOUND exception is triggered, over and above nOpCyclesBound. */ this.setIP(this.opEA - this.segCS.base); X86Help.opHelpINT.call(this, X86.EXCEPTION.BOUND_ERR, null, 0); @@ -416,6 +417,11 @@ var X86Help = { * @param {number} nCycles (in addition to the default of nOpCyclesInt) */ opHelpINT: function(nIDT, nError, nCycles) { + /* + * TODO: We assess the cycle cost up front, because if loadIDTEntry() fails and we end up in opHelpFault(), + * no cost may get assessed. opHelpFault() needs to determine an appropriate cycle cost. + */ + this.nStepCycles -= this.CYCLES.nOpCyclesInt + nCycles; if (this.loadIDTEntry(nIDT)) { this.pushWord(this.getPS()); this.regPS &= this.descIDT.maskPS; @@ -423,11 +429,9 @@ var X86Help = { this.pushWord(this.regIP); if (nError != null) this.pushWord(nError); this.setCSIP(this.descIDT.off, this.descIDT.sel); - this.nStepCycles -= this.CYCLES.nOpCyclesInt + nCycles; + return; } - /* - * TODO: Now what? - */ + X86Help.opHelpFault.call(this, X86.EXCEPTION.GP_FAULT, (nIDT << 3) | X86.ERRCODE.IDT | X86.ERRCODE.EXT, true); }, /** * opHelpLMSW(w) @@ -460,7 +464,7 @@ var X86Help = { opHelpDIVOverflow: function() { this.setIP(this.opEA - this.segCS.base); /* - * TODO: Determine the proper cycle count + * TODO: Determine the proper cycle cost. */ X86Help.opHelpINT.call(this, X86.EXCEPTION.DIV_ERR, null, 2); }, @@ -471,34 +475,61 @@ var X86Help = { * @param {boolean} [fHalt] will halt the CPU if true *and* a Debugger is loaded */ opHelpFault: function(nFault, nError, fHalt) { + var fFault = false; + if (this.model >= X86.MODEL_80186) { + if (this.nFault < 0) { + /* + * Single-fault (error code is passed through, and the original instruction is restartable) + */ + this.setIP(this.opEA - this.segCS.base); + fFault = 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; + fFault = 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; + } + } + X86Help.opHelpFaultMessage.call(this, nFault, nError, fHalt); + if (fFault) X86Help.opHelpINT.call(this, this.nFault = nFault, nError, 0); + }, + /** + * @this {X86CPU} + * @param {number} nFault + * @param {number} [nError] + * @param {boolean} [fHalt] will halt the CPU if true *and* a Debugger is loaded + */ + opHelpFaultMessage: function(nFault, nError, fHalt) { if (DEBUGGER && this.dbg) { /* * NOTE: By using Debugger.message(), we have the option of setting "m halt on" and halting on messages like this. */ + var bOpcode = this.bus.getByteDirect(this.regEIP); if (this.dbg.messageEnabled(Debugger.MESSAGE.CPU)) { - this.dbg.message("Fault 0x" + str.toHexByte(nFault) + (nError != null? " (0x" + str.toHexWord(nError) + ")" : "") + " on opcode 0x" + str.toHexByte(this.bus.getByteDirect(this.regEIP)) + " at " + str.toHexAddr(this.regIP, this.segCS.sel)); + this.dbg.message("Fault 0x" + str.toHexByte(nFault) + (nError != null? " (0x" + str.toHexWord(nError) + ")" : "") + " on opcode 0x" + str.toHexByte(bOpcode) + " at " + str.toHexAddr(this.regIP, this.segCS.sel) + " (%" + str.toHex(this.regEIP) + ")"); } - if (fHalt) this.stopCPU(); + /* + * 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 INT 0x10 BIOS calls for each character, and each call 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 the INT3 (0xCC) opcode, + * and so whenever we see that opcode, we ignore the caller's fHalt flag. + */ + if (fHalt && bOpcode != X86.OPCODE.INT3) this.dbg.stopCPU(); } - if (this.model >= X86.MODEL_80186) { - this.setIP(this.opEA - this.segCS.base); - X86Help.opHelpINT.call(this, nFault, nError, 0); - } - }, - /** - * @this {X86CPU} - */ - opInvalid: function() { - X86Help.opHelpFault.call(this, X86.EXCEPTION.UD_FAULT); - this.stopCPU(); - }, - /** - * @this {X86CPU} - */ - opUndefined: function() { - this.setIP(this.opEA - this.segCS.base); - this.setError("Undefined opcode 0x" + str.toHexByte(this.bus.getByteDirect(this.regEIP)) + " at " + str.toHexAddr(this.regIP, this.segCS.sel)); - this.stopCPU(); } }; diff --git a/modules/pcjs/lib/x86op0f.js b/modules/pcjs/lib/x86op0f.js index a1e99d9ac..ccd03b6b8 100644 --- a/modules/pcjs/lib/x86op0f.js +++ b/modules/pcjs/lib/x86op0f.js @@ -37,6 +37,7 @@ if (typeof module !== 'undefined') { var X86Grps = require("./x86grps"); var X86Help = require("./x86help"); var X86Mods = require("./x86mods"); + var X86OpXX = require("./x86opxx"); } var X86Op0F = { @@ -218,7 +219,7 @@ var X86Op0F = { if (this.segVER.load(dst, true) >= 0) { /* * Verify that this is a readable segment; that is, of these four combinations (code+readable, - * code+nonreadable, data+writeable, date+nonwriteable), make sure we're not the second combination. + * code+nonreadable, data+writable, date+nonwritable), make sure we're not the second combination. */ if ((this.segVER.acc & (X86.DESC.ACC.TYPE.READABLE | X86.DESC.ACC.TYPE.CODE)) != X86.DESC.ACC.TYPE.CODE) { /* @@ -253,9 +254,9 @@ var X86Op0F = { this.nStepCycles -= (14 + (this.regEA < 0? 0 : 2)); if (this.segVER.load(dst, true) >= 0) { /* - * Verify that this is a writeable data segment + * Verify that this is a writable data segment */ - if ((this.segVER.acc & (X86.DESC.ACC.TYPE.WRITEABLE | X86.DESC.ACC.TYPE.CODE)) == X86.DESC.ACC.TYPE.WRITEABLE) { + if ((this.segVER.acc & (X86.DESC.ACC.TYPE.WRITABLE | X86.DESC.ACC.TYPE.CODE)) == X86.DESC.ACC.TYPE.WRITABLE) { /* * DPL must be greater than or equal to (have less or the same privilege as) both the current * privilege level and the selector's RPL. @@ -277,7 +278,7 @@ var X86Op0F = { */ opSGDT: function(dst, src) { if (this.regEA < 0) { - X86Help.opInvalid.call(this); + X86OpXX.opInvalid.call(this); } else { /* * We don't need to setWord() the first word of the operand, because the ModRM group decoder that calls @@ -331,7 +332,7 @@ var X86Op0F = { */ opSIDT: function(dst, src) { if (this.regEA < 0) { - X86Help.opInvalid.call(this); + X86OpXX.opInvalid.call(this); } else { /* * We don't need to setWord() the first word of the operand, because the ModRM group decoder that calls @@ -364,7 +365,7 @@ var X86Op0F = { */ opLGDT: function(dst, src) { if (this.regEA < 0) { - X86Help.opInvalid.call(this); + X86OpXX.opInvalid.call(this); } else { this.addrGDT = this.getWord(this.regEA + 2) | (this.getByte(this.regEA + 4) << 16); this.addrGDTLimit = this.addrGDT + dst; @@ -387,7 +388,7 @@ var X86Op0F = { */ opLIDT: function(dst, src) { if (this.regEA < 0) { - X86Help.opInvalid.call(this); + X86OpXX.opInvalid.call(this); } else { this.addrIDT = this.getWord(this.regEA + 2) | (this.getByte(this.regEA + 4) << 16); this.addrIDTLimit = this.addrIDT + dst; @@ -422,73 +423,73 @@ var X86Op0F = { X86Op0F.aOps0F = [ X86Op0F.opGRP6, X86Op0F.opGRP7, X86Op0F.opLAR, X86Op0F.opLSL, // 0x00-0x03 - X86Help.opUndefined, X86Op0F.opLOADALL, X86Help.opUndefined, X86Help.opUndefined, // 0x04-0x07 + X86OpXX.opUndefined, X86Op0F.opLOADALL, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x04-0x07 /* * On all processors (except the 8086/8088, of course), 0x0F,0x0B is also referred to as "UD2": an * instruction guaranteed to raise a #UD (Invalid Opcode) exception (INT 0x06) on all future x86 processors. */ - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opInvalid, // 0x08-0x0B - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x0C-0x0F - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x10-0x13 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x14-0x17 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x18-0x1B - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x1C-0x1F - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x20-0x23 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x24-0x27 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x28-0x2B - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x2C-0x2F - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x30-0x33 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x34-0x37 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x38-0x3B - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x3C-0x3F - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x40-0x43 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x44-0x47 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x48-0x4B - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x4C-0x4F - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x50-0x53 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x54-0x57 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x58-0x5B - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x5C-0x5F - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x60-0x63 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x64-0x67 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x68-0x6B - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x6C-0x6F - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x70-0x73 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x74-0x77 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x78-0x7B - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x7C-0x7F - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x80-0x83 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x84-0x87 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x88-0x8B - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x8C-0x8F - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x90-0x93 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x94-0x97 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x98-0x9B - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x9C-0x9F - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xA0-0xA3 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xA4-0xA7 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xA8-0xAB - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xAC-0xAF - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xB0-0xB3 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xB4-0xB7 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xB8-0xBB - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xBC-0xBF - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xC0-0xC3 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xC4-0xC7 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xC8-0xCB - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xCC-0xCF - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xD0-0xD3 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xD4-0xD7 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xD8-0xDB - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xDC-0xDF - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xE0-0xE3 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xE4-0xE7 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xE8-0xEB - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xEC-0xEF - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xF0-0xF3 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xF4-0xF7 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xF8-0xFB - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined // 0xFC-0xFF + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opInvalid, // 0x08-0x0B + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x0C-0x0F + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x10-0x13 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x14-0x17 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x18-0x1B + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x1C-0x1F + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x20-0x23 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x24-0x27 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x28-0x2B + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x2C-0x2F + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x30-0x33 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x34-0x37 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x38-0x3B + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x3C-0x3F + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x40-0x43 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x44-0x47 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x48-0x4B + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x4C-0x4F + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x50-0x53 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x54-0x57 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x58-0x5B + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x5C-0x5F + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x60-0x63 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x64-0x67 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x68-0x6B + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x6C-0x6F + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x70-0x73 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x74-0x77 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x78-0x7B + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x7C-0x7F + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x80-0x83 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x84-0x87 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x88-0x8B + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x8C-0x8F + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x90-0x93 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x94-0x97 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x98-0x9B + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0x9C-0x9F + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xA0-0xA3 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xA4-0xA7 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xA8-0xAB + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xAC-0xAF + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xB0-0xB3 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xB4-0xB7 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xB8-0xBB + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xBC-0xBF + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xC0-0xC3 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xC4-0xC7 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xC8-0xCB + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xCC-0xCF + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xD0-0xD3 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xD4-0xD7 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xD8-0xDB + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xDC-0xDF + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xE0-0xE3 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xE4-0xE7 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xE8-0xEB + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xEC-0xEF + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xF0-0xF3 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xF4-0xF7 + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, // 0xF8-0xFB + X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined, X86OpXX.opUndefined // 0xFC-0xFF ]; /* diff --git a/modules/pcjs/lib/x86opxx.js b/modules/pcjs/lib/x86opxx.js index ab0d4402f..23f1588d5 100644 --- a/modules/pcjs/lib/x86opxx.js +++ b/modules/pcjs/lib/x86opxx.js @@ -1741,7 +1741,7 @@ var X86OpXX = { this.regMD16 = this.segDS.sel; break; default: - X86Help.opUndefined.call(this); + X86OpXX.opUndefined.call(this); return; } /* @@ -1787,7 +1787,7 @@ var X86OpXX = { break; default: if (this.model >= X86.MODEL_80286) { - X86Help.opInvalid.call(this); + X86OpXX.opInvalid.call(this); return; } switch(reg) { @@ -1987,7 +1987,7 @@ var X86OpXX = { /* * TODO: Implement */ - X86Help.opUndefined.call(this); + X86OpXX.opUndefined.call(this); }, /** * @this {X86CPU} @@ -2032,7 +2032,7 @@ var X86OpXX = { if (ah & X86.PS.ZF) this.setZF(); else this.clearZF(); if (ah & X86.PS.SF) this.setSF(); else this.clearSF(); this.nStepCycles -= this.CYCLES.nOpCyclesLAHF; - Component.assert((this.getPS() & X86.PS.SAHF) == (ah & X86.PS.SAHF)); + if (DEBUG) this.assert((this.getPS() & X86.PS.SAHF) == (ah & X86.PS.SAHF)); }, /** * @this {X86CPU} @@ -2846,10 +2846,8 @@ var X86OpXX = { opIRET: function() { this.setCSIP(this.popWord(), this.popWord()); this.setPS(this.popWord()); + this.nFault = -1; if (this.cIntReturn) this.checkIntReturn(this.regEIP); - /* - * NOTE: I'm assuming that neither POPF nor IRET are required to set NOINTR like STI does. - */ this.nStepCycles -= this.CYCLES.nOpCyclesIRet; }, /** @@ -3159,7 +3157,7 @@ var X86OpXX = { * I still treat this as undefined, until I can verify the behavior on real hardware. */ opINT1: function() { - X86Help.opUndefined.call(this); + X86OpXX.opUndefined.call(this); }, /** * @this {X86CPU} @@ -3353,6 +3351,21 @@ var X86OpXX = { opGRP4w: function() { X86Mods.aOpModsGrpWord[this.getIPByte()].call(this, X86Grps.aOpGRP4w, X86Grps.opGrpNoSrc); if (EAFUNCS) this.setEAWord = this.setEAWordEnabled; + }, + /** + * @this {X86CPU} + */ + opInvalid: function() { + X86Help.opHelpFault.call(this, X86.EXCEPTION.UD_FAULT); + this.stopCPU(); + }, + /** + * @this {X86CPU} + */ + opUndefined: function() { + this.setIP(this.opEA - this.segCS.base); + this.setError("Undefined opcode 0x" + str.toHexByte(this.bus.getByteDirect(this.regEIP)) + " at " + str.toHexAddr(this.regIP, this.segCS.sel)); + this.stopCPU(); } }; diff --git a/modules/pcjs/lib/x86seg.js b/modules/pcjs/lib/x86seg.js index 892b3c355..4651973f7 100644 --- a/modules/pcjs/lib/x86seg.js +++ b/modules/pcjs/lib/x86seg.js @@ -88,8 +88,8 @@ X86Seg.loadReal = function loadReal(sel, fSuppress) * This replaces the segment's default load() function whenever the segment is notified via updateAccess() by the * CPU's setProtMode() that the processor is now in protected-mode. * - * Segments in protected-mode are referenced by selectors, which are indexes into descriptor tables (GDT, LDT, IDT) whose - * descriptors are 4-word (8-byte) entries: + * Segments in protected-mode are referenced by selectors, which are indexes into descriptor tables (GDT, LDT, IDT) + * whose descriptors are 4-word (8-byte) entries: * * word 0: segment limit (0-15) * word 1: base address low @@ -112,7 +112,7 @@ X86Seg.loadProt = function loadProt(sel, fSuppress) addrDTLimit = this.cpu.addrGDTLimit; } else { addrDT = this.cpu.segLDT.base; - addrDTLimit = this.cpu.segLDT.limit; + addrDTLimit = addrDT + this.cpu.segLDT.limit; } var addrDesc = addrDT + (sel & X86.SEL.MASK); if (addrDesc + 7 <= addrDTLimit) { @@ -291,8 +291,9 @@ X86Seg.prototype.loadDesc8 = function(sel, addrDesc) var ext = (DEBUG? this.cpu.getWord(addrDesc + X86.DESC.EXT.OFFSET) : 0); if (DEBUG) { - this.cpu.messageDebugger("loadDesc8(" + this.sName + "): base=" + str.toHex(base) + " limit=" + str.toHexWord(limit) + " acc=" + str.toHexWord(acc) + (ext? " ext=" + str.toHexWord(ext) : "")); - Component.assert(!ext); + var ch = (this.sName.length < 3? " " : ""); + this.cpu.messageDebugger("loadDesc8(" + this.sName + "):" + ch + " base=" + str.toHex(base) + " limit=" + str.toHexWord(limit) + " acc=" + str.toHexWord(acc) + (ext? " ext=" + str.toHexWord(ext) : "")); + // this.cpu.assert(!ext); } /* @@ -400,9 +401,9 @@ X86Seg.prototype.updateAccess = function(fProt) this.checkWrite = X86Seg.checkReadProtDisabled; } /* - * If the CODE bit is set, or the the WRITEABLE bit is not set, then disallow writes + * If the CODE bit is set, or the the WRITABLE bit is not set, then disallow writes */ - if ((this.acc & X86.DESC.ACC.TYPE.CODE) || !(this.acc & X86.DESC.ACC.TYPE.WRITEABLE)) { + if ((this.acc & X86.DESC.ACC.TYPE.CODE) || !(this.acc & X86.DESC.ACC.TYPE.WRITABLE)) { this.checkWrite = X86Seg.checkWriteProtDisabled; } } diff --git a/modules/shared/lib/component.js b/modules/shared/lib/component.js index d13cc705f..25884cc6b 100644 --- a/modules/shared/lib/component.js +++ b/modules/shared/lib/component.js @@ -63,11 +63,6 @@ if (typeof module !== 'undefined') { /** * Component(type, parms, constructor) * - * @constructor - * @param {string} type - * @param {Object} [parms] - * @param {Object} [constructor] - * * A Component object requires: * * type: a user-defined type name (eg, "CPU") @@ -79,6 +74,11 @@ if (typeof module !== 'undefined') { * comment: component comment string (default is undefined) * * Subclasses that use Component.subclass() to extend Component will likely have additional (parms) properties. + * + * @constructor + * @param {string} type + * @param {Object} [parms] + * @param {Object} [constructor] */ function Component(type, parms, constructor) { @@ -110,9 +110,8 @@ function Component(type, parms, constructor) this[type] = constructor; /* - * TODO: Decide how to reintegrate this code into the components that still want it.... - * - if (this.initStep) this.initStep(parms); + * Gather all the various component flags (booleans) into a single "flags" object, and encourage + * subclasses to do the same, to reduce the property clutter we have to wade through while debugging. */ this.aFlags = { fReady: false, @@ -151,8 +150,8 @@ Component.parmsURL = web.getURLParameters(); */ Component.inherit = function(p) { - if (window) { // an alternative to "if (typeof window === 'undefined')" if require("defines") has been invoked - if (!p) throw new TypeError(); // TODO: Why does this barf under Node? + if (window) { + if (!p) throw new TypeError(); if (Object.create) { return Object.create(p); } @@ -257,8 +256,11 @@ Component.log = function(s, type) /** * Component.assert(f, s) * - * Used to verify conditions that must be true (for DEBUG builds only; compiled builds should automatically have all - * references to Component.assert() removed). + * Verifies conditions that must be true (for DEBUG builds only). + * + * The Closure Compiler should automatically remove all references to Component.assert() in non-DEBUG builds. + * + * TODO: Add a task to the build process that "asserts" there are no occurrences of "assertion failure" in the final code. * * @param {boolean} f is the expression we are asserting to be true * @param {string} [s] is description of the assertion on failure @@ -268,7 +270,7 @@ Component.assert = function(f, s) if (DEBUG) { if (!f) { /* - * TODO: An accompanying source file/line number/function call would be nice, if there was a browser-independent way.... + * TODO: An accompanying source file/line number/function call (eg, stack trace) would be nice. */ if (!s) s = "assertion failure"; Component.log(s); @@ -465,7 +467,7 @@ Component.getComponentParms = function(element) * element.removeAttribute("data-value"); * } */ - } catch (e) { + } catch(e) { Component.error(e.message + " (" + sParms + ")"); } } @@ -693,6 +695,49 @@ Component.prototype = { Component.log(s, type || this.id || this.type); } }, + /** + * assert(f, s) + * + * Verifies conditions that must be true (for DEBUG builds only). + * + * WARNING: Make sure you preface all calls to this.assert() with "if (DEBUG)", because unlike Component.assert(), + * the Closure Compiler can't be sure that this instance method hasn't been overridden, so it refuses to treat it as + * dead code in non-DEBUG builds. + * + * TODO: Add a task to the build process that "asserts" there are no occurrences of "assertion failure" in the final code. + * + * @param {boolean} f is the expression we are asserting to be true + * @param {string} [s] is description of the assertion on failure + */ + assert: function(f, s) { + if (DEBUG) { + if (!f) { + /* + * TODO: An accompanying source file/line number/function call (eg, stack trace) would be nice. + */ + if (!s) s = "assertion failure in " + (this.id || this.type); + if (DEBUGGER && this.dbg) { + this.dbg.stopCPU(s); + /* + * Why do we throw an Error only to immediately catch it and ignore it? Simply to give our IDE + * the opportunity to stop and smell the roses. If the user has no desire to stop on assertions, + * then yes, consider this a giant NO-OP. + */ + try { + throw new Error(s); + } catch(e) {} + return; + } + /* + * If there's no Debugger, or the current component didn't bother saving a reference to the Debugger + * (eg, in its initBus() handler), then this component-level assert() the same as the class-level assert(), + * except for the (slightly) more detailed log() message. + */ + this.log(s); + throw new Error(s); + } + } + }, /** * println(s, type) * diff --git a/modules/shared/lib/defines.js b/modules/shared/lib/defines.js index 94ec28f46..4eadc621d 100644 --- a/modules/shared/lib/defines.js +++ b/modules/shared/lib/defines.js @@ -63,7 +63,7 @@ var DEBUG = true; // this @define is overridden by the Closure Com var MAXDEBUG = false; // this @define is overridden by the Closure Compiler (to false) to remove MAXDEBUG-only code if (typeof module !== 'undefined') { - global.window = false; + global.window = false; // provides an alternative "if (typeof window === 'undefined')" (ie, "if (window) ...") global.APPNAME = APPNAME; global.APPVERSION = APPVERSION; global.SITEHOST = SITEHOST; diff --git a/package.json b/package.json index 191f173ea..881fc14e3 100644 --- a/package.json +++ b/package.json @@ -107,8 +107,8 @@ "./modules/pcjs/lib/x86grps.js", "./modules/pcjs/lib/x86help.js", "./modules/pcjs/lib/x86mods.js", - "./modules/pcjs/lib/x86op0f.js", "./modules/pcjs/lib/x86opxx.js", + "./modules/pcjs/lib/x86op0f.js", "./modules/pcjs/lib/chipset.js", "./modules/pcjs/lib/rom.js", "./modules/pcjs/lib/ram.js",