diff --git a/_config.yml b/_config.yml
index 81e6e6068..94adc6217 100644
--- a/_config.yml
+++ b/_config.yml
@@ -56,7 +56,6 @@ pcjs:
- /modules/pcjs/lib/x86fpu.js
- /modules/pcjs/lib/x86func.js
- /modules/pcjs/lib/x86help.js
- - /modules/pcjs/lib/x86ints.js
- /modules/pcjs/lib/x86mods.js
- /modules/pcjs/lib/x86ops.js
- /modules/pcjs/lib/x86op0f.js
diff --git a/modules/pcjs/lib/x86func.js b/modules/pcjs/lib/x86func.js
index 8c43d6a3d..483253f91 100644
--- a/modules/pcjs/lib/x86func.js
+++ b/modules/pcjs/lib/x86func.js
@@ -734,6 +734,60 @@ X86.fnESC = function(dst, src)
return dst;
};
+/**
+ * fnGRPFault(dst, src)
+ *
+ * @this {X86CPU}
+ * @param {number} dst
+ * @param {number} src
+ * @return {number}
+ */
+X86.fnGRPFault = function(dst, src)
+{
+ /*
+ * This should NEVER be called on 8086/8088 CPUs, and yet we preset some of the handlers in aOpGrpPOPw,
+ * aOpGrp4b, and aOpGrp4w to call it. initProcessor() DOES patch aOpGrp4b[0x07] and aOpGrp4w[0x07] to
+ * fnGRPInvalid, but that's it.
+ *
+ * However, given the infrequency of this call, it's simpler to continue presetting all the handlers in
+ * aOpGrpPOPw to their post-8086 default, and deal with the appropriate 8086 behavior here (which for now,
+ * is to call fnGRPUndefined instead).
+ */
+ if (this.model < X86.MODEL_80186) {
+ return X86.fnGRPUndefined.call(this, dst, src);
+ }
+ X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
+ return dst;
+};
+
+/**
+ * fnGRPInvalid(dst, src)
+ *
+ * @this {X86CPU}
+ * @param {number} dst
+ * @param {number} src
+ * @return {number}
+ */
+X86.fnGRPInvalid = function(dst, src)
+{
+ X86.opInvalid.call(this);
+ return dst;
+};
+
+/**
+ * fnGRPUndefined(dst, src)
+ *
+ * @this {X86CPU}
+ * @param {number} dst
+ * @param {number} src
+ * @return {number}
+ */
+X86.fnGRPUndefined = function(dst, src)
+{
+ X86.opUndefined.call(this);
+ return dst;
+};
+
/**
* fnIDIVb(dst, src)
*
diff --git a/modules/pcjs/lib/x86help.js b/modules/pcjs/lib/x86help.js
index eb6e15379..a18a8feb7 100644
--- a/modules/pcjs/lib/x86help.js
+++ b/modules/pcjs/lib/x86help.js
@@ -439,3 +439,552 @@ X86.fnSRCxx = function()
{
return this.regXX;
};
+
+/**
+ * fnCALLF(off, sel)
+ *
+ * For protected-mode, this function must attempt to load the new code segment first, because if the new segment
+ * requires a change in privilege level, the return address must be pushed on the NEW stack, not the current stack.
+ *
+ * Also, we rely on a new function, pushData(), instead of pushWord(), to accommodate the outgoing segment size,
+ * which may differ from the incoming segment. For example, when a 32-bit code segment performs a 16:32 call to a
+ * 16-bit code segment, we must push 32-bit segment and offset values.
+ *
+ * TODO: Since setCSIP() already informs the segCS load() function when it's making a call, the load() function
+ * could automatically push the old CS and IP values *before* segCS is updated -- which would be a better time to do
+ * those pushes AND eliminate the need for pushData(). Unfortunately, load() is also used by loadIDT(), and loadIDT()
+ * has different requirements (eg, pushing flags first), so it's not a trivial change.
+ *
+ * @this {X86CPU}
+ * @param {number} off
+ * @param {number} sel
+ */
+X86.fnCALLF = function(off, sel)
+{
+ /*
+ * Since we always push the return address AFTER calling setCSIP(), and since either push could trigger a
+ * fault (eg, segment fault, page fault, etc), we must not only snapshot regLSP into opLSP, but also the
+ * current CS into opCS, so that fnFault() can always make CALLF restartable. Ditto for opSS and the SS register.
+ */
+ this.opCS = this.getCS();
+ this.opSS = this.getSS();
+ this.opLSP = this.regLSP;
+ var oldIP = this.getIP();
+ var oldSize = (I386? this.sizeData : 2);
+ if (this.setCSIP(off, sel, true) != null) {
+ /*
+ * When the OPERAND size is 32 bits, the 80386 will decrement the stack pointer by 4, write the selector
+ * into the 2 lower bytes, and leave the 2 upper bytes untouched; at least, that's the case for all other
+ * segment register writes, so we assume this case is no different. Hence, the hard-coded size of 2.
+ */
+ this.pushData(this.opCS, oldSize, 2);
+ this.pushData(oldIP, oldSize, oldSize);
+ }
+ this.opLSP = X86.ADDR_INVALID;
+ this.opCS = this.opSS = -1;
+};
+
+/**
+ * fnINT(nIDT, nError, nCycles)
+ *
+ * NOTE: We no longer use setCSIP(), because it always loads the new CS using segCS.load(), which only knows
+ * how to load GDT and LDT descriptors, whereas interrupts must use setCS.loadIDT(), which deals exclusively
+ * with IDT descriptors.
+ *
+ * @this {X86CPU}
+ * @param {number} nIDT
+ * @param {number|null} [nError]
+ * @param {number} [nCycles] (in addition to the default of nOpCyclesInt)
+ */
+X86.fnINT = function(nIDT, nError, nCycles)
+{
+ /*
+ * TODO: We assess the cycle cost up front, because otherwise, if loadIDT() fails, no cost may be assessed.
+ */
+ this.nStepCycles -= this.cycleCounts.nOpCyclesInt + (nCycles || 0);
+ var oldPS = this.getPS();
+ var oldCS = this.getCS();
+ var oldIP = this.getIP();
+ var addr = this.segCS.loadIDT(nIDT);
+ if (addr !== X86.ADDR_INVALID) {
+ /*
+ * TODO: Determine if we should use pushData() instead of pushWord() for oldCS and nError, to deal with
+ * the same 32-bit 80386 compatibility issue that fnCALLF(), opPUSHCS(), et al must deal with; namely, that
+ * 32-bit segment register writes (and, reportedly, 32-bit error codes) don't modify the upper 16 bits.
+ *
+ * Also, note that fnCALLF() is using the OPERAND size in effect *before* CS is loaded, whereas here we're
+ * using the OPERAND size in effect *after* CS is loaded. Is that correct? And does an explicit OPERAND
+ * size override on an "INT" instruction have any effect on that behavior? Is that even allowed?
+ */
+ this.pushWord(oldPS);
+ this.pushWord(oldCS);
+ this.pushWord(oldIP);
+ if (nError != null) this.pushWord(nError);
+ this.nFault = -1;
+ this.setLIP(addr);
+ }
+};
+
+/**
+ * fnIRET()
+ *
+ * @this {X86CPU}
+ */
+X86.fnIRET = function()
+{
+ /*
+ * Originally, we would snapshot regLSP into opLSP because newCS could trigger a segment fault,
+ * but additionally, the stack segment could trigger either a segment fault or a page fault; indeed,
+ * any operation that performs multiple stack modifications must take this precaution and snapshot regLSP.
+ */
+ this.opLSP = this.regLSP;
+
+ this.nStepCycles -= this.cycleCounts.nOpCyclesIRet;
+
+ if ((this.regCR0 & X86.CR0.MSW.PE) && (this.regPS & X86.PS.NT)) {
+ var addrNew = this.segTSS.base;
+ /*
+ * Fortunately, X86.TSS286.PREV_TSS and X86.TSS386.PREV_TSS refer to the same TSS offset.
+ * TODO: Update switchTS() to assess a cycle cost; currently, all we assess is what's shown above.
+ */
+ var sel = this.getShort(addrNew + X86.TSS286.PREV_TSS);
+ this.segCS.switchTSS(sel, false);
+ }
+ else {
+ var cpl = this.nCPL;
+ var newIP = this.popWord();
+ var newCS = this.popWord();
+ var newPS = this.popWord();
+
+ if (I386) {
+ if (this.regPS & X86.PS.VM) {
+ /*
+ * On the 80386, in V86-mode, RF is the only defined EFLAGS bit above bit 15 that may be changed by IRETD.
+ * This is less restrictive than POPFD, which cannot change ANY bits above bit 15; see opPOPF() for details.
+ */
+ newPS = (newPS & (0xffff | X86.PS.RF)) | (this.regPS & ~(0xffff | X86.PS.RF));
+ }
+ else {
+ if (newPS & X86.PS.VM) {
+ /*
+ * As noted in loadDesc8(), where the V86-mode frame we're about to pop was originally pushed,
+ * these frames ALWAYS contain 32-bit values, so make sure that sizeData reflects that.
+ */
+ this.assert(!!(this.regCR0 & X86.CR0.MSW.PE) && this.sizeData == 4);
+ /*
+ * We have to assume that a full V86-mode interrupt frame was on the protected-mode stack; namely:
+ *
+ * low: EIP
+ * CS (padded to 32 bits)
+ * EFLAGS
+ * ESP
+ * SS (padded to 32 bits)
+ * ES (padded to 32 bits)
+ * DS (padded to 32 bits)
+ * FS (padded to 32 bits)
+ * high: GS (padded to 32 bits)
+ *
+ * We've already popped EIP, CS, and EFLAGS into newIP, newCS and newPS, respectively, so we must now
+ * pop the rest, while we're still in protected-mode, before the switch to V86-mode alters the current
+ * operand size (among other things).
+ */
+ var newSP = this.popWord();
+ var newSS = this.popWord();
+ var newES = this.popWord();
+ var newDS = this.popWord();
+ var newFS = this.popWord();
+ var newGS = this.popWord();
+ this.setProtMode(true, true); // flip the switch to V86-mode now
+ this.setSS(newSS);
+ this.setSP(newSP);
+ this.setES(newES);
+ this.setDS(newDS);
+ this.setFS(newFS);
+ this.setGS(newGS);
+ }
+ }
+ }
+
+ if (this.setCSIP(newIP, newCS, false) != null) {
+ this.setPS(newPS, cpl);
+ if (this.cIntReturn) this.checkIntReturn(this.regLIP);
+ }
+ }
+
+ this.opLSP = X86.ADDR_INVALID;
+};
+
+/**
+ * fnRETF(n)
+ *
+ * For protected-mode, this function must pop any arguments off the current stack AND whatever stack
+ * we may have switched to; setCSIP() returns true if a stack switch occurred, false if not, and null
+ * if an error occurred.
+ *
+ * @this {X86CPU}
+ * @param {number} n
+ */
+X86.fnRETF = function(n)
+{
+ /*
+ * Originally, we would snapshot regLSP into opLSP because newCS could trigger a segment fault,
+ * but additionally, the stack segment could trigger either a segment fault or a page fault; indeed,
+ * any operation that performs multiple stack modifications must take this precaution and snapshot regLSP.
+ */
+ this.opLSP = this.regLSP;
+
+ var newIP = this.popWord();
+ var newCS = this.popWord();
+
+ if (n) this.setSP(this.getSP() + n); // TODO: optimize
+
+ if (this.setCSIP(newIP, newCS, false)) { // returns true if a stack switch occurred
+ /*
+ * Fool me once, shame on... whatever. If setCSIP() indicates a stack switch occurred,
+ * make sure we're in protected mode, because automatic stack switches can't occur in real mode.
+ */
+ this.assert(!!(this.regCR0 & X86.CR0.MSW.PE));
+
+ if (n) this.setSP(this.getSP() + n); // TODO: optimize
+
+ /*
+ * As per Intel documentation: "If any of [the DS or ES] registers refer to segments whose DPL is
+ * less than the new CPL (excluding conforming code segments), the segment register is loaded with
+ * the null selector."
+ *
+ * TODO: I'm not clear on whether a conforming code segment must also be marked readable, so I'm playing
+ * it safe and using CODE_CONFORMING instead of CODE_CONFORMING_READABLE. Also, for the record, I've not
+ * seen this situation occur yet (eg, in OS/2 1.0).
+ */
+ this.zeroSeg(this.segDS);
+ this.zeroSeg(this.segES);
+ if (I386 && this.model >= X86.MODEL_80386) {
+ this.zeroSeg(this.segFS);
+ this.zeroSeg(this.segGS);
+ }
+ }
+ if (n == 2 && this.cIntReturn) this.checkIntReturn(this.regLIP);
+
+ this.opLSP = X86.ADDR_INVALID;
+};
+
+/**
+ * fnDivOverflow()
+ *
+ * @this {X86CPU}
+ */
+X86.fnDivOverflow = function()
+{
+ /*
+ * Divide error exceptions are traps on the 8086 and faults on later processors. I question the value of that
+ * change, because it implies that someone might actually want to restart a failing divide. The only reasonable
+ * explanation I can see for the change is to enable the exception handler to accurately record the address of
+ * the failing divide, which seems like a very minor benefit. It doesn't change the fact that, on any processor,
+ * the exception handler's only reasonable recourse is to unwind execution to a safe point (or terminate the app).
+ *
+ * TODO: Determine the proper cycle cost.
+ */
+ if (this.model == X86.MODEL_8086) {
+ X86.fnTrap.call(this, X86.EXCEPTION.DE_EXC, 2);
+ } else {
+ X86.fnFault.call(this, X86.EXCEPTION.DE_EXC, null, 2);
+ }
+};
+
+/**
+ * fnInterrupt(nIDT, nCycles)
+ *
+ * Helper to dispatch external interrupts. nCycles defaults to 11 for the 8086/8088
+ * if no alternate value is specified.
+ *
+ * @this {X86CPU}
+ * @param {number} nIDT
+ * @param {number} [nCycles] (number of cycles in addition to the default of nOpCyclesInt)
+ */
+X86.fnInterrupt = function(nIDT, nCycles)
+{
+ this.nFault = nIDT;
+ if (nCycles === undefined) nCycles = 11;
+ X86.fnINT.call(this, nIDT, null, nCycles);
+};
+
+/**
+ * fnTrap(nIDT, nCycles)
+ *
+ * Helper to dispatch traps (ie, exceptions that occur AFTER the instruction, with NO error code)
+ *
+ * @this {X86CPU}
+ * @param {number} nIDT
+ * @param {number} [nCycles] (number of cycles in addition to the default of nOpCyclesInt)
+ */
+X86.fnTrap = function(nIDT, nCycles)
+{
+ this.nFault = -1;
+ X86.fnINT.call(this, nIDT, null, nCycles);
+};
+
+/**
+ * fnFault(nFault, nError, nCycles, fHalt)
+ *
+ * Helper to dispatch faults (ie, exceptions that occur DURING an instruction and MAY generate an error code)
+ *
+ * @this {X86CPU}
+ * @param {number} nFault
+ * @param {number|null} [nError] (if omitted, no error code will be pushed)
+ * @param {number} [nCycles] cycle count to pass through to fnINT(), if any
+ * @param {boolean} [fHalt] (true to halt the CPU, false to not, undefined if "it depends")
+ */
+X86.fnFault = function(nFault, nError, nCycles, fHalt)
+{
+ var fDispatch = false;
+
+ if (!this.aFlags.fComplete) {
+ /*
+ * Prior to each new burst of instructions, stepCPU() sets fComplete to true, and the only (normal) way
+ * for fComplete to become false is through stopCPU(), which isn't ordinarily called, except by the Debugger.
+ */
+ this.setLIP(this.opLIP);
+ }
+ else if (this.model >= X86.MODEL_80186) {
+
+ fDispatch = true;
+
+ if (this.nFault < 0) {
+ /*
+ * Single-fault (error code is passed through, and the responsible instruction is restartable.
+ */
+ if (this.opCS != -1) {
+ /*
+ * HACK: We must slam 3 into this.segCS.cpl to ensure that loading the original CS segment doesn't
+ * fail. For example, if we faulted in the middle of a ring transition that loaded CS with a higher
+ * privilege (lower CPL) code segment, then our attempt here to reload the lower privilege (higher CPL)
+ * code segment could be viewed as a privilege violation (which it would be outside this context).
+ */
+ this.segCS.cpl = 3;
+ this.setCS(this.opCS);
+ this.opCS = -1;
+ }
+ this.setLIP(this.opLIP);
+ if (this.opSS != -1) {
+ this.setSS(this.opSS);
+ this.opSS = -1;
+ }
+ if (this.opLSP !== X86.ADDR_INVALID) {
+ this.setSP((this.regESP & ~this.segSS.maskAddr) | (this.opLSP - this.segSS.base));
+ this.opLSP = X86.ADDR_INVALID;
+ }
+ }
+ 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;
+ }
+ else {
+ /*
+ * Triple-fault (usually referred to in Intel literature as a "shutdown", but at least on the 80286,
+ * it's actually a "reset")
+ */
+ nError = 0;
+ nFault = -1;
+ this.resetRegs();
+ fDispatch = fHalt = false;
+ }
+ }
+
+ if (X86.fnCheckFault.call(this, nFault, nError, fHalt)) {
+ /*
+ * If this is a fault that would normally be dispatched BUT fnCheckFault() wants us to halt,
+ * then we throw a bogus fault number (-1), simply to interrupt the current instruction in exactly
+ * the same way that a dispatched fault would interrupt it.
+ */
+ if (fDispatch) throw -1;
+ }
+
+ if (fDispatch) {
+
+ this.nFault = nFault;
+ X86.fnINT.call(this, nFault, nError, nCycles);
+
+ /*
+ * REP'eated instructions that rewind regLIP to opLIP used to screw up this dispatch,
+ * so now we slip the new regLIP into opLIP, effectively turning their action into a no-op.
+ */
+ this.opLIP = this.regLIP;
+
+ /*
+ * X86.OPFLAG.FAULT flag is used by selected opcodes to provide an early exit, restore register(s),
+ * or whatever is needed to help ensure instruction restartability; there is currently no general
+ * mechanism for snapping and restoring all registers for any instruction that might fault.
+ *
+ * X86.EXCEPTION.DB_EXC exceptions set their own special flag, X86.OPFLAG.DBEXC, to prevent redundant
+ * DEBUG exceptions, so we don't need to set OPFLAG.FAULT in that case, because a DEBUG exception
+ * doesn't actually prevent an instruction from executing (and therefore doesn't need to be restarted).
+ */
+ if (nFault == X86.EXCEPTION.DB_EXC) {
+ this.opFlags |= X86.OPFLAG.DBEXC;
+ } else {
+ this.assert(nFault >= 0);
+ this.opFlags |= X86.OPFLAG.FAULT;
+ }
+
+ /*
+ * Since this fault is likely being issued in the context of an instruction that hasn't finished
+ * executing, if we don't do anything to interrupt that execution (eg, throw a JavaScript exception),
+ * then we would need to shut off all further reads/writes for the current instruction.
+ *
+ * That's easy for any EA-based memory accesses: simply set both the NOREAD and NOWRITE flags.
+ * However, there are also direct, non-EA-based memory accesses to consider. A perfect example is
+ * opPUSHA(): if a GP fault occurs on any PUSH other than the last, a subsequent PUSH is likely to
+ * cause another fault, which we will misinterpret as a double-fault -- unless the handler for
+ * such an opcode checks this.opFlags for X86.OPFLAG.FAULT after each step of the operation.
+ *
+ * this.opFlags |= (X86.OPFLAG.NOREAD | X86.OPFLAG.NOWRITE);
+ *
+ * Fortunately, we now throw an exception that terminates the current instruction, so the above hack
+ * should no longer be necessary.
+ */
+ throw nFault;
+ }
+};
+
+/**
+ * fnPageFault(addr, fPresent, fWrite)
+ *
+ * Helper to dispatch page faults.
+ *
+ * @this {X86CPU}
+ * @param {number} addr
+ * @param {boolean} fPresent
+ * @param {boolean} fWrite
+ */
+X86.fnPageFault = function(addr, fPresent, fWrite)
+{
+ this.regCR2 = addr;
+ var nError = 0;
+ if (fPresent) nError |= X86.PTE.PRESENT;
+ if (fWrite) nError |= X86.PTE.READWRITE;
+ if (this.nCPL == 3) nError |= X86.PTE.USER;
+ X86.fnFault.call(this, X86.EXCEPTION.PF_FAULT, nError);
+};
+
+/**
+ * fnCheckFault(nFault, nError, fHalt)
+ *
+ * Aside from giving the Debugger an opportunity to report every fault, this also gives us the ability to
+ * halt exception processing in tracks: return true to prevent the fault handler from being dispatched.
+ *
+ * At the moment, the only Debugger control you have over fault interception is setting MESSAGE.FAULT, which
+ * will display faults as they occur, and MESSAGE.HALT, which will halt after any Debugger message, including
+ * MESSAGE.FAULT. If you want execution to continue after halting, clear MESSAGE.FAULT and/or MESSAGE.HALT,
+ * or single-step over the offending instruction, which will allow the fault to be dispatched.
+ *
+ * @this {X86CPU}
+ * @param {number} nFault
+ * @param {number|null} [nError] (if omitted, no error code will be reported)
+ * @param {boolean} [fHalt] (true to halt the CPU, false to not, undefined if "it depends")
+ * @return {boolean|undefined} true to block the fault (often desirable when fHalt is true), otherwise dispatch it
+ */
+X86.fnCheckFault = function(nFault, nError, fHalt)
+{
+ var bitsMessage = Messages.FAULT;
+
+ var bOpcode = this.probeAddr(this.regLIP);
+
+ /*
+ * OS/2 1.0 uses an INT3 (0xCC) opcode in conjunction with an invalid IDT to trigger a triple-fault
+ * reset and return to real-mode, and these resets happen quite frequently during boot; for example,
+ * OS/2 startup messages are displayed using a series of INT 0x10 BIOS calls for each character, and
+ * each series of BIOS calls requires a round-trip mode switch.
+ *
+ * Since we really only want to halt on "bad" faults, not "good" (ie, intentional) faults, we take
+ * advantage of the fact that all 3 faults comprising the triple-fault point to an INT3 (0xCC) opcode,
+ * and so whenever we see that opcode, we ignore the caller's fHalt flag, and suppress FAULT messages
+ * unless CPU messages are also enabled.
+ *
+ * When a triple fault shows up, nFault is -1; it displays as 0xff only because we use toHexByte().
+ */
+ if (bOpcode == X86.OPCODE.INT3 && !this.addrIDTLimit) {
+ fHalt = false;
+ }
+
+ /*
+ * There are a number of V86-mode exceptions we don't need to know about. For starters, Windows 3.00
+ * (and other versions of enhanced-mode Windows) use an ARPL to switch out of V86-mode, so we can ignore
+ * those UD_FAULTs.
+ *
+ * Ditto for software interrupts, which will generate a GP_FAULT when the interrupt number (eg, 0x6D)
+ * exceeds the protected-mode IDT's limit (eg, a limit of 0x2FF corresponds to a maximum interrupt number
+ * of 0x5F). Windows doesn't really care if its IDT is too small, because it has to simulate all software
+ * interrupts in V86-mode regardless (they generate a GP_FAULT if IOPL < 3, and even when IOPL == 3, only
+ * the protected-mode IDT handler gets to run).
+ */
+ if ((this.regPS & X86.PS.VM)) {
+ if (nFault == X86.EXCEPTION.UD_FAULT && bOpcode == X86.OPCODE.ARPL ||
+ nFault == X86.EXCEPTION.GP_FAULT && bOpcode == X86.OPCODE.INTN) {
+ fHalt = false;
+ }
+ }
+
+ /*
+ * If fHalt has been explicitly set to false, we also take that as a cue to disable fault messages
+ * (which you can override by turning on CPU messages).
+ */
+ if (fHalt === false) {
+ bitsMessage |= Messages.CPU;
+ }
+
+ /*
+ * Similarly, the PC AT ROM BIOS deliberately generates a couple of GP faults as part of the POST
+ * (Power-On Self Test); we don't want to ignore those, but we don't want to halt on them either. We
+ * detect those faults by virtue of the LIP being in the range 0x0F0000 to 0x0FFFFF.
+ *
+ * TODO: Be aware that this test can trigger false positives, such as when a V86-mode ARPL is hit; eg:
+ *
+ * &FD82:22F7 6338 ARPL [BX+SI],DI
+ */
+ if (this.regLIP >= 0x0F0000 && this.regLIP <= 0x0FFFFF) {
+ fHalt = false;
+ }
+
+ /*
+ * However, the foregoing notwithstanding, if MESSAGE.HALT is enabled along with all the other required
+ * MESSAGE bits, then we want to halt regardless.
+ */
+ if (this.messageEnabled(bitsMessage | Messages.HALT)) {
+ fHalt = true;
+ }
+
+ if (this.messageEnabled(bitsMessage) || fHalt) {
+
+ var fRunning = this.aFlags.fRunning;
+ var sMessage = "Fault " + str.toHexByte(nFault) + (nError != null? " (" + str.toHexWord(nError) + ")" : "") + " on opcode " + str.toHexByte(bOpcode);
+ if (fHalt && fRunning) sMessage += " (blocked by PCjs Debugger)";
+
+ if (DEBUGGER && this.dbg) {
+ this.printMessage(sMessage, fHalt || bitsMessage, true);
+ if (fHalt) {
+ /*
+ * By setting fHalt to fRunning (which is true while running but false while single-stepping),
+ * this allows a fault to be dispatched when you single-step over a faulting instruction; you can
+ * then continue single-stepping into the fault handler, or start running again.
+ *
+ * Note that we had to capture fRunning before calling printMessage(), because if MESSAGE.HALT
+ * is set, printMessage() will have already halted the CPU.
+ */
+ fHalt = fRunning;
+ this.dbg.stopCPU();
+ }
+ } else {
+ /*
+ * If there's no Debugger, then messageEnabled() must have returned false, which means that fHalt must
+ * be true. Which means we should shut the machine down.
+ */
+ this.assert(fHalt);
+ this.notice(sMessage);
+ this.stopCPU();
+ }
+ }
+ return fHalt;
+};
diff --git a/modules/pcjs/lib/x86ints.js b/modules/pcjs/lib/x86ints.js
deleted file mode 100644
index 13cd46ca7..000000000
--- a/modules/pcjs/lib/x86ints.js
+++ /dev/null
@@ -1,642 +0,0 @@
-/**
- * @fileoverview Implements PCjs control flow functions.
- * @author Jeff Parsons
- * @version 1.0
- * Created 2016-Mar-04
- *
- * Copyright © 2012-2016 Jeff Parsons
- *
- * This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
- * at and .
- *
- * PCjs is free software: you can redistribute it and/or modify it under the terms of the
- * GNU General Public License as published by the Free Software Foundation, either version 3
- * of the License, or (at your option) any later version.
- *
- * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
- * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with PCjs. If not,
- * see .
- *
- * You are required to include the above copyright notice in every source code file of every
- * copy or modified version of this work, and to display that copyright notice on every screen
- * that loads or runs any version of this software (see Computer.COPYRIGHT).
- *
- * Some PCjs files also attempt to load external resource files, such as character-image files,
- * ROM files, and disk image files. Those external resource files are not considered part of the
- * PCjs program for purposes of the GNU General Public License, and the author does not claim
- * any copyright as to their contents.
- */
-
-"use strict";
-
-if (NODE) {
- var str = require("../../shared/lib/strlib");
- var Messages = require("./messages");
- var X86 = require("./x86");
-}
-
-/**
- * fnCALLF(off, sel)
- *
- * For protected-mode, this function must attempt to load the new code segment first, because if the new segment
- * requires a change in privilege level, the return address must be pushed on the NEW stack, not the current stack.
- *
- * Also, we rely on a new function, pushData(), instead of pushWord(), to accommodate the outgoing segment size,
- * which may differ from the incoming segment. For example, when a 32-bit code segment performs a 16:32 call to a
- * 16-bit code segment, we must push 32-bit segment and offset values.
- *
- * TODO: Since setCSIP() already informs the segCS load() function when it's making a call, the load() function
- * could automatically push the old CS and IP values *before* segCS is updated -- which would be a better time to do
- * those pushes AND eliminate the need for pushData(). Unfortunately, load() is also used by loadIDT(), and loadIDT()
- * has different requirements (eg, pushing flags first), so it's not a trivial change.
- *
- * @this {X86CPU}
- * @param {number} off
- * @param {number} sel
- */
-X86.fnCALLF = function(off, sel)
-{
- /*
- * Since we always push the return address AFTER calling setCSIP(), and since either push could trigger a
- * fault (eg, segment fault, page fault, etc), we must not only snapshot regLSP into opLSP, but also the
- * current CS into opCS, so that fnFault() can always make CALLF restartable. Ditto for opSS and the SS register.
- */
- this.opCS = this.getCS();
- this.opSS = this.getSS();
- this.opLSP = this.regLSP;
- var oldIP = this.getIP();
- var oldSize = (I386? this.sizeData : 2);
- if (this.setCSIP(off, sel, true) != null) {
- /*
- * When the OPERAND size is 32 bits, the 80386 will decrement the stack pointer by 4, write the selector
- * into the 2 lower bytes, and leave the 2 upper bytes untouched; at least, that's the case for all other
- * segment register writes, so we assume this case is no different. Hence, the hard-coded size of 2.
- */
- this.pushData(this.opCS, oldSize, 2);
- this.pushData(oldIP, oldSize, oldSize);
- }
- this.opLSP = X86.ADDR_INVALID;
- this.opCS = this.opSS = -1;
-};
-
-/**
- * fnINT(nIDT, nError, nCycles)
- *
- * NOTE: We no longer use setCSIP(), because it always loads the new CS using segCS.load(), which only knows
- * how to load GDT and LDT descriptors, whereas interrupts must use setCS.loadIDT(), which deals exclusively
- * with IDT descriptors.
- *
- * @this {X86CPU}
- * @param {number} nIDT
- * @param {number|null} [nError]
- * @param {number} [nCycles] (in addition to the default of nOpCyclesInt)
- */
-X86.fnINT = function(nIDT, nError, nCycles)
-{
- /*
- * TODO: We assess the cycle cost up front, because otherwise, if loadIDT() fails, no cost may be assessed.
- */
- this.nStepCycles -= this.cycleCounts.nOpCyclesInt + (nCycles || 0);
- var oldPS = this.getPS();
- var oldCS = this.getCS();
- var oldIP = this.getIP();
- var addr = this.segCS.loadIDT(nIDT);
- if (addr !== X86.ADDR_INVALID) {
- /*
- * TODO: Determine if we should use pushData() instead of pushWord() for oldCS and nError, to deal with
- * the same 32-bit 80386 compatibility issue that fnCALLF(), opPUSHCS(), et al must deal with; namely, that
- * 32-bit segment register writes (and, reportedly, 32-bit error codes) don't modify the upper 16 bits.
- *
- * Also, note that fnCALLF() is using the OPERAND size in effect *before* CS is loaded, whereas here we're
- * using the OPERAND size in effect *after* CS is loaded. Is that correct? And does an explicit OPERAND
- * size override on an "INT" instruction have any effect on that behavior? Is that even allowed?
- */
- this.pushWord(oldPS);
- this.pushWord(oldCS);
- this.pushWord(oldIP);
- if (nError != null) this.pushWord(nError);
- this.nFault = -1;
- this.setLIP(addr);
- }
-};
-
-/**
- * fnIRET()
- *
- * @this {X86CPU}
- */
-X86.fnIRET = function()
-{
- /*
- * Originally, we would snapshot regLSP into opLSP because newCS could trigger a segment fault,
- * but additionally, the stack segment could trigger either a segment fault or a page fault; indeed,
- * any operation that performs multiple stack modifications must take this precaution and snapshot regLSP.
- */
- this.opLSP = this.regLSP;
-
- this.nStepCycles -= this.cycleCounts.nOpCyclesIRet;
-
- if ((this.regCR0 & X86.CR0.MSW.PE) && (this.regPS & X86.PS.NT)) {
- var addrNew = this.segTSS.base;
- /*
- * Fortunately, X86.TSS286.PREV_TSS and X86.TSS386.PREV_TSS refer to the same TSS offset.
- * TODO: Update switchTS() to assess a cycle cost; currently, all we assess is what's shown above.
- */
- var sel = this.getShort(addrNew + X86.TSS286.PREV_TSS);
- this.segCS.switchTSS(sel, false);
- }
- else {
- var cpl = this.nCPL;
- var newIP = this.popWord();
- var newCS = this.popWord();
- var newPS = this.popWord();
-
- if (I386) {
- if (this.regPS & X86.PS.VM) {
- /*
- * On the 80386, in V86-mode, RF is the only defined EFLAGS bit above bit 15 that may be changed by IRETD.
- * This is less restrictive than POPFD, which cannot change ANY bits above bit 15; see opPOPF() for details.
- */
- newPS = (newPS & (0xffff | X86.PS.RF)) | (this.regPS & ~(0xffff | X86.PS.RF));
- }
- else {
- if (newPS & X86.PS.VM) {
- /*
- * As noted in loadDesc8(), where the V86-mode frame we're about to pop was originally pushed,
- * these frames ALWAYS contain 32-bit values, so make sure that sizeData reflects that.
- */
- this.assert(!!(this.regCR0 & X86.CR0.MSW.PE) && this.sizeData == 4);
- /*
- * We have to assume that a full V86-mode interrupt frame was on the protected-mode stack; namely:
- *
- * low: EIP
- * CS (padded to 32 bits)
- * EFLAGS
- * ESP
- * SS (padded to 32 bits)
- * ES (padded to 32 bits)
- * DS (padded to 32 bits)
- * FS (padded to 32 bits)
- * high: GS (padded to 32 bits)
- *
- * We've already popped EIP, CS, and EFLAGS into newIP, newCS and newPS, respectively, so we must now
- * pop the rest, while we're still in protected-mode, before the switch to V86-mode alters the current
- * operand size (among other things).
- */
- var newSP = this.popWord();
- var newSS = this.popWord();
- var newES = this.popWord();
- var newDS = this.popWord();
- var newFS = this.popWord();
- var newGS = this.popWord();
- this.setProtMode(true, true); // flip the switch to V86-mode now
- this.setSS(newSS);
- this.setSP(newSP);
- this.setES(newES);
- this.setDS(newDS);
- this.setFS(newFS);
- this.setGS(newGS);
- }
- }
- }
-
- if (this.setCSIP(newIP, newCS, false) != null) {
- this.setPS(newPS, cpl);
- if (this.cIntReturn) this.checkIntReturn(this.regLIP);
- }
- }
-
- this.opLSP = X86.ADDR_INVALID;
-};
-
-/**
- * fnRETF(n)
- *
- * For protected-mode, this function must pop any arguments off the current stack AND whatever stack
- * we may have switched to; setCSIP() returns true if a stack switch occurred, false if not, and null
- * if an error occurred.
- *
- * @this {X86CPU}
- * @param {number} n
- */
-X86.fnRETF = function(n)
-{
- /*
- * Originally, we would snapshot regLSP into opLSP because newCS could trigger a segment fault,
- * but additionally, the stack segment could trigger either a segment fault or a page fault; indeed,
- * any operation that performs multiple stack modifications must take this precaution and snapshot regLSP.
- */
- this.opLSP = this.regLSP;
-
- var newIP = this.popWord();
- var newCS = this.popWord();
-
- if (n) this.setSP(this.getSP() + n); // TODO: optimize
-
- if (this.setCSIP(newIP, newCS, false)) { // returns true if a stack switch occurred
- /*
- * Fool me once, shame on... whatever. If setCSIP() indicates a stack switch occurred,
- * make sure we're in protected mode, because automatic stack switches can't occur in real mode.
- */
- this.assert(!!(this.regCR0 & X86.CR0.MSW.PE));
-
- if (n) this.setSP(this.getSP() + n); // TODO: optimize
-
- /*
- * As per Intel documentation: "If any of [the DS or ES] registers refer to segments whose DPL is
- * less than the new CPL (excluding conforming code segments), the segment register is loaded with
- * the null selector."
- *
- * TODO: I'm not clear on whether a conforming code segment must also be marked readable, so I'm playing
- * it safe and using CODE_CONFORMING instead of CODE_CONFORMING_READABLE. Also, for the record, I've not
- * seen this situation occur yet (eg, in OS/2 1.0).
- */
- this.zeroSeg(this.segDS);
- this.zeroSeg(this.segES);
- if (I386 && this.model >= X86.MODEL_80386) {
- this.zeroSeg(this.segFS);
- this.zeroSeg(this.segGS);
- }
- }
- if (n == 2 && this.cIntReturn) this.checkIntReturn(this.regLIP);
-
- this.opLSP = X86.ADDR_INVALID;
-};
-
-/**
- * fnGRPFault(dst, src)
- *
- * @this {X86CPU}
- * @param {number} dst
- * @param {number} src
- * @return {number}
- */
-X86.fnGRPFault = function(dst, src)
-{
- /*
- * This should NEVER be called on 8086/8088 CPUs, and yet we preset some of the handlers in aOpGrpPOPw,
- * aOpGrp4b, and aOpGrp4w to call it. initProcessor() DOES patch aOpGrp4b[0x07] and aOpGrp4w[0x07] to
- * fnGRPInvalid, but that's it.
- *
- * However, given the infrequency of this call, it's simpler to continue presetting all the handlers in
- * aOpGrpPOPw to their post-8086 default, and deal with the appropriate 8086 behavior here (which for now,
- * is to call fnGRPUndefined instead).
- */
- if (this.model < X86.MODEL_80186) {
- return X86.fnGRPUndefined.call(this, dst, src);
- }
- X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
- return dst;
-};
-
-/**
- * fnGRPInvalid(dst, src)
- *
- * @this {X86CPU}
- * @param {number} dst
- * @param {number} src
- * @return {number}
- */
-X86.fnGRPInvalid = function(dst, src)
-{
- X86.opInvalid.call(this);
- return dst;
-};
-
-/**
- * fnGRPUndefined(dst, src)
- *
- * @this {X86CPU}
- * @param {number} dst
- * @param {number} src
- * @return {number}
- */
-X86.fnGRPUndefined = function(dst, src)
-{
- X86.opUndefined.call(this);
- return dst;
-};
-
-/**
- * fnDivOverflow()
- *
- * @this {X86CPU}
- */
-X86.fnDivOverflow = function()
-{
- /*
- * Divide error exceptions are traps on the 8086 and faults on later processors. I question the value of that
- * change, because it implies that someone might actually want to restart a failing divide. The only reasonable
- * explanation I can see for the change is to enable the exception handler to accurately record the address of
- * the failing divide, which seems like a very minor benefit. It doesn't change the fact that, on any processor,
- * the exception handler's only reasonable recourse is to unwind execution to a safe point (or terminate the app).
- *
- * TODO: Determine the proper cycle cost.
- */
- if (this.model == X86.MODEL_8086) {
- X86.fnTrap.call(this, X86.EXCEPTION.DE_EXC, 2);
- } else {
- X86.fnFault.call(this, X86.EXCEPTION.DE_EXC, null, 2);
- }
-};
-
-/**
- * fnInterrupt(nIDT, nCycles)
- *
- * Helper to dispatch external interrupts. nCycles defaults to 11 for the 8086/8088
- * if no alternate value is specified.
- *
- * @this {X86CPU}
- * @param {number} nIDT
- * @param {number} [nCycles] (number of cycles in addition to the default of nOpCyclesInt)
- */
-X86.fnInterrupt = function(nIDT, nCycles)
-{
- this.nFault = nIDT;
- if (nCycles === undefined) nCycles = 11;
- X86.fnINT.call(this, nIDT, null, nCycles);
-};
-
-/**
- * fnTrap(nIDT, nCycles)
- *
- * Helper to dispatch traps (ie, exceptions that occur AFTER the instruction, with NO error code)
- *
- * @this {X86CPU}
- * @param {number} nIDT
- * @param {number} [nCycles] (number of cycles in addition to the default of nOpCyclesInt)
- */
-X86.fnTrap = function(nIDT, nCycles)
-{
- this.nFault = -1;
- X86.fnINT.call(this, nIDT, null, nCycles);
-};
-
-/**
- * fnFault(nFault, nError, nCycles, fHalt)
- *
- * Helper to dispatch faults (ie, exceptions that occur DURING an instruction and MAY generate an error code)
- *
- * @this {X86CPU}
- * @param {number} nFault
- * @param {number|null} [nError] (if omitted, no error code will be pushed)
- * @param {number} [nCycles] cycle count to pass through to fnINT(), if any
- * @param {boolean} [fHalt] (true to halt the CPU, false to not, undefined if "it depends")
- */
-X86.fnFault = function(nFault, nError, nCycles, fHalt)
-{
- var fDispatch = false;
-
- if (!this.aFlags.fComplete) {
- /*
- * Prior to each new burst of instructions, stepCPU() sets fComplete to true, and the only (normal) way
- * for fComplete to become false is through stopCPU(), which isn't ordinarily called, except by the Debugger.
- */
- this.setLIP(this.opLIP);
- }
- else if (this.model >= X86.MODEL_80186) {
-
- fDispatch = true;
-
- if (this.nFault < 0) {
- /*
- * Single-fault (error code is passed through, and the responsible instruction is restartable.
- */
- if (this.opCS != -1) {
- /*
- * HACK: We must slam 3 into this.segCS.cpl to ensure that loading the original CS segment doesn't
- * fail. For example, if we faulted in the middle of a ring transition that loaded CS with a higher
- * privilege (lower CPL) code segment, then our attempt here to reload the lower privilege (higher CPL)
- * code segment could be viewed as a privilege violation (which it would be outside this context).
- */
- this.segCS.cpl = 3;
- this.setCS(this.opCS);
- this.opCS = -1;
- }
- this.setLIP(this.opLIP);
- if (this.opSS != -1) {
- this.setSS(this.opSS);
- this.opSS = -1;
- }
- if (this.opLSP !== X86.ADDR_INVALID) {
- this.setSP((this.regESP & ~this.segSS.maskAddr) | (this.opLSP - this.segSS.base));
- this.opLSP = X86.ADDR_INVALID;
- }
- }
- 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;
- }
- else {
- /*
- * Triple-fault (usually referred to in Intel literature as a "shutdown", but at least on the 80286,
- * it's actually a "reset")
- */
- nError = 0;
- nFault = -1;
- this.resetRegs();
- fDispatch = fHalt = false;
- }
- }
-
- if (X86.fnCheckFault.call(this, nFault, nError, fHalt)) {
- /*
- * If this is a fault that would normally be dispatched BUT fnCheckFault() wants us to halt,
- * then we throw a bogus fault number (-1), simply to interrupt the current instruction in exactly
- * the same way that a dispatched fault would interrupt it.
- */
- if (fDispatch) throw -1;
- }
-
- if (fDispatch) {
-
- this.nFault = nFault;
- X86.fnINT.call(this, nFault, nError, nCycles);
-
- /*
- * REP'eated instructions that rewind regLIP to opLIP used to screw up this dispatch,
- * so now we slip the new regLIP into opLIP, effectively turning their action into a no-op.
- */
- this.opLIP = this.regLIP;
-
- /*
- * X86.OPFLAG.FAULT flag is used by selected opcodes to provide an early exit, restore register(s),
- * or whatever is needed to help ensure instruction restartability; there is currently no general
- * mechanism for snapping and restoring all registers for any instruction that might fault.
- *
- * X86.EXCEPTION.DB_EXC exceptions set their own special flag, X86.OPFLAG.DBEXC, to prevent redundant
- * DEBUG exceptions, so we don't need to set OPFLAG.FAULT in that case, because a DEBUG exception
- * doesn't actually prevent an instruction from executing (and therefore doesn't need to be restarted).
- */
- if (nFault == X86.EXCEPTION.DB_EXC) {
- this.opFlags |= X86.OPFLAG.DBEXC;
- } else {
- this.assert(nFault >= 0);
- this.opFlags |= X86.OPFLAG.FAULT;
- }
-
- /*
- * Since this fault is likely being issued in the context of an instruction that hasn't finished
- * executing, if we don't do anything to interrupt that execution (eg, throw a JavaScript exception),
- * then we would need to shut off all further reads/writes for the current instruction.
- *
- * That's easy for any EA-based memory accesses: simply set both the NOREAD and NOWRITE flags.
- * However, there are also direct, non-EA-based memory accesses to consider. A perfect example is
- * opPUSHA(): if a GP fault occurs on any PUSH other than the last, a subsequent PUSH is likely to
- * cause another fault, which we will misinterpret as a double-fault -- unless the handler for
- * such an opcode checks this.opFlags for X86.OPFLAG.FAULT after each step of the operation.
- *
- * this.opFlags |= (X86.OPFLAG.NOREAD | X86.OPFLAG.NOWRITE);
- *
- * Fortunately, we now throw an exception that terminates the current instruction, so the above hack
- * should no longer be necessary.
- */
- throw nFault;
- }
-};
-
-/**
- * fnPageFault(addr, fPresent, fWrite)
- *
- * Helper to dispatch page faults.
- *
- * @this {X86CPU}
- * @param {number} addr
- * @param {boolean} fPresent
- * @param {boolean} fWrite
- */
-X86.fnPageFault = function(addr, fPresent, fWrite)
-{
- this.regCR2 = addr;
- var nError = 0;
- if (fPresent) nError |= X86.PTE.PRESENT;
- if (fWrite) nError |= X86.PTE.READWRITE;
- if (this.nCPL == 3) nError |= X86.PTE.USER;
- X86.fnFault.call(this, X86.EXCEPTION.PF_FAULT, nError);
-};
-
-/**
- * fnCheckFault(nFault, nError, fHalt)
- *
- * Aside from giving the Debugger an opportunity to report every fault, this also gives us the ability to
- * halt exception processing in tracks: return true to prevent the fault handler from being dispatched.
- *
- * At the moment, the only Debugger control you have over fault interception is setting MESSAGE.FAULT, which
- * will display faults as they occur, and MESSAGE.HALT, which will halt after any Debugger message, including
- * MESSAGE.FAULT. If you want execution to continue after halting, clear MESSAGE.FAULT and/or MESSAGE.HALT,
- * or single-step over the offending instruction, which will allow the fault to be dispatched.
- *
- * @this {X86CPU}
- * @param {number} nFault
- * @param {number|null} [nError] (if omitted, no error code will be reported)
- * @param {boolean} [fHalt] (true to halt the CPU, false to not, undefined if "it depends")
- * @return {boolean|undefined} true to block the fault (often desirable when fHalt is true), otherwise dispatch it
- */
-X86.fnCheckFault = function(nFault, nError, fHalt)
-{
- var bitsMessage = Messages.FAULT;
-
- var bOpcode = this.probeAddr(this.regLIP);
-
- /*
- * OS/2 1.0 uses an INT3 (0xCC) opcode in conjunction with an invalid IDT to trigger a triple-fault
- * reset and return to real-mode, and these resets happen quite frequently during boot; for example,
- * OS/2 startup messages are displayed using a series of INT 0x10 BIOS calls for each character, and
- * each series of BIOS calls requires a round-trip mode switch.
- *
- * Since we really only want to halt on "bad" faults, not "good" (ie, intentional) faults, we take
- * advantage of the fact that all 3 faults comprising the triple-fault point to an INT3 (0xCC) opcode,
- * and so whenever we see that opcode, we ignore the caller's fHalt flag, and suppress FAULT messages
- * unless CPU messages are also enabled.
- *
- * When a triple fault shows up, nFault is -1; it displays as 0xff only because we use toHexByte().
- */
- if (bOpcode == X86.OPCODE.INT3 && !this.addrIDTLimit) {
- fHalt = false;
- }
-
- /*
- * There are a number of V86-mode exceptions we don't need to know about. For starters, Windows 3.00
- * (and other versions of enhanced-mode Windows) use an ARPL to switch out of V86-mode, so we can ignore
- * those UD_FAULTs.
- *
- * Ditto for software interrupts, which will generate a GP_FAULT when the interrupt number (eg, 0x6D)
- * exceeds the protected-mode IDT's limit (eg, a limit of 0x2FF corresponds to a maximum interrupt number
- * of 0x5F). Windows doesn't really care if its IDT is too small, because it has to simulate all software
- * interrupts in V86-mode regardless (they generate a GP_FAULT if IOPL < 3, and even when IOPL == 3, only
- * the protected-mode IDT handler gets to run).
- */
- if ((this.regPS & X86.PS.VM)) {
- if (nFault == X86.EXCEPTION.UD_FAULT && bOpcode == X86.OPCODE.ARPL ||
- nFault == X86.EXCEPTION.GP_FAULT && bOpcode == X86.OPCODE.INTN) {
- fHalt = false;
- }
- }
-
- /*
- * If fHalt has been explicitly set to false, we also take that as a cue to disable fault messages
- * (which you can override by turning on CPU messages).
- */
- if (fHalt === false) {
- bitsMessage |= Messages.CPU;
- }
-
- /*
- * Similarly, the PC AT ROM BIOS deliberately generates a couple of GP faults as part of the POST
- * (Power-On Self Test); we don't want to ignore those, but we don't want to halt on them either. We
- * detect those faults by virtue of the LIP being in the range 0x0F0000 to 0x0FFFFF.
- *
- * TODO: Be aware that this test can trigger false positives, such as when a V86-mode ARPL is hit; eg:
- *
- * &FD82:22F7 6338 ARPL [BX+SI],DI
- */
- if (this.regLIP >= 0x0F0000 && this.regLIP <= 0x0FFFFF) {
- fHalt = false;
- }
-
- /*
- * However, the foregoing notwithstanding, if MESSAGE.HALT is enabled along with all the other required
- * MESSAGE bits, then we want to halt regardless.
- */
- if (this.messageEnabled(bitsMessage | Messages.HALT)) {
- fHalt = true;
- }
-
- if (this.messageEnabled(bitsMessage) || fHalt) {
-
- var fRunning = this.aFlags.fRunning;
- var sMessage = "Fault " + str.toHexByte(nFault) + (nError != null? " (" + str.toHexWord(nError) + ")" : "") + " on opcode " + str.toHexByte(bOpcode);
- if (fHalt && fRunning) sMessage += " (blocked by PCjs Debugger)";
-
- if (DEBUGGER && this.dbg) {
- this.printMessage(sMessage, fHalt || bitsMessage, true);
- if (fHalt) {
- /*
- * By setting fHalt to fRunning (which is true while running but false while single-stepping),
- * this allows a fault to be dispatched when you single-step over a faulting instruction; you can
- * then continue single-stepping into the fault handler, or start running again.
- *
- * Note that we had to capture fRunning before calling printMessage(), because if MESSAGE.HALT
- * is set, printMessage() will have already halted the CPU.
- */
- fHalt = fRunning;
- this.dbg.stopCPU();
- }
- } else {
- /*
- * If there's no Debugger, then messageEnabled() must have returned false, which means that fHalt must
- * be true. Which means we should shut the machine down.
- */
- this.assert(fHalt);
- this.notice(sMessage);
- this.stopCPU();
- }
- }
- return fHalt;
-};
diff --git a/modules/pcjs/lib/x86mods.js b/modules/pcjs/lib/x86mods.js
index 046b43779..d7c7001a6 100644
--- a/modules/pcjs/lib/x86mods.js
+++ b/modules/pcjs/lib/x86mods.js
@@ -37,17 +37,15 @@ if (NODE) {
}
/**
- * decodeModRegByte16(bModRM, fn)
+ * decodeModRegByte16(fn)
*
* @this {X86CPU}
- * @param {number} bModRM
* @param {function(number,number)} fn (dst,src)
*/
-X86.decodeModRegByte16 = function(bModRM, fn)
+X86.decodeModRegByte16 = function(fn)
{
var dst, src;
- this.bModRM = bModRM;
- bModRM &= 0xC7;
+ var bModRM = (this.bModRM = this.getIPByte()) & 0xC7;
switch(bModRM) {
case 0x00:
@@ -184,7 +182,7 @@ X86.decodeModRegByte16 = function(bModRM, fn)
break;
}
- var reg = (bModRM >> 3) & 0x7;
+ var reg = (this.bModRM >> 3) & 0x7;
switch(reg) {
case 0x0:
@@ -255,17 +253,15 @@ X86.decodeModRegByte16 = function(bModRM, fn)
};
/**
- * decodeModMemByte16(bModRM, fn)
+ * decodeModMemByte16(fn)
*
* @this {X86CPU}
- * @param {number} bModRM
* @param {function(number,number)} fn (dst,src)
*/
-X86.decodeModMemByte16 = function(bModRM, fn)
+X86.decodeModMemByte16 = function(fn)
{
var dst, src;
- this.bModRM = bModRM;
- bModRM &= 0xC7;
+ var bModRM = (this.bModRM = this.getIPByte()) & 0xC7;
switch(bModRM) {
case 0x00:
@@ -394,7 +390,7 @@ X86.decodeModMemByte16 = function(bModRM, fn)
break;
}
- var reg = (bModRM >> 3) & 0x7;
+ var reg = (this.bModRM >> 3) & 0x7;
switch(reg) {
case 0x0:
@@ -521,7 +517,7 @@ X86.decodeModMemByte16 = function(bModRM, fn)
};
/**
- * decodeModGrpByte16(bModRM, afnGrp, fnSrc)
+ * decodeModGrpByte16(afnGrp, fnSrc)
*
* @this {X86CPU}
* @param {Array.} afnGrp
@@ -658,7 +654,7 @@ X86.decodeModGrpByte16 = function(afnGrp, fnSrc) {
break;
}
- var reg = (bModRM >> 3) & 0x7;
+ var reg = (this.bModRM >> 3) & 0x7;
var b = afnGrp[reg].call(this, dst, fnSrc.call(this));
@@ -897,7 +893,7 @@ X86.decodeModRegShort16 = function(fn)
break;
}
- var reg = (bModRM >> 3) & 0x7;
+ var reg = (this.bModRM >> 3) & 0x7;
switch(reg) {
case 0x0:
@@ -1118,7 +1114,7 @@ X86.decodeModMemShort16 = function(fn)
break;
}
- var reg = (bModRM >> 3) & 0x7;
+ var reg = (this.bModRM >> 3) & 0x7;
switch(reg) {
case 0x0:
@@ -1411,7 +1407,7 @@ X86.decodeModGrpShort16 = function(afnGrp, fnSrc) {
break;
}
- var reg = (bModRM >> 3) & 0x7;
+ var reg = (this.bModRM >> 3) & 0x7;
var w = afnGrp[reg].call(this, dst, fnSrc.call(this));
@@ -1650,7 +1646,7 @@ X86.decodeModRegLong16 = function(fn)
break;
}
- var reg = (bModRM >> 3) & 0x7;
+ var reg = (this.bModRM >> 3) & 0x7;
switch(reg) {
case 0x0:
@@ -1871,7 +1867,7 @@ X86.decodeModMemLong16 = function(fn)
break;
}
- var reg = (bModRM >> 3) & 0x7;
+ var reg = (this.bModRM >> 3) & 0x7;
switch(reg) {
case 0x0:
@@ -2163,7 +2159,7 @@ X86.decodeModGrpLong16 = function(afnGrp, fnSrc) {
break;
}
- var reg = (bModRM >> 3) & 0x7;
+ var reg = (this.bModRM >> 3) & 0x7;
var l = afnGrp[reg].call(this, dst, fnSrc.call(this));
@@ -2362,7 +2358,7 @@ X86.decodeModRegByte32 = function(fn)
break;
}
- var reg = (bModRM >> 3) & 0x7;
+ var reg = (this.bModRM >> 3) & 0x7;
switch(reg) {
case 0x0:
@@ -2570,7 +2566,7 @@ X86.decodeModMemByte32 = function(fn)
break;
}
- var reg = (bModRM >> 3) & 0x7;
+ var reg = (this.bModRM >> 3) & 0x7;
switch(reg) {
case 0x0:
@@ -2789,7 +2785,7 @@ X86.decodeModGrpByte32 = function(afnGrp, fnSrc) {
break;
}
- var reg = (bModRM >> 3) & 0x7;
+ var reg = (this.bModRM >> 3) & 0x7;
var b = afnGrp[reg].call(this, dst, fnSrc.call(this));
@@ -2962,7 +2958,7 @@ X86.decodeModRegShort32 = function(fn)
break;
}
- var reg = (bModRM >> 3) & 0x7;
+ var reg = (this.bModRM >> 3) & 0x7;
switch(reg) {
case 0x0:
@@ -3183,7 +3179,7 @@ X86.decodeModMemShort32 = function(fn)
break;
}
- var reg = (bModRM >> 3) & 0x7;
+ var reg = (this.bModRM >> 3) & 0x7;
switch(reg) {
case 0x0:
@@ -3431,7 +3427,7 @@ X86.decodeModGrpShort32 = function(afnGrp, fnSrc) {
break;
}
- var reg = (bModRM >> 3) & 0x7;
+ var reg = (this.bModRM >> 3) & 0x7;
var w = afnGrp[reg].call(this, dst, fnSrc.call(this));
@@ -3604,7 +3600,7 @@ X86.decodeModRegLong32 = function(fn)
break;
}
- var reg = (bModRM >> 3) & 0x7;
+ var reg = (this.bModRM >> 3) & 0x7;
switch(reg) {
case 0x0:
@@ -3825,7 +3821,7 @@ X86.decodeModMemLong32 = function(fn)
break;
}
- var reg = (bModRM >> 3) & 0x7;
+ var reg = (this.bModRM >> 3) & 0x7;
switch(reg) {
case 0x0:
@@ -4073,7 +4069,7 @@ X86.decodeModGrpLong32 = function(afnGrp, fnSrc) {
break;
}
- var reg = (bModRM >> 3) & 0x7;
+ var reg = (this.bModRM >> 3) & 0x7;
var l = afnGrp[reg].call(this, dst, fnSrc.call(this));
diff --git a/package.json b/package.json
index 2f6c4b505..94007bac5 100644
--- a/package.json
+++ b/package.json
@@ -110,7 +110,6 @@
"./modules/pcjs/lib/x86fpu.js",
"./modules/pcjs/lib/x86func.js",
"./modules/pcjs/lib/x86help.js",
- "./modules/pcjs/lib/x86ints.js",
"./modules/pcjs/lib/x86mods.js",
"./modules/pcjs/lib/x86ops.js",
"./modules/pcjs/lib/x86op0f.js",