Fixed divide-by-zero and data breakpoint exception dispatching
This commit is contained in:
parent
a3107a73ef
commit
d1d733e38b
8 changed files with 144 additions and 105 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue