diff --git a/modules/pcjs/lib/x86.js b/modules/pcjs/lib/x86.js index faa45d3d6..95084bc5d 100644 --- a/modules/pcjs/lib/x86.js +++ b/modules/pcjs/lib/x86.js @@ -65,7 +65,7 @@ var X86 = { * Of the following exceptions, all are designed to be restartable, except for 0x08 and 0x09 (and 0x0D * after an attempt to write to a read-only segment). * - * Error codes are pushed onto the stack for 0x08 (always 0) and 0x0A through 0x0D. + * Error codes are pushed onto the stack for 0x08 (always 0) and 0x0A through 0x0E. * * Priority: Instruction exception, TRAP, NMI, Processor Extension Segment Overrun, and finally INTR. * @@ -84,22 +84,22 @@ var X86 = { * The term "undocumented" should be limited to operations that are valid but Intel simply never documented. */ EXCEPTION: { - DIV_ERR: 0x00, // Divide Error Interrupt - DEBUG: 0x01, // Debug (aka Single Step Trap) Interrupt + DE_EXC: 0x00, // Divide Error Exception (#DE: fault, no error code) + DB_EXC: 0x01, // Debug (aka Single Step Trap) Exception (#DB: fault or trap) NMI: 0x02, // Non-Maskable Interrupt - BREAKPOINT: 0x03, // Breakpoint Interrupt - OVERFLOW: 0x04, // INTO Overflow Interrupt (FYI, return address does NOT point to offending instruction) - BOUND_ERR: 0x05, // BOUND Error Interrupt - UD_FAULT: 0x06, // Invalid (aka Undefined or Illegal) Opcode (see implementation detail above) - NM_FAULT: 0x07, // No Math Unit Available (see ESC or WAIT) - DF_FAULT: 0x08, // Double Fault (see LIDT) - MP_FAULT: 0x09, // Math Unit Protection Fault (see ESC) - TS_FAULT: 0x0A, // Invalid Task State Segment Fault (protected-mode only) - NP_FAULT: 0x0B, // Not Present Fault (protected-mode only) - SS_FAULT: 0x0C, // Stack Fault (protected-mode only) - GP_FAULT: 0x0D, // General Protection Fault - PG_FAULT: 0x0E, // Page Fault - MF_FAULT: 0x10 // Math Fault (see ESC or WAIT) + BP_TRAP: 0x03, // Breakpoint Exception (#BP: trap) + OF_TRAP: 0x04, // INTO Overflow Exception (#OF: trap) + BR_FAULT: 0x05, // BOUND Error Exception (#BR: fault, no error code) + UD_FAULT: 0x06, // Invalid (aka Undefined/Illegal) Opcode (#UD: fault, no error code) + NM_FAULT: 0x07, // No Math Unit Available; see ESC or WAIT (#NM: fault, no error code) + DF_FAULT: 0x08, // Double Fault; see LIDT (#DF: fault, with error code) + MP_FAULT: 0x09, // Math Unit Protection Fault; see ESC (#MP: fault, no error code) + TS_FAULT: 0x0A, // Invalid Task State Segment Fault (#TS: fault, with error code; protected-mode only) + NP_FAULT: 0x0B, // Not Present Fault (#NP: fault, with error code; protected-mode only) + SS_FAULT: 0x0C, // Stack Fault (#SS: fault, with error code; protected-mode only) + GP_FAULT: 0x0D, // General Protection Fault (#GP: fault, with error code) + PF_FAULT: 0x0E, // Page Fault (#PF: fault, with error code) + MF_FAULT: 0x10 // Math Fault; see ESC or WAIT (#MF: fault, no error code) }, /* * Processor Status flag definitions (stored in regPS) @@ -421,7 +421,7 @@ var X86 = { DATASIZE: 0x0400, // data size override ADDRSIZE: 0x0800, // address size override FAULT: 0x1000, // a fault occurred during the current instruction - DEBUG: 0x2000 // a DEBUG exception occurred during the current instruction + DBEXC: 0x2000 // a DB_EXC exception occurred during the current instruction }, /* * Bit values for intFlags diff --git a/modules/pcjs/lib/x86cpu.js b/modules/pcjs/lib/x86cpu.js index e63a06293..7b4bbdbd8 100644 --- a/modules/pcjs/lib/x86cpu.js +++ b/modules/pcjs/lib/x86cpu.js @@ -1403,7 +1403,7 @@ X86CPU.prototype.resetRegs = function() this.resultDst = this.resultSrc = this.resultArith = this.resultLogic = 0; /* - * nFault is set by fnFault() and reset (to -1) by resetRegs() and opIRET(). Its initial purpose is to + * nFault is set by fnFault() and reset (to -1) by resetRegs() and opIRET(). Its initial purpose was to * help fnFault() determine when a nested fault should be converted into either a double-fault (DF_FAULT) * or a triple-fault (ie, a processor reset). * @@ -1412,9 +1412,9 @@ X86CPU.prototype.resetRegs = function() * to the corresponding fault #, whereas the latter must set it to -1, so that if the IDT contains a gate * whose DPL < CPL, a GP fault will be generated instead. * - * The former always call fnFault(), so that happens automatically. The latter call fnINT(), so they must - * set nFault manually. There are also intermediate cases, like hardware interrupts, which call fnINT() - * after manually setting nFault to the IDT #. + * The former always call fnFault(), and the latter call fnTrap(), so nFault is updated automatically. + * However, there are also intermediate cases, like hardware interrupts, which call fnINT() after manually + * setting nFault to the IDT #. TODO: Review all those "intermediate" cases. */ this.nFault = -1; @@ -1896,13 +1896,13 @@ X86CPU.prototype.checkDebugRegisters = function(fEnable) X86CPU.prototype.checkMemoryException = function(addr, nb, fWrite) { /* - * NOTE: We're preventing redundant X86.EXCEPTION.DEBUG exceptions for a single instruction by checking - * X86.OPFLAG.DEBUG. I decided not to rely on the generic X86.OPFLAG.FAULT, because if an instruction + * NOTE: We're preventing redundant X86.EXCEPTION.DB_EXC exceptions for a single instruction by checking + * X86.OPFLAG.DBEXC. I decided not to rely on the generic X86.OPFLAG.FAULT, because if an instruction * first triggers a DIFFERENT exception which then triggers a DEBUG exception (eg, because a Debug register * was set on the IDT entry of the first exception), then presumably we'd like to see that DEBUG exception, * as opposed to, say, a double fault. TODO: Determine whether that SHOULD generate a double-fault. */ - if (!(this.opFlags & X86.OPFLAG.DEBUG) && (this.regDR[7] & X86.DR7.ENABLE)) { + if (!(this.opFlags & X86.OPFLAG.DBEXC) && (this.regDR[7] & X86.DR7.ENABLE)) { nb--; /* * We use a constant mask for the enable bits (X86.DR7.L0 | X86.DR7.G0) and shift our copy of regDR7 @@ -1928,7 +1928,13 @@ X86CPU.prototype.checkMemoryException = function(addr, nb, fWrite) */ if (addr + nb >= this.regDR[i] && addr <= this.regDR[i] + len) { this.regDR[6] |= (1 << i); - X86.fnFault.call(this, X86.EXCEPTION.DEBUG); + /* + * Data access breakpoints are not faults; they must generate a trap at the end of the + * instruction, so we use the X86.INTFLAG.TRAP flag to generate the X86.EXCEPTION.DB_EXC trap. + * + * X86.fnFault.call(this, X86.EXCEPTION.DB_EXC); + */ + this.intFlags |= X86.INTFLAG.TRAP; return; } } @@ -3145,7 +3151,7 @@ X86CPU.prototype.checkIOPM = function(port, nPorts, fInput) } if (bitsPorts) { if (this.messageEnabled(Messages.PORT)) this.printMessage("checkIOPM(" + str.toHexWord(port) + "," + nPorts + "," + (fInput? "input" : "output") + "): trapped", true, true); - X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0, false); + X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0); return false; } return true; @@ -4300,12 +4306,7 @@ X86CPU.prototype.checkINTR = function() if ((this.intFlags & X86.INTFLAG.TRAP)) { this.intFlags &= ~X86.INTFLAG.TRAP; if (I386 && this.model >= X86.MODEL_80386) this.regDR[6] |= X86.DR6.BS; - /* - * TODO: Perhaps we should call fnFault() instead; eg: - * - * X86.fnFault.call(this, X86.EXCEPTION.DEBUG, null, false, 11); - */ - X86.fnINT.call(this, this.nFault = X86.EXCEPTION.DEBUG, null, 11); + X86.fnINT.call(this, this.nFault = X86.EXCEPTION.DB_EXC, null, 11); return true; } break; diff --git a/modules/pcjs/lib/x86func.js b/modules/pcjs/lib/x86func.js index 18b851e70..d2fedc544 100644 --- a/modules/pcjs/lib/x86func.js +++ b/modules/pcjs/lib/x86func.js @@ -183,15 +183,12 @@ X86.fnBOUND = function(dst, src) this.nStepCycles -= this.cycleCounts.nOpCyclesBound; if (wIndex < wLower || wIndex > wUpper) { /* - * The INT 0x05 handler must be called with CS:IP pointing to the BOUND instruction, which - * fnFault() takes care of. TODO: Determine whether this should be treated like a fault, or like - * a software interrupt, with an explicit call to fnINT() and nFault = -1, like opINT3(), opINTn() - * and opINTO(). + * The INT 0x05 handler must be called with CS:IP pointing to the BOUND instruction. * * TODO: Determine the cycle cost when a BOUND exception is triggered, over and above nCyclesBound, - * and then call X86.fnFault(X86.EXCEPTION.BOUND_ERR, null, false, nCycles). + * and then call X86.fnFault(X86.EXCEPTION.BR_FAULT, null, nCycles). */ - X86.fnFault.call(this, X86.EXCEPTION.BOUND_ERR); + X86.fnFault.call(this, X86.EXCEPTION.BR_FAULT); } this.opFlags |= X86.OPFLAG.NOWRITE; return dst; @@ -1434,15 +1431,15 @@ X86.fnINCw = function(dst, src) * * @this {X86CPU} * @param {number} nIDT - * @param {number|null|undefined} nError - * @param {number} nCycles (in addition to the default of nOpCyclesInt) + * @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; + this.nStepCycles -= this.cycleCounts.nOpCyclesInt + (nCycles || 0); var oldPS = this.getPS(); var oldCS = this.getCS(); var oldIP = this.getIP(); @@ -3858,9 +3855,19 @@ X86.fnGRPUndefined = function(dst, src) 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. */ - X86.fnFault.call(this, X86.EXCEPTION.DIV_ERR, null, false, 2); + 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); + } }; /** @@ -3928,17 +3935,32 @@ X86.fnSRCxx = function() }; /** - * fnFault(nFault, nError, fHalt, nCycles) + * fnTrap(nIDT, nCycles) * - * Helper to dispatch faults. + * 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 {boolean} [fHalt] (true to halt the CPU, false to not, undefined if "it depends") * @param {number} [nCycles] cycle count to pass through to fnINT(), if any + * @param {boolean} [fHalt] (true to halt the CPU, false to not, undefined if "it depends") */ -X86.fnFault = function(nFault, nError, fHalt, nCycles) +X86.fnFault = function(nFault, nError, nCycles, fHalt) { var fDispatch = null; @@ -3990,7 +4012,7 @@ X86.fnFault = function(nFault, nError, fHalt, nCycles) if (fDispatch) { this.nFault = nFault; - X86.fnINT.call(this, nFault, nError, nCycles || 0); + X86.fnINT.call(this, nFault, nError, nCycles); /* * REP'eated instructions that rewind regLIP to opLIP used to screw up this dispatch, @@ -4003,12 +4025,12 @@ X86.fnFault = function(nFault, nError, fHalt, nCycles) * 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.DEBUG exceptions set their own special flag, X86.OPFLAG.DEBUG, to prevent redundant + * 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.DEBUG) { - this.opFlags |= X86.OPFLAG.DEBUG; + if (nFault == X86.EXCEPTION.DB_EXC) { + this.opFlags |= X86.OPFLAG.DBEXC; } else { this.assert(nFault >= 0); this.opFlags |= X86.OPFLAG.FAULT; @@ -4051,7 +4073,7 @@ X86.fnPageFault = function(addr, fPresent, fWrite) 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.PG_FAULT, nError); + X86.fnFault.call(this, X86.EXCEPTION.PF_FAULT, nError); }; /** @@ -4111,7 +4133,7 @@ X86.fnFaultMessage = function(nFault, nError, fHalt) fHalt = false; } } - if (nFault == X86.EXCEPTION.PG_FAULT && bOpcode == X86.OPCODE.IRET) { + if (nFault == X86.EXCEPTION.PF_FAULT && bOpcode == X86.OPCODE.IRET) { fHalt = true; } diff --git a/modules/pcjs/lib/x86op0f.js b/modules/pcjs/lib/x86op0f.js index d30422de1..f60c8a2af 100644 --- a/modules/pcjs/lib/x86op0f.js +++ b/modules/pcjs/lib/x86op0f.js @@ -149,7 +149,7 @@ X86.opLOADALL286 = function LOADALL286() /* * To use LOADALL, CPL must be zero. */ - X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0, true); + X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0, 0, true); return; } this.setMSW(this.getShort(0x806)); @@ -301,7 +301,7 @@ X86.opLOADALL386 = function LOADALL386() /* * To use LOADALL, CPL must be zero. */ - X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0, true); + X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0, 0, true); return; } /* diff --git a/modules/pcjs/lib/x86ops.js b/modules/pcjs/lib/x86ops.js index d4d2b6c31..c6a5c7eee 100644 --- a/modules/pcjs/lib/x86ops.js +++ b/modules/pcjs/lib/x86ops.js @@ -3613,33 +3613,12 @@ X86.opINT3 = function INT3() return; } /* - * To give our own Debugger the ability to stop execution on INT3, I thought about treating this as - * a fault rather than an interrupt, in order to leverage the existing Debugger logic inside fnFault() - * processing, but that has the unwanted side-effect of rewinding EIP to the INT3 prior to issuing - * the interrupt, and the corresponding IRET takes us right back to the INT3. - * - * X86.fnFault.call(this, X86.EXCEPTION.BREAKPOINT, null, false, this.cycleCounts.nOpCyclesInt3D); - * - * Then I had the idea of using the fnFaultMessage() function, in much the same way that fnFault() - * does for actual faults: if the user turned on the FAULT and HALT message bits, then fnFaultMessage() - * would tell us to halt; otherwise, we'd perform the normal fnINT() call. - * - * if (X86.fnFaultMessage.call(this, X86.EXCEPTION.BREAKPOINT)) { - * this.setIP(this.opLIP - this.segCS.base); - * return; - * } - * - * However, that makes it a little tedious to get past the INT3 (you have to use a Debugger command - * like "t;g"), and a somewhat confusing fault message is displayed; eg: - * - * Fault 0x03 on opcode 0xB4 at 09CE:0155 (%009E35) - * - * The best solution was to leave this function alone, and change the Debugger's checkBreakpoint() - * function to stop execution on INT3 whenever both the INT and HALT message bits are set; a simple "g" - * command allows you to continue. + * Because INT3 is a trap, not a fault, we must use fnTrap() rather than fnFault(). Unfortunately, that + * means you can't rely on the Debugger logic instead fnFault() to conditionally stop execution on an INT3, + * so I've changed the Debugger's checkBreakpoint() function to stop execution on INT3 whenever both the + * INT and HALT message bits are set; a simple "g" command allows you to continue. */ - this.nFault = -1; - X86.fnINT.call(this, X86.EXCEPTION.BREAKPOINT, null, this.cycleCounts.nOpCyclesInt3D); + X86.fnTrap.call(this, X86.EXCEPTION.BP_TRAP, this.cycleCounts.nOpCyclesInt3D); }; /** @@ -3663,8 +3642,7 @@ X86.opINTn = function INTn() * and returns false ONLY if a notification handler returned false (ie, requesting the interrupt be skipped). */ if (this.checkIntNotify(nInt)) { - this.nFault = -1; - X86.fnINT.call(this, nInt, null, 0); + X86.fnTrap.call(this, nInt, 0); return; } this.nStepCycles--; // we don't need to assess the full cost of nOpCyclesInt, but we need to assess something... @@ -3686,8 +3664,7 @@ X86.opINTO = function INTO() X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0); return; } - this.nFault = -1; - X86.fnINT.call(this, X86.EXCEPTION.OVERFLOW, null, this.cycleCounts.nOpCyclesIntOD); + X86.fnTrap.call(this, X86.EXCEPTION.OF_TRAP, this.cycleCounts.nOpCyclesIntOD); return; } this.nStepCycles -= this.cycleCounts.nOpCyclesIntOFall; diff --git a/modules/pcjs/lib/x86seg.js b/modules/pcjs/lib/x86seg.js index 0a5610841..df98dc14d 100644 --- a/modules/pcjs/lib/x86seg.js +++ b/modules/pcjs/lib/x86seg.js @@ -105,7 +105,7 @@ function X86Seg(cpu, id, sName, fProt) * Preallocated object for "probed" segment loads */ this.probe = { - sel: 0, base: 0, limit: 0, acc: 0, type: 0, ext: 0, addrDesc: X86.ADDR_INVALID + sel: -1, base: 0, limit: 0, acc: 0, type: 0, ext: 0, addrDesc: X86.ADDR_INVALID }; /* @@ -636,15 +636,15 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe) this.type = this.probe.type; this.ext = this.probe.ext; this.addrDesc = this.probe.addrDesc; - this.probe.sel = 0; + this.probe.sel = -1; this.updateMode(true, true, false); return this.base; } /* - * Any other load, probed or otherwise, should "flush" the probe cache, by setting probe.sel to zero. + * Any other load, probed or otherwise, should "flush" the probe cache, by setting probe.sel to -1. */ - this.probe.sel = 0; + this.probe.sel = -1; /* * Load the descriptor from memory. @@ -970,10 +970,7 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe) return X86.ADDR_INVALID; } if (!selMasked || type < X86.DESC.ACC.TYPE.SEG || (type & (X86.DESC.ACC.TYPE.CODE | X86.DESC.ACC.TYPE.WRITABLE)) != X86.DESC.ACC.TYPE.WRITABLE) { - /* - * TODO: Remove fHalt=true from this fnFault() call once this code path has been tested. - */ - if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel & X86.ERRCODE.SELMASK, true); + if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel & X86.ERRCODE.SELMASK); return X86.ADDR_INVALID; } break; @@ -981,10 +978,7 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe) case X86Seg.ID.TSS: var typeTSS = type & ~X86.DESC.ACC.TSS_BUSY; if (!selMasked || typeTSS != X86.DESC.ACC.TYPE.TSS286 && typeTSS != X86.DESC.ACC.TYPE.TSS386) { - /* - * TODO: Remove fHalt=true from this fnFault() call once this code path has been tested. - */ - if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel & X86.ERRCODE.SELMASK, true); + if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel & X86.ERRCODE.SELMASK); return X86.ADDR_INVALID; } /* @@ -1156,10 +1150,7 @@ X86Seg.prototype.switchTSS = function switchTSS(selNew, fNest) * TODO: Verify that it is (always) correct to require that the BUSY bit be currently set. */ if (!(cpu.segTSS.type & X86.DESC.ACC.TSS_BUSY)) { - /* - * TODO: Remove fHalt=true from this fnFault() call once this code path has been tested. - */ - X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, selNew & X86.ERRCODE.SELMASK, true); + X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, selNew & X86.ERRCODE.SELMASK); return false; } /* @@ -1179,10 +1170,7 @@ X86Seg.prototype.switchTSS = function switchTSS(selNew, fNest) if (fNest !== false) { if (cpu.segTSS.type & X86.DESC.ACC.TSS_BUSY) { - /* - * TODO: Remove fHalt=true from this fnFault() call once this code path has been tested. - */ - X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, selNew & X86.ERRCODE.SELMASK, true); + X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, selNew & X86.ERRCODE.SELMASK); return false; } cpu.setShort(cpu.segTSS.addrDesc + X86.DESC.ACC.OFFSET, cpu.segTSS.acc |= X86.DESC.ACC.TSS_BUSY); diff --git a/tests/pc/80386/test386.nasm b/tests/pc/80386/test386.nasm index 15a8e21e3..5c1618938 100644 --- a/tests/pc/80386/test386.nasm +++ b/tests/pc/80386/test386.nasm @@ -107,6 +107,18 @@ DSEG_PROT16 equ 0x0018 DSEG_PROT32 equ 0x0020 SSEG_PROT32 equ 0x0028 +OFF_INTDIVERR equ 0xe000 + +; +; The "defGate" macro defines an interrupt gate, given a selector (%1) and an offset (%2) +; +%macro defGate 2 + dw (%2 & 0xffff) + dw %1 + dw ACC_TYPE_GATE386_INT | ACC_PRESENT + dw (%2 >> 16) & 0xffff +%endmacro + ; ; The "defDesc" macro defines a descriptor, given a name (%1), base (%2), limit (%3), type (%4), and ext (%5) ; @@ -205,6 +217,12 @@ myGDT: defDesc NULL ; the first descriptor in any descriptor table is always a defDesc SSEG_PROT32,0x00010000,0x000effff,ACC_TYPE_DATA_WRITABLE,EXT_BIG myGDTEnd: +addrIDT:dw myIDTEnd - myIDT - 1 ; 16-bit limit of myIDT + dw myIDT, 0x000f ; 32-bit base address of myIDT + +myIDT: defGate CSEG_PROT32,OFF_INTDIVERR +myIDTEnd: + initGDT: %ifdef RAM_GDT set edi,RAM_GDT @@ -241,6 +259,9 @@ initGDT: mov eax,edx ; recover the base address of the current CS add eax,myGDT ; EAX == physical address of myGDT mov [cs:addrGDT+2],eax ; update the 32-bit base address of myGDT in addrGDT + mov eax,edx ; recover the base address of the current CS again + add eax,myIDT ; EAX == physical address of myIDT + mov [cs:addrIDT+2],eax ; update the 32-bit base address of myIDT in addrIDT mov ax,cs %ifdef REAL32 mov [cs:jmpReal+5],ax ; update the segment of the FAR jump that returns us to real-mode @@ -318,6 +339,7 @@ initPT: stosd goProt: cli ; make sure interrupts are off now, since we've not initialized the IDT yet + o32 lidt [cs:addrIDT] o32 lgdt [cs:addrGDT] mov cr3,esi mov eax,cr0 @@ -748,8 +770,10 @@ SIZE_LONG equ 2 %%beg: %ifidni %4,none %2 %3 - %else + %elifidni %5,none %2 %3,%4 + %else + %2 %3,%4,%5 %endif ret %%end: @@ -758,6 +782,7 @@ SIZE_LONG equ 2 strEAX: db "EAX=",0 strEDX: db "EDX=",0 strPS: db "PS=",0 +strDE: db "#DE ",0 ; when this is displayed, it indicates a Divide Error exception achSize db "BWD" tableOps: @@ -796,6 +821,13 @@ tableOps: defOp "IMULA",imul,edx,none,none,TYPE_MULDIV defOp "IMUL",imul,ax,dx,none,TYPE_MULDIV defOp "IMUL",imul,eax,edx,none,TYPE_MULDIV + defOp "IMUL8",imul,ax,dx,0x77,TYPE_ARITH1 + defOp "IMUL8",imul,ax,dx,-0x77,TYPE_ARITH1 + defOp "IMUL8",imul,eax,edx,0x77,TYPE_ARITH1 + defOp "IMUL8",imul,eax,edx,-0x77,TYPE_ARITH1 + defOp "IMUL16",imul,ax,0x777,none,TYPE_ARITH1 + defOp "IMUL32",imul,eax,0x777777,none,TYPE_ARITH1 + defOp "IDIVA",idiv,dl,none,none,TYPE_MULDIV db 0 align 4 @@ -858,6 +890,23 @@ typeValues: error: jmp error + times OFF_INTDIVERR-0x100-($-$$) nop + +intDivErr: + push esi + mov esi,strDE + call printStr + pop esi +; +; It's rather annoying that the 80386 treats #DE as a fault rather than a trap, leaving CS:EIP pointing to the +; faulting instruction. So we must "patch" the EIP on the stack to point to a RET; it's easier to use our own RET +; rather than figuring out how long the DIV instruction is. +; + mov dword [esp],intDivRet + iretd +intDivRet: + ret + doneProt: mov ax,DSEG_PROT16 mov ss,ax diff --git a/tests/pc/inc/x86.inc b/tests/pc/inc/x86.inc index 73978d110..c397d15ce 100644 --- a/tests/pc/inc/x86.inc +++ b/tests/pc/inc/x86.inc @@ -17,6 +17,7 @@ PS_MULDIV equ (PS_CF | PS_OF) CR0_MSW_PE equ 0x0001 CR0_PG equ 0x80000000 ; set if paging enabled +ACC_TYPE_GATE386_INT equ 0x0E00 ACC_TYPE_SEG equ 0x1000 ACC_PRESENT equ 0x8000 ACC_TYPE_CODE equ 0x0800 @@ -25,6 +26,7 @@ ACC_TYPE_WRITABLE equ 0x0200 ACC_TYPE_CODE_READABLE equ 0x1a00 ACC_TYPE_DATA_WRITABLE equ 0x1200 + EXT_NONE equ 0x0000 EXT_BIG equ 0x0040