v1.19.2: Added support for timer Read-Back command, segment probes, and improved instruction restartability
This commit is contained in:
parent
0398afab84
commit
927a71d681
171 changed files with 9205 additions and 2121 deletions
|
|
@ -548,7 +548,7 @@ ChipSet.IRQ = {
|
|||
* TIMER1, and TIMER2. For machines with a second PIT (eg, the DeskPro 386), we refer
|
||||
* to those additional counters as TIMER3, TIMER4, and TIMER5.
|
||||
*
|
||||
* In addition, if there's a need to refer to a specfic PIT, use PIT0 for the first PIT
|
||||
* In addition, if there's a need to refer to a specific PIT, use PIT0 for the first PIT
|
||||
* and PIT1 for the second. This mirrors how we refer to multiple DMA controllers
|
||||
* (eg, DMA0 and DMA1) and multiple PICs (eg, PIC0 and PIC1).
|
||||
*
|
||||
|
|
@ -590,7 +590,15 @@ ChipSet.PIT_CTRL = {
|
|||
SC_CTR0: 0x00,
|
||||
SC_CTR1: 0x40,
|
||||
SC_CTR2: 0x80,
|
||||
SC_BACK: 0xC0
|
||||
SC_BACK: 0xC0,
|
||||
SC_SHIFT: 6,
|
||||
RB_CTR0: 0x02,
|
||||
RB_CTR1: 0x04,
|
||||
RB_CTR2: 0x08,
|
||||
RB_STATUS: 0x10, // if this bit is CLEAR, then latch the current status of the selected counter(s)
|
||||
RB_COUNTS: 0x20, // if this bit is CLEAR, then latch the current count(s) of the selected counter(s)
|
||||
RB_NULL: 0x40, // bit set in Read-Back status byte if the counter has not been "fully loaded" yet
|
||||
RB_OUT: 0x80 // bit set in Read-Back status byte if fOUT is true
|
||||
};
|
||||
|
||||
ChipSet.TIMER_TICKS_PER_SEC = 1193181;
|
||||
|
|
@ -1052,8 +1060,8 @@ ChipSet.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
dbg.messageDump(Messages.PIC, function onDumpPIC() {
|
||||
chipset.dumpPIC();
|
||||
});
|
||||
dbg.messageDump(Messages.TIMER, function onDumpTimer() {
|
||||
chipset.dumpTimer();
|
||||
dbg.messageDump(Messages.TIMER, function onDumpTimer(sParm) {
|
||||
chipset.dumpTimer(sParm);
|
||||
});
|
||||
dbg.messageDump(Messages.CMOS, function onDumpCMOS() {
|
||||
chipset.dumpCMOS();
|
||||
|
|
@ -2042,7 +2050,7 @@ ChipSet.prototype.initTimer = function(iTimer, aState)
|
|||
countLatched: [0,0]
|
||||
};
|
||||
}
|
||||
var a = aState && aState.length == 13? aState : ChipSet.aTimerInit;
|
||||
var a = aState && aState.length >= 13? aState : ChipSet.aTimerInit;
|
||||
timer.countInit[0] = a[0][0]; timer.countInit[1] = a[0][1];
|
||||
timer.countStart[0] = a[1][0]; timer.countStart[1] = a[1][1];
|
||||
timer.countCurrent[0] = a[2][0]; timer.countCurrent[1] = a[2][1];
|
||||
|
|
@ -2053,9 +2061,11 @@ ChipSet.prototype.initTimer = function(iTimer, aState)
|
|||
timer.countIndex = a[7];
|
||||
timer.countBytes = a[8];
|
||||
timer.fOUT = a[9];
|
||||
timer.fLatched = a[10];
|
||||
timer.fCountLatched = a[10];
|
||||
timer.fCounting = a[11];
|
||||
timer.nCyclesStart = a[12];
|
||||
timer.bStatus = a[13] || 0;
|
||||
timer.fStatusLatched = a[14] || false;
|
||||
this.aTimers[iTimer] = timer;
|
||||
};
|
||||
|
||||
|
|
@ -2081,9 +2091,11 @@ ChipSet.prototype.saveTimers = function()
|
|||
timer.countIndex,
|
||||
timer.countBytes,
|
||||
timer.fOUT,
|
||||
timer.fLatched,
|
||||
timer.fCountLatched,
|
||||
timer.fCounting,
|
||||
timer.nCyclesStart
|
||||
timer.nCyclesStart,
|
||||
timer.bStatus,
|
||||
timer.fStatusLatched
|
||||
];
|
||||
}
|
||||
return data;
|
||||
|
|
@ -2332,14 +2344,19 @@ ChipSet.prototype.dumpPIC = function()
|
|||
};
|
||||
|
||||
/**
|
||||
* dumpTimer()
|
||||
* dumpTimer(sParm)
|
||||
*
|
||||
* Use "d timer" to dump all timers, or "d timer n" to dump only timer n.
|
||||
*
|
||||
* @this {ChipSet}
|
||||
* @param {string} [sParm]
|
||||
*/
|
||||
ChipSet.prototype.dumpTimer = function()
|
||||
ChipSet.prototype.dumpTimer = function(sParm)
|
||||
{
|
||||
if (DEBUGGER) {
|
||||
var nTimer = (sParm? +sParm : null);
|
||||
for (var iTimer = 0; iTimer < this.aTimers.length; iTimer++) {
|
||||
if (nTimer != null && iTimer != nTimer) continue;
|
||||
this.updateTimer(iTimer);
|
||||
var timer = this.aTimers[iTimer];
|
||||
var sDump = "TIMER" + iTimer + ":";
|
||||
|
|
@ -2349,7 +2366,7 @@ ChipSet.prototype.dumpTimer = function()
|
|||
count |= (timer.countCurrent[i] << (i * 8));
|
||||
}
|
||||
}
|
||||
sDump += " mode=" + timer.mode + " bytes=" + timer.countBytes + " count=" + str.toHexWord(count);
|
||||
sDump += " mode=" + (timer.mode >> 1) + " bytes=" + timer.countBytes + " count=" + str.toHexWord(count);
|
||||
this.dbg.println(sDump);
|
||||
}
|
||||
}
|
||||
|
|
@ -3467,12 +3484,26 @@ ChipSet.prototype.inTimer = function(iTimer, port, addrFrom)
|
|||
{
|
||||
var b;
|
||||
var timer = this.aTimers[iTimer];
|
||||
if (timer.countIndex == timer.countBytes) this.resetTimerIndex(iTimer);
|
||||
if (timer.fLatched) {
|
||||
return timer.countLatched[timer.countIndex++];
|
||||
|
||||
if (timer.fStatusLatched) {
|
||||
b = timer.bStatus;
|
||||
timer.fStatusLatched = false;
|
||||
}
|
||||
else {
|
||||
if (timer.countIndex == timer.countBytes) {
|
||||
this.resetTimerIndex(iTimer);
|
||||
}
|
||||
if (timer.fCountLatched) {
|
||||
b = timer.countLatched[timer.countIndex++];
|
||||
if (timer.countIndex == timer.countBytes) {
|
||||
timer.fCountLatched = false
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.updateTimer(iTimer);
|
||||
b = timer.countCurrent[timer.countIndex++];
|
||||
}
|
||||
}
|
||||
this.updateTimer(iTimer);
|
||||
b = timer.countCurrent[timer.countIndex++];
|
||||
if (this.messageEnabled(Messages.TIMER | Messages.PORT)) {
|
||||
this.printMessageIO(port, null, addrFrom, "TIMER" + iTimer, b, true);
|
||||
}
|
||||
|
|
@ -3502,16 +3533,22 @@ ChipSet.prototype.outTimer = function(iTimer, port, bOut, addrFrom)
|
|||
if (this.messageEnabled(Messages.TIMER | Messages.PORT)) {
|
||||
this.printMessageIO(port, bOut, addrFrom, "TIMER" + iTimer, null, true);
|
||||
}
|
||||
|
||||
var timer = this.aTimers[iTimer];
|
||||
if (timer.countIndex == timer.countBytes) this.resetTimerIndex(iTimer);
|
||||
|
||||
if (timer.countIndex == timer.countBytes) {
|
||||
this.resetTimerIndex(iTimer);
|
||||
}
|
||||
|
||||
timer.countInit[timer.countIndex++] = bOut;
|
||||
|
||||
if (timer.countIndex == timer.countBytes) {
|
||||
/*
|
||||
* In general, writing a new count to a timer that's already counting isn't supposed to affect the current
|
||||
* count, with the notable exceptions of MODE0 and MODE4.
|
||||
*/
|
||||
if (!timer.fCounting || timer.mode == ChipSet.PIT_CTRL.MODE0 || timer.mode == ChipSet.PIT_CTRL.MODE4) {
|
||||
timer.fLatched = false;
|
||||
timer.fCountLatched = false;
|
||||
timer.countCurrent[0] = timer.countStart[0] = timer.countInit[0];
|
||||
timer.countCurrent[1] = timer.countStart[1] = timer.countInit[1];
|
||||
timer.nCyclesStart = this.cpu.getCycles(this.fScaleTimers);
|
||||
|
|
@ -3552,8 +3589,11 @@ ChipSet.prototype.outTimer = function(iTimer, port, bOut, addrFrom)
|
|||
ChipSet.prototype.inPIT1Ctrl = function(port, addrFrom)
|
||||
{
|
||||
this.printMessageIO(port, null, addrFrom, "PIT1_CTRL", null, Messages.TIMER);
|
||||
if (DEBUG) this.printMessage("PIT1_CTRL: Read-Back command not supported (yet)", Messages.TIMER);
|
||||
return null;
|
||||
/*
|
||||
* NOTE: Even though reads to port 0x43 are undefined (I think), I'm going to "define" it
|
||||
* as returning the last value written, purely for the Debugger's benefit.
|
||||
*/
|
||||
return this.bPIT1Ctrl;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -3568,23 +3608,53 @@ ChipSet.prototype.outPIT1Ctrl = function(port, bOut, addrFrom)
|
|||
{
|
||||
this.bPIT1Ctrl = bOut;
|
||||
this.printMessageIO(port, bOut, addrFrom, "PIT1_CTRL", null, Messages.TIMER);
|
||||
|
||||
/*
|
||||
* Extract the SC (Select Counter) bits
|
||||
* Extract the SC (Select Counter) bits.
|
||||
*/
|
||||
var iTimer = (bOut & ChipSet.PIT_CTRL.SC) >> 6;
|
||||
if (iTimer == 0x3) {
|
||||
if (DEBUG) this.printMessage("PIT1_CTRL: Read-Back command not supported (yet)", Messages.TIMER);
|
||||
var iTimer = (bOut & ChipSet.PIT_CTRL.SC);
|
||||
|
||||
/*
|
||||
* Check for the Read-Back command and process as needed.
|
||||
*/
|
||||
if (iTimer == ChipSet.PIT_CTRL.SC_BACK) {
|
||||
if (!(bOut & ChipSet.PIT_CTRL.RB_STATUS)) {
|
||||
for (iTimer = 0; iTimer <= 2; iTimer++) {
|
||||
if (bOut & (ChipSet.PIT_CTRL.RB_CTR0 << iTimer)) {
|
||||
this.latchTimerStatus(iTimer);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!(bOut & ChipSet.PIT_CTRL.RB_COUNTS)) {
|
||||
for (iTimer = 0; iTimer <= 2; iTimer++) {
|
||||
if (bOut & (ChipSet.PIT_CTRL.RB_CTR0 << iTimer)) {
|
||||
this.latchTimerCount(iTimer);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Extract the BCD, MODE, and RW bits, which we simply store as-is (see setTimerMode)
|
||||
* Convert the SC (Select Counter) bits into an iTimer index (0-2).
|
||||
*/
|
||||
iTimer >>= ChipSet.PIT_CTRL.SC_SHIFT;
|
||||
|
||||
/*
|
||||
* Extract BCD (bit 0), MODE (bits 1-3), and RW (bits 4-5), which we simply store as-is (see setTimerMode).
|
||||
*/
|
||||
var bcd = (bOut & ChipSet.PIT_CTRL.BCD);
|
||||
var mode = (bOut & ChipSet.PIT_CTRL.MODE);
|
||||
var rw = (bOut & ChipSet.PIT_CTRL.RW);
|
||||
if (!rw) {
|
||||
this.latchTimer(iTimer);
|
||||
} else {
|
||||
|
||||
if (rw == ChipSet.PIT_CTRL.RW_LATCH) {
|
||||
/*
|
||||
* Of all the RW bit combinations, this is the only one that "countermands" normal control register
|
||||
* processing (the BCD and MODE bits are "don't care").
|
||||
*/
|
||||
this.latchTimerCount(iTimer);
|
||||
}
|
||||
else {
|
||||
this.setTimerMode(iTimer, bcd, mode, rw);
|
||||
|
||||
/*
|
||||
|
|
@ -3692,32 +3762,48 @@ ChipSet.prototype.getTimerCycleLimit = function(iTimer, nCycles)
|
|||
};
|
||||
|
||||
/**
|
||||
* latchTimer(iTimer)
|
||||
* latchTimerCount(iTimer)
|
||||
*
|
||||
* @this {ChipSet}
|
||||
* @param {number} iTimer
|
||||
*/
|
||||
ChipSet.prototype.latchTimer = function(iTimer)
|
||||
ChipSet.prototype.latchTimerCount = function(iTimer)
|
||||
{
|
||||
/*
|
||||
* Update the timer's current count
|
||||
* Update the timer's current count.
|
||||
*/
|
||||
this.updateTimer(iTimer);
|
||||
|
||||
/*
|
||||
* Now we can latch it
|
||||
* Now we can latch it.
|
||||
*/
|
||||
var timer = this.aTimers[iTimer];
|
||||
timer.countLatched[0] = timer.countCurrent[0];
|
||||
timer.countLatched[1] = timer.countCurrent[1];
|
||||
timer.fLatched = true;
|
||||
timer.fCountLatched = true;
|
||||
|
||||
/*
|
||||
* VERIFY: That a latch request resets the timer index
|
||||
* VERIFY: That a latch request resets the timer index.
|
||||
*/
|
||||
this.resetTimerIndex(iTimer);
|
||||
};
|
||||
|
||||
/**
|
||||
* latchTimerStatus(iTimer)
|
||||
*
|
||||
* @this {ChipSet}
|
||||
* @param {number} iTimer
|
||||
*/
|
||||
ChipSet.prototype.latchTimerStatus = function(iTimer)
|
||||
{
|
||||
var timer = this.aTimers[iTimer];
|
||||
if (!timer.fStatusLatched) {
|
||||
this.updateTimer(iTimer);
|
||||
timer.bStatus = timer.bcd | timer.mode | timer.rw | (timer.countIndex < timer.countBytes? ChipSet.PIT_CTRL.RB_NULL : 0) | (timer.fOUT? ChipSet.PIT_CTRL.RB_OUT : 0);
|
||||
timer.fStatusLatched = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* setTimerMode(iTimer, bcd, mode, rw)
|
||||
*
|
||||
|
|
@ -3740,8 +3826,9 @@ ChipSet.prototype.setTimerMode = function(iTimer, bcd, mode, rw)
|
|||
timer.countCurrent = [0, 0];
|
||||
timer.countLatched = [0, 0];
|
||||
timer.fOUT = false;
|
||||
timer.fLatched = false;
|
||||
timer.fCountLatched = false;
|
||||
timer.fCounting = false;
|
||||
timer.fStatusLatched = false;
|
||||
this.resetTimerIndex(iTimer);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2615,7 +2615,7 @@ if (DEBUGGER) {
|
|||
Debugger.prototype.message = function(sMessage, fAddress)
|
||||
{
|
||||
if (fAddress) {
|
||||
sMessage += " @" + this.hexOffset(this.cpu.getIP(), this.cpu.getCS());
|
||||
sMessage += " @" + this.hexOffset(this.cpu.getIP(), this.cpu.getCS()) + " (%" + str.toHex(this.cpu.regLIP, 6) + ")";
|
||||
}
|
||||
|
||||
if (this.sMessagePrev && sMessage == this.sMessagePrev) return;
|
||||
|
|
@ -5236,33 +5236,23 @@ if (DEBUGGER) {
|
|||
}
|
||||
|
||||
if (sCmd == "ds") { // transform a "ds" command into a "d desc" command
|
||||
sCmd = 'd';
|
||||
sCmd = "d";
|
||||
sLen = sAddr;
|
||||
sAddr = "desc";
|
||||
}
|
||||
|
||||
for (m in Debugger.MESSAGES) {
|
||||
if (sAddr == m) {
|
||||
var fnDumper = this.afnDumpers[m];
|
||||
if (fnDumper) {
|
||||
fnDumper(sLen);
|
||||
} else {
|
||||
this.println("no dump registered for " + sAddr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var cb = 0; // 0 is not a default; 0 triggers the appropriate defaults below
|
||||
if (sLen) {
|
||||
if (sLen.charAt(0) == 'l') {
|
||||
sLen = sLen.substr(1) || sBytes;
|
||||
}
|
||||
cb = this.parseValue(sLen) >>> 0; // negative lengths not allowed
|
||||
if (cb > 0x10000) cb = 0x10000; // prevent bad user (or register) input from producing excessive output
|
||||
}
|
||||
|
||||
if (sCmd == 'd') {
|
||||
for (m in Debugger.MESSAGES) {
|
||||
if (sAddr == m) {
|
||||
var fnDumper = this.afnDumpers[m];
|
||||
if (fnDumper) {
|
||||
fnDumper(sLen);
|
||||
} else {
|
||||
this.println("no dump registered for " + sAddr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
sCmd = this.sCmdDumpPrev || "db";
|
||||
} else {
|
||||
this.sCmdDumpPrev = sCmd;
|
||||
|
|
@ -5282,6 +5272,15 @@ if (DEBUGGER) {
|
|||
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR_DATA);
|
||||
if (!dbgAddr || dbgAddr.sel == null && dbgAddr.addr == null) return;
|
||||
|
||||
var cb = 0; // 0 is not a default; it triggers the appropriate default below
|
||||
if (sLen) {
|
||||
if (sLen.charAt(0) == 'l') {
|
||||
sLen = sLen.substr(1) || sBytes;
|
||||
}
|
||||
cb = this.parseValue(sLen) >>> 0; // negative lengths not allowed
|
||||
if (cb > 0x10000) cb = 0x10000; // prevent bad user (or register) input from producing excessive output
|
||||
}
|
||||
|
||||
var sDump = "";
|
||||
var cLines = (((cb || 128) + 15) >> 4) || 1;
|
||||
var size = (sCmd == "dd"? 4 : (sCmd == "dw"? 2 : 1));
|
||||
|
|
|
|||
|
|
@ -6722,7 +6722,7 @@ Video.prototype.inCardStatus = function(card, addrFrom)
|
|||
* dumpVideo(sParm)
|
||||
*
|
||||
* @this {Video}
|
||||
* @param {string|undefined} sParm
|
||||
* @param {string} [sParm]
|
||||
*/
|
||||
Video.prototype.dumpVideo = function(sParm)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1294,11 +1294,10 @@ X86CPU.prototype.resetRegs = function()
|
|||
* or a triple-fault (ie, a processor reset).
|
||||
*/
|
||||
this.nFault = -1;
|
||||
|
||||
/*
|
||||
* These are used to snapshot regLIP and regLSP, to help make instructions restartable;
|
||||
* currently opLIP is updated prior to every instruction, but opLSP is updated only for
|
||||
* "problematic" instructions (eg, RETF) and should otherwise remain set to X86.ADDR_INVALID.
|
||||
* currently opLIP is updated prior to every instruction, but opLSP is updated only for instructions
|
||||
* that read/write the stack (eg, RETF) and should otherwise remain set to X86.ADDR_INVALID.
|
||||
*/
|
||||
this.opLIP = this.opLSP = X86.ADDR_INVALID;
|
||||
|
||||
|
|
@ -2098,11 +2097,15 @@ X86CPU.prototype.getCS = function()
|
|||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} sel
|
||||
* @return {boolean}
|
||||
*/
|
||||
X86CPU.prototype.setCS = function(sel)
|
||||
{
|
||||
this.setCSIP(this.getIP(), sel);
|
||||
if (!BUGS_8086) this.opFlags |= this.OPFLAG_NOINTR_8086;
|
||||
if (this.setCSIP(this.getIP(), sel) != null) {
|
||||
if (!BUGS_8086) this.opFlags |= this.OPFLAG_NOINTR_8086;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -2124,8 +2127,11 @@ X86CPU.prototype.getDS = function()
|
|||
*/
|
||||
X86CPU.prototype.setDS = function(sel)
|
||||
{
|
||||
this.segDS.load(sel);
|
||||
if (!BUGS_8086) this.opFlags |= this.OPFLAG_NOINTR_8086;
|
||||
if (this.segDS.load(sel) !== X86.ADDR_INVALID) {
|
||||
if (!BUGS_8086) this.opFlags |= this.OPFLAG_NOINTR_8086;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -2145,19 +2151,25 @@ X86CPU.prototype.getSS = function()
|
|||
* @this {X86CPU}
|
||||
* @param {number} sel
|
||||
* @param {boolean} [fInterruptable]
|
||||
* @return {boolean}
|
||||
*/
|
||||
X86CPU.prototype.setSS = function(sel, fInterruptable)
|
||||
{
|
||||
var regESP = this.getSP();
|
||||
this.regLSP = (this.segSS.load(sel) + regESP)|0;
|
||||
if (this.segSS.fExpDown) {
|
||||
this.regLSPLimit = (this.segSS.base + this.segSS.addrMask)|0;
|
||||
this.regLSPLimitLow = (this.segSS.base + this.segSS.limit)|0;
|
||||
} else {
|
||||
this.regLSPLimit = (this.segSS.base + this.segSS.limit)|0;
|
||||
this.regLSPLimitLow = this.segSS.base;
|
||||
var regLSP = this.segSS.load(sel);
|
||||
if (regLSP !== X86.ADDR_INVALID) {
|
||||
this.regLSP = (regLSP + regESP)|0;
|
||||
if (this.segSS.fExpDown) {
|
||||
this.regLSPLimit = (this.segSS.base + this.segSS.addrMask)|0;
|
||||
this.regLSPLimitLow = (this.segSS.base + this.segSS.limit)|0;
|
||||
} else {
|
||||
this.regLSPLimit = (this.segSS.base + this.segSS.limit)|0;
|
||||
this.regLSPLimitLow = this.segSS.base;
|
||||
}
|
||||
if (!BUGS_8086 && !fInterruptable) this.opFlags |= X86.OPFLAG.NOINTR;
|
||||
return true;
|
||||
}
|
||||
if (!BUGS_8086 && !fInterruptable) this.opFlags |= X86.OPFLAG.NOINTR;
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -2176,11 +2188,15 @@ X86CPU.prototype.getES = function()
|
|||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} sel
|
||||
* @return {boolean}
|
||||
*/
|
||||
X86CPU.prototype.setES = function(sel)
|
||||
{
|
||||
this.segES.load(sel);
|
||||
if (!BUGS_8086) this.opFlags |= this.OPFLAG_NOINTR_8086;
|
||||
if (this.segES.load(sel) !== X86.ADDR_INVALID) {
|
||||
if (!BUGS_8086) this.opFlags |= this.OPFLAG_NOINTR_8086;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -2203,10 +2219,11 @@ X86CPU.prototype.getFS = function()
|
|||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} sel
|
||||
* @return {boolean}
|
||||
*/
|
||||
X86CPU.prototype.setFS = function(sel)
|
||||
{
|
||||
this.segFS.load(sel);
|
||||
return this.segFS.load(sel) !== X86.ADDR_INVALID;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -2229,10 +2246,11 @@ X86CPU.prototype.getGS = function()
|
|||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} sel
|
||||
* @return {boolean}
|
||||
*/
|
||||
X86CPU.prototype.setGS = function(sel)
|
||||
{
|
||||
this.segGS.load(sel);
|
||||
return this.segGS.load(sel) !== X86.ADDR_INVALID;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1264,79 +1264,77 @@ X86.fnINT = function INT(nIDT, nError, nCycles)
|
|||
*/
|
||||
X86.fnIRET = function IRET()
|
||||
{
|
||||
/*
|
||||
* TODO: We assess a fixed cycle cost up front, because at the moment, switchTSS() doesn't assess anything.
|
||||
*/
|
||||
this.opLSP = this.regLSP;
|
||||
this.nStepCycles -= this.cycleCounts.nOpCyclesIRet;
|
||||
|
||||
if (this.regCR0 & X86.CR0.MSW.PE) {
|
||||
if (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.
|
||||
*/
|
||||
var sel = this.getShort(addrNew + X86.TSS286.PREV_TSS);
|
||||
this.segCS.switchTSS(sel, false);
|
||||
return;
|
||||
}
|
||||
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();
|
||||
|
||||
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) {
|
||||
this.assert(!!(this.regCR0 & X86.CR0.MSW.PE));
|
||||
if (I386) {
|
||||
if (this.regPS & X86.PS.VM) {
|
||||
/*
|
||||
* We have to assume that a full V86-mode interrupt frame was on the protected-mode stack; namely:
|
||||
*
|
||||
* GS
|
||||
* FS
|
||||
* DS
|
||||
* ES
|
||||
* SS
|
||||
* ESP
|
||||
* EFLAGS
|
||||
* CS
|
||||
* EIP
|
||||
*
|
||||
* 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).
|
||||
* 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.
|
||||
*/
|
||||
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);
|
||||
newPS = (newPS & (0xffff | X86.PS.RF)) | (this.regPS & ~(0xffff | X86.PS.RF));
|
||||
}
|
||||
else {
|
||||
if (newPS & X86.PS.VM) {
|
||||
this.assert(!!(this.regCR0 & X86.CR0.MSW.PE));
|
||||
/*
|
||||
* We have to assume that a full V86-mode interrupt frame was on the protected-mode stack; namely:
|
||||
*
|
||||
* GS
|
||||
* FS
|
||||
* DS
|
||||
* ES
|
||||
* SS
|
||||
* ESP
|
||||
* EFLAGS
|
||||
* CS
|
||||
* EIP
|
||||
*
|
||||
* 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 (DEBUG) this.printMessage(" returning to " + str.toHex(newCS, 4) + ':' + str.toHex(newIP, this.dataSize << 1), this.bitsMessage, true);
|
||||
// if (DEBUG) this.printMessage(" returning to " + str.toHex(newCS, 4) + ':' + str.toHex(newIP, this.dataSize << 1), this.bitsMessage, true);
|
||||
|
||||
if (this.setCSIP(newIP, newCS, false) != null) {
|
||||
this.setPS(newPS, cpl);
|
||||
if (MAXDEBUG && this.cIntReturn) this.checkIntReturn(this.regLIP);
|
||||
if (this.setCSIP(newIP, newCS, false) != null) {
|
||||
this.setPS(newPS, cpl);
|
||||
if (MAXDEBUG && this.cIntReturn) this.checkIntReturn(this.regLIP);
|
||||
}
|
||||
}
|
||||
this.opLSP = X86.ADDR_INVALID;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -2239,17 +2237,18 @@ X86.fnRCRd = function RCRd(dst, src)
|
|||
* this.pushWord(oldIP);
|
||||
* }
|
||||
*
|
||||
* That code makes opCALLF() restartable, because it doesn't modify the stack unless setCSIP() succeeds.
|
||||
* That code is inherently restartable, because it doesn't modify the stack unless setCSIP() succeeds.
|
||||
*
|
||||
* Here, our task is a little more complicated, because 1) it's not convenient to defer our stack
|
||||
* operations (it's much simpler to perform them BEFORE the setCSIP() call rather than AFTER); 2) we
|
||||
* have to deal with an additional stack adjustment value (n); and 3) if setCSIP() triggers a fault
|
||||
* (eg, NP_FAULT), fnFault() must be able to do the rewinding, which happens BEFORE setCSIP() returns.
|
||||
* operations until AFTER setCSIP(); 2) we have to deal with an additional stack adjustment value (n);
|
||||
* and 3) if setCSIP() triggers a fault (eg, NP_FAULT), fnFault() must be able to do the rewinding,
|
||||
* which happens BEFORE setCSIP() returns.
|
||||
*
|
||||
* The current hack to make the stack "rewindable" involves copying regLSP to opLSP, similar to what we do
|
||||
* for EIP (ie, by copying regLIP into opLIP prior to executing every opcode). However, I don't really want
|
||||
* to snapshot more data inside the opcode loop, so my compromise is to set opLSP only within "problematic"
|
||||
* instructions (like this one), and set it back to X86.ADDR_INVALID when we're done.
|
||||
* for EIP (ie, by copying regLIP into opLIP prior to executing every opcode), so that fnFault() can rewind
|
||||
* ESP as needed. And since I don't really want to snapshot more data inside the opcode loop, my compromise
|
||||
* is to set opLSP only within instructions (like this one) that read/write the stack, and then reset opLSP
|
||||
* back to X86.ADDR_INVALID when we're done.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} n
|
||||
|
|
@ -2289,8 +2288,8 @@ X86.fnRETF = function RETF(n)
|
|||
this.zeroSeg(this.segGS);
|
||||
}
|
||||
}
|
||||
this.opLSP = X86.ADDR_INVALID;
|
||||
if (MAXDEBUG && n == 2 && this.cIntReturn) this.checkIntReturn(this.regLIP);
|
||||
this.opLSP = X86.ADDR_INVALID;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -3773,7 +3772,7 @@ X86.fnFault = function(nFault, nError, fHalt, nCycles)
|
|||
this.nFault = nFault;
|
||||
X86.fnINT.call(this, nFault, nError, nCycles || 0);
|
||||
/*
|
||||
* REP'eated instructions that want to rewind regLIP to opLIP used to screw up this dispatch,
|
||||
* 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;
|
||||
|
|
@ -3871,7 +3870,7 @@ X86.fnFaultMessage = function(nFault, nError, fHalt)
|
|||
fHalt = false;
|
||||
}
|
||||
} else {
|
||||
if (nFault == X86.EXCEPTION.SS_FAULT || nFault == X86.EXCEPTION.PG_FAULT || nFault == X86.EXCEPTION.GP_FAULT && this.model == X86.MODEL_80386 /* || nFault == X86.EXCEPTION.NP_FAULT && bOpcode == 0x8E */) {
|
||||
if (nFault == X86.EXCEPTION.PG_FAULT || nFault == X86.EXCEPTION.GP_FAULT && this.model == X86.MODEL_80386 /* || nFault == X86.EXCEPTION.NP_FAULT && bOpcode == 0x8E */) {
|
||||
fHalt = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -3902,9 +3901,9 @@ X86.fnFaultMessage = function(nFault, nError, fHalt)
|
|||
}
|
||||
|
||||
if (this.messageEnabled(bitsMessage) || fHalt) {
|
||||
var sMessage = "Fault " + str.toHexByte(nFault) + (nError != null? " (" + str.toHexWord(nError) + ")" : "") + " on opcode " + str.toHexByte(bOpcode) + " at " + this.dbg.hexOffset(this.getIP(), this.getCS()) + " (%" + str.toHex(this.regLIP, 6) + ")";
|
||||
var sMessage = "Fault " + str.toHexByte(nFault) + (nError != null? " (" + str.toHexWord(nError) + ")" : "") + " on opcode " + str.toHexByte(bOpcode);
|
||||
var fRunning = this.aFlags.fRunning;
|
||||
if (this.printMessage(sMessage, bitsMessage)) {
|
||||
if (this.printMessage(sMessage, fHalt || bitsMessage, true)) {
|
||||
if (fHalt) {
|
||||
/*
|
||||
* By setting fHalt to fRunning (which is true while running but false while single-stepping),
|
||||
|
|
@ -3919,8 +3918,8 @@ X86.fnFaultMessage = function(nFault, nError, fHalt)
|
|||
}
|
||||
} else {
|
||||
/*
|
||||
* If printMessage() returned false, then messageEnabled() must have returned false as well, which
|
||||
* means that fHalt must be true. Which means we should shut the machine down.
|
||||
* If printMessage() returned false, then there's no Debugger, which means that messageEnabled() must have
|
||||
* returned false as well, which means that fHalt must be true. Which means we should shut the machine down.
|
||||
*/
|
||||
this.assert(fHalt);
|
||||
this.notice(sMessage);
|
||||
|
|
|
|||
|
|
@ -1113,8 +1113,10 @@ X86.opPUSHFS = function PUSHFS()
|
|||
*/
|
||||
X86.opPOPFS = function POPFS()
|
||||
{
|
||||
this.opLSP = this.regLSP;
|
||||
this.setFS(this.popWord());
|
||||
this.nStepCycles -= this.cycleCounts.nOpCyclesPopReg;
|
||||
this.opLSP = X86.ADDR_INVALID;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1178,8 +1180,10 @@ X86.opPUSHGS = function PUSHGS()
|
|||
*/
|
||||
X86.opPOPGS = function POPGS()
|
||||
{
|
||||
this.opLSP = this.regLSP;
|
||||
this.setGS(this.popWord());
|
||||
this.nStepCycles -= this.cycleCounts.nOpCyclesPopReg;
|
||||
this.opLSP = X86.ADDR_INVALID;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -137,8 +137,10 @@ X86.opPUSHES = function PUSHES()
|
|||
*/
|
||||
X86.opPOPES = function POPES()
|
||||
{
|
||||
this.opLSP = this.regLSP;
|
||||
this.setES(this.popWord());
|
||||
this.nStepCycles -= this.cycleCounts.nOpCyclesPopReg;
|
||||
this.opLSP = X86.ADDR_INVALID;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -323,8 +325,10 @@ X86.opPUSHSS = function PUSHSS()
|
|||
*/
|
||||
X86.opPOPSS = function POPSS()
|
||||
{
|
||||
this.opLSP = this.regLSP;
|
||||
this.setSS(this.popWord());
|
||||
this.nStepCycles -= this.cycleCounts.nOpCyclesPopReg;
|
||||
this.opLSP = X86.ADDR_INVALID;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -411,8 +415,10 @@ X86.opPUSHDS = function PUSHDS()
|
|||
*/
|
||||
X86.opPOPDS = function POPDS()
|
||||
{
|
||||
this.opLSP = this.regLSP;
|
||||
this.setDS(this.popWord());
|
||||
this.nStepCycles -= this.cycleCounts.nOpCyclesPopReg;
|
||||
this.opLSP = X86.ADDR_INVALID;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -99,6 +99,13 @@ function X86Seg(cpu, id, sName, fProt)
|
|||
this.checkReadV86 = this.checkReadReal;
|
||||
this.checkWriteV86 = this.checkWriteReal;
|
||||
|
||||
/*
|
||||
* Preallocated object for "probed" segment loads
|
||||
*/
|
||||
this.probe = {
|
||||
sel: 0, base: 0, limit: 0, acc: 0, type: 0, ext: 0, addrDesc: X86.ADDR_INVALID
|
||||
};
|
||||
|
||||
/*
|
||||
* The following properties are used for CODE segments only (ie, segCS); if the process of loading
|
||||
* CS also requires a stack switch, then fStackSwitch will be set to true; additionally, if the stack
|
||||
|
|
@ -140,7 +147,7 @@ X86Seg.ID = {
|
|||
* @this {X86Seg}
|
||||
* @param {number} sel
|
||||
* @param {boolean|undefined} fCall is true if CALLF in progress, false if RETF/IRET in progress, undefined otherwise
|
||||
* @return {number} base address of selected segment, or ADDR_INVALID if error
|
||||
* @return {number} base address of selected segment, or X86.ADDR_INVALID if error
|
||||
*/
|
||||
X86Seg.prototype.loadCode = function loadCode(sel, fCall)
|
||||
{
|
||||
|
|
@ -149,15 +156,16 @@ X86Seg.prototype.loadCode = function loadCode(sel, fCall)
|
|||
};
|
||||
|
||||
/**
|
||||
* loadReal(sel)
|
||||
* loadReal(sel, fProbe)
|
||||
*
|
||||
* The default segment load() function for real-mode.
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} sel
|
||||
* @return {number} base address of selected segment, or ADDR_INVALID if error (TODO: No error conditions yet)
|
||||
* @param {boolean} [fProbe] (here only to make the function signatures of loadReal() and loadProt() match)
|
||||
* @return {number} base address of selected segment
|
||||
*/
|
||||
X86Seg.prototype.loadReal = function loadReal(sel)
|
||||
X86Seg.prototype.loadReal = function loadReal(sel, fProbe)
|
||||
{
|
||||
this.sel = sel & 0xffff;
|
||||
/*
|
||||
|
|
@ -171,7 +179,7 @@ X86Seg.prototype.loadReal = function loadReal(sel)
|
|||
};
|
||||
|
||||
/**
|
||||
* loadProt(sel)
|
||||
* loadProt(sel, fProbe)
|
||||
*
|
||||
* This replaces the segment's default load() function whenever the segment is notified via updateMode() by the
|
||||
* CPU's setProtMode() that the processor is now in protected-mode.
|
||||
|
|
@ -190,9 +198,10 @@ X86Seg.prototype.loadReal = function loadReal(sel)
|
|||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} sel
|
||||
* @return {number} base address of selected segment, or ADDR_INVALID if error
|
||||
* @param {boolean} [fProbe]
|
||||
* @return {number} base address of selected segment, or X86.ADDR_INVALID if error
|
||||
*/
|
||||
X86Seg.prototype.loadProt = function loadProt(sel)
|
||||
X86Seg.prototype.loadProt = function loadProt(sel, fProbe)
|
||||
{
|
||||
var addrDT;
|
||||
var addrDTLimit;
|
||||
|
|
@ -228,7 +237,7 @@ X86Seg.prototype.loadProt = function loadProt(sel)
|
|||
* and will be much greater whenever the load fails.
|
||||
*/
|
||||
if (this.id != X86Seg.ID.DBG) cpu.nStepCycles -= 15;
|
||||
return this.loadDesc8(addrDesc, sel);
|
||||
return this.loadDesc8(addrDesc, sel, fProbe);
|
||||
}
|
||||
if (this.id < X86Seg.ID.VER) {
|
||||
X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel & X86.ERRCODE.SELMASK);
|
||||
|
|
@ -242,7 +251,7 @@ X86Seg.prototype.loadProt = function loadProt(sel)
|
|||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} nIDT
|
||||
* @return {number} address from selected vector, or ADDR_INVALID if error (TODO: No error conditions yet)
|
||||
* @return {number} address from selected vector
|
||||
*/
|
||||
X86Seg.prototype.loadIDTReal = function loadIDTReal(nIDT)
|
||||
{
|
||||
|
|
@ -271,7 +280,7 @@ X86Seg.prototype.loadIDTReal = function loadIDTReal(nIDT)
|
|||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} nIDT
|
||||
* @return {number} address from selected vector, or ADDR_INVALID if error (TODO: No error conditions yet)
|
||||
* @return {number} address from selected vector, or X86.ADDR_INVALID if error
|
||||
*/
|
||||
X86Seg.prototype.loadIDTProt = function loadIDTProt(nIDT)
|
||||
{
|
||||
|
|
@ -282,7 +291,7 @@ X86Seg.prototype.loadIDTProt = function loadIDTProt(nIDT)
|
|||
var addrDesc = (cpu.addrIDT + nIDT)|0;
|
||||
if (((cpu.addrIDTLimit - addrDesc)|0) >= 7) {
|
||||
this.fCall = true;
|
||||
return this.loadDesc8(addrDesc, nIDT, true) + cpu.regEIP;
|
||||
return this.loadDesc8(addrDesc, nIDT) + cpu.regEIP;
|
||||
}
|
||||
X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, nIDT | X86.ERRCODE.IDT, true);
|
||||
return X86.ADDR_INVALID;
|
||||
|
|
@ -297,7 +306,7 @@ X86Seg.prototype.loadIDTProt = function loadIDTProt(nIDT)
|
|||
* @this {X86Seg}
|
||||
* @param {number} off is a segment-relative offset
|
||||
* @param {number} cb is number of bytes to check (1, 2 or 4)
|
||||
* @return {number} corresponding linear address if valid, or ADDR_INVALID if error (TODO: No error conditions yet)
|
||||
* @return {number} corresponding linear address if valid, or X86.ADDR_INVALID if error (TODO: No error conditions yet)
|
||||
*/
|
||||
X86Seg.prototype.checkReadReal = function checkReadReal(off, cb)
|
||||
{
|
||||
|
|
@ -313,7 +322,7 @@ X86Seg.prototype.checkReadReal = function checkReadReal(off, cb)
|
|||
* @this {X86Seg}
|
||||
* @param {number} off is a segment-relative offset
|
||||
* @param {number} cb is number of bytes to check (1, 2 or 4)
|
||||
* @return {number} corresponding linear address if valid, or ADDR_INVALID if error (TODO: No error conditions yet)
|
||||
* @return {number} corresponding linear address if valid, or X86.ADDR_INVALID if error (TODO: No error conditions yet)
|
||||
*/
|
||||
X86Seg.prototype.checkWriteReal = function checkWriteReal(off, cb)
|
||||
{
|
||||
|
|
@ -326,7 +335,7 @@ X86Seg.prototype.checkWriteReal = function checkWriteReal(off, cb)
|
|||
* @this {X86Seg}
|
||||
* @param {number} off is a segment-relative offset
|
||||
* @param {number} cb is number of bytes to check (1, 2 or 4)
|
||||
* @return {number} corresponding linear address if valid, or ADDR_INVALID if not
|
||||
* @return {number} corresponding linear address if valid, or X86.ADDR_INVALID if not
|
||||
*/
|
||||
X86Seg.prototype.checkReadProt = function checkReadProt(off, cb)
|
||||
{
|
||||
|
|
@ -346,7 +355,7 @@ X86Seg.prototype.checkReadProt = function checkReadProt(off, cb)
|
|||
* @this {X86Seg}
|
||||
* @param {number} off is a segment-relative offset
|
||||
* @param {number} cb is number of bytes to check (1, 2 or 4)
|
||||
* @return {number} corresponding linear address if valid, ADDR_INVALID if not
|
||||
* @return {number} corresponding linear address if valid, X86.ADDR_INVALID if not
|
||||
*/
|
||||
X86Seg.prototype.checkReadProtDown = function checkReadProtDown(off, cb)
|
||||
{
|
||||
|
|
@ -366,7 +375,7 @@ X86Seg.prototype.checkReadProtDown = function checkReadProtDown(off, cb)
|
|||
* @this {X86Seg}
|
||||
* @param {number} off is a segment-relative offset
|
||||
* @param {number} cb is number of bytes to check (1, 2 or 4)
|
||||
* @return {number} corresponding linear address if valid, ADDR_INVALID if not
|
||||
* @return {number} corresponding linear address if valid, X86.ADDR_INVALID if not
|
||||
*/
|
||||
X86Seg.prototype.checkReadProtDisallowed = function checkReadProtDisallowed(off, cb)
|
||||
{
|
||||
|
|
@ -380,7 +389,7 @@ X86Seg.prototype.checkReadProtDisallowed = function checkReadProtDisallowed(off,
|
|||
* @this {X86Seg}
|
||||
* @param {number} off is a segment-relative offset
|
||||
* @param {number} cb is number of bytes to check (1, 2 or 4)
|
||||
* @return {number} corresponding linear address if valid, ADDR_INVALID if not
|
||||
* @return {number} corresponding linear address if valid, X86.ADDR_INVALID if not
|
||||
*/
|
||||
X86Seg.prototype.checkWriteProt = function checkWriteProt(off, cb)
|
||||
{
|
||||
|
|
@ -400,7 +409,7 @@ X86Seg.prototype.checkWriteProt = function checkWriteProt(off, cb)
|
|||
* @this {X86Seg}
|
||||
* @param {number} off is a segment-relative offset
|
||||
* @param {number} cb is number of bytes to check (1, 2 or 4)
|
||||
* @return {number} corresponding linear address if valid, ADDR_INVALID if not
|
||||
* @return {number} corresponding linear address if valid, X86.ADDR_INVALID if not
|
||||
*/
|
||||
X86Seg.prototype.checkWriteProtDown = function checkWriteProtDown(off, cb)
|
||||
{
|
||||
|
|
@ -420,7 +429,7 @@ X86Seg.prototype.checkWriteProtDown = function checkWriteProtDown(off, cb)
|
|||
* @this {X86Seg}
|
||||
* @param {number} off is a segment-relative offset
|
||||
* @param {number} cb is number of bytes to check (1, 2 or 4)
|
||||
* @return {number} corresponding linear address if valid, ADDR_INVALID if not
|
||||
* @return {number} corresponding linear address if valid, X86.ADDR_INVALID if not
|
||||
*/
|
||||
X86Seg.prototype.checkWriteProtDisallowed = function checkWriteProtDisallowed(off, cb)
|
||||
{
|
||||
|
|
@ -434,7 +443,7 @@ X86Seg.prototype.checkWriteProtDisallowed = function checkWriteProtDisallowed(of
|
|||
* @this {X86Seg}
|
||||
* @param {number} off is a segment-relative offset
|
||||
* @param {number} cb is number of bytes to check (1, 2 or 4)
|
||||
* @return {number} corresponding linear address if valid, or ADDR_INVALID if error
|
||||
* @return {number} corresponding linear address if valid, or X86.ADDR_INVALID if error
|
||||
*/
|
||||
X86Seg.prototype.checkReadDebugger = function checkReadDebugger(off, cb)
|
||||
{
|
||||
|
|
@ -456,7 +465,7 @@ X86Seg.prototype.checkReadDebugger = function checkReadDebugger(off, cb)
|
|||
* @this {X86Seg}
|
||||
* @param {number} off is a segment-relative offset
|
||||
* @param {number} cb is number of bytes to check (1, 2 or 4)
|
||||
* @return {number} corresponding linear address if valid, or ADDR_INVALID if error
|
||||
* @return {number} corresponding linear address if valid, or X86.ADDR_INVALID if error
|
||||
*/
|
||||
X86Seg.prototype.checkWriteDebugger = function checkWriteDebugger(off, cb)
|
||||
{
|
||||
|
|
@ -540,7 +549,7 @@ X86Seg.prototype.loadDesc6 = function(addrDesc, sel)
|
|||
};
|
||||
|
||||
/**
|
||||
* loadDesc8(addrDesc, sel, fIDT)
|
||||
* loadDesc8(addrDesc, sel, fProbe)
|
||||
*
|
||||
* Used to load a protected-mode selector that refers to an 8-byte "descriptor table" (GDT, LDT, IDT) entry:
|
||||
*
|
||||
|
|
@ -551,15 +560,47 @@ X86Seg.prototype.loadDesc6 = function(addrDesc, sel)
|
|||
*
|
||||
* See X86.DESC for offset and bit definitions.
|
||||
*
|
||||
* When fProbe is set, this function will not modify the X86Seg object; it will still generate a fault if any of
|
||||
* the usual error conditions are detected (and return X86.ADDR_INVALID), but in the success case, it merely stashes
|
||||
* all descriptor values it reads in the X86Seg's "probe" object. If the caller ultimately decides to propagate
|
||||
* those "probed" values to the X86Seg object, it must then call loadProbe().
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} addrDesc is the descriptor address
|
||||
* @param {number} sel is the associated selector, or nIDT*8 if IDT descriptor
|
||||
* @param {boolean} [fIDT] is true if sel refers to the IDT (only affects error handling)
|
||||
* @return {number} base address of selected segment, or ADDR_INVALID if error
|
||||
* @param {boolean} [fProbe] (true if this is a probe)
|
||||
* @return {number} base address of selected segment, or X86.ADDR_INVALID if error
|
||||
*/
|
||||
X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fIDT)
|
||||
X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe)
|
||||
{
|
||||
var cpu = this.cpu;
|
||||
|
||||
/*
|
||||
* If the previous load was a successful "probed" load of the same segment, then we simply load
|
||||
* up all the cached descriptor values from that probe and return.
|
||||
*/
|
||||
if (!fProbe && sel === this.probe.sel) {
|
||||
this.sel = sel;
|
||||
this.base = this.probe.base;
|
||||
this.limit = this.probe.limit;
|
||||
this.offMax = (this.probe.limit >>> 0) + 1;
|
||||
this.acc = this.probe.acc;
|
||||
this.type = this.probe.type;
|
||||
this.ext = this.probe.ext;
|
||||
this.addrDesc = this.probe.addrDesc;
|
||||
this.probe.sel = 0;
|
||||
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.
|
||||
*/
|
||||
this.probe.sel = 0;
|
||||
|
||||
/*
|
||||
* Load the descriptor from memory.
|
||||
*/
|
||||
var limit = cpu.getShort(addrDesc + X86.DESC.LIMIT.OFFSET);
|
||||
var acc = cpu.getShort(addrDesc + X86.DESC.ACC.OFFSET);
|
||||
var type = (acc & X86.DESC.ACC.TYPE.MASK);
|
||||
|
|
@ -574,257 +615,291 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fIDT)
|
|||
if (ext & X86.DESC.EXT.LIMITPAGES) limit = (limit << 12) | 0xfff;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
switch (this.id) {
|
||||
|
||||
var fGate, selCode, cplPrev, addrTSS, offSP, offSS, regSPPrev, regSSPrev;
|
||||
case X86Seg.ID.CODE:
|
||||
|
||||
this.fStackSwitch = false;
|
||||
var fCall = this.fCall;
|
||||
var rpl = sel & X86.SEL.RPL;
|
||||
var dpl = (acc & X86.DESC.ACC.DPL.MASK) >> X86.DESC.ACC.DPL.SHIFT;
|
||||
|
||||
var fGate, selCode, cplOld, addrTSS, offSP, lenSP, regSPPrev, regSSPrev, regPSClear, regSP;
|
||||
|
||||
if (selMasked && !(acc & X86.DESC.ACC.PRESENT)) {
|
||||
if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.NP_FAULT, sel & X86.ERRCODE.SELMASK);
|
||||
return X86.ADDR_INVALID;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: Consider moving the following chunks of code into worker functions for each X86Seg.ID;
|
||||
* however, it's not clear that these tests are more costly than making additional function calls.
|
||||
* Since we are X86Seg.ID.CODE, we can use this.cpl instead of the more generic cpu.segCS.cpl
|
||||
*/
|
||||
if (this.id == X86Seg.ID.CODE) {
|
||||
this.fStackSwitch = false;
|
||||
var fCall = this.fCall;
|
||||
var regPSClear, regSP;
|
||||
var rpl = sel & X86.SEL.RPL;
|
||||
var dpl = (acc & X86.DESC.ACC.DPL.MASK) >> X86.DESC.ACC.DPL.SHIFT;
|
||||
|
||||
if (selMasked && !(acc & X86.DESC.ACC.PRESENT)) {
|
||||
if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.NP_FAULT, sel & X86.ERRCODE.SELMASK);
|
||||
base = addrDesc = X86.ADDR_INVALID;
|
||||
break;
|
||||
if (type >= X86.DESC.ACC.TYPE.CODE_EXECONLY) {
|
||||
rpl = sel & X86.SEL.RPL;
|
||||
if (rpl > this.cpl) {
|
||||
/*
|
||||
* If fCall is false, then we must have a RETF to a less privileged segment, which is OK.
|
||||
*
|
||||
* Otherwise, we must be dealing with a CALLF or JMPF to a less privileged segment, in which
|
||||
* case either DPL == CPL *or* the new segment is conforming and DPL <= CPL.
|
||||
*/
|
||||
if (fCall !== false && !(dpl == this.cpl || (type & X86.DESC.ACC.TYPE.CONFORMING) && dpl <= this.cpl)) {
|
||||
return X86.ADDR_INVALID;
|
||||
}
|
||||
/*
|
||||
* It's critical that any stack switch occur with the operand size in effect at the time of
|
||||
* the current instruction, BEFORE any calls to updateMode() and resetSizes(), otherwise the
|
||||
* operand size (or operand override) in effect on an instruction like IRETD will be ignored.
|
||||
*/
|
||||
regSP = cpu.popWord();
|
||||
cpu.setSS(cpu.popWord(), true);
|
||||
cpu.setSP(regSP);
|
||||
this.fStackSwitch = true;
|
||||
}
|
||||
fGate = false;
|
||||
}
|
||||
else if (type == X86.DESC.ACC.TYPE.TSS286 || type == X86.DESC.ACC.TYPE.TSS386) {
|
||||
if (!this.switchTSS(sel, fCall)) {
|
||||
return X86.ADDR_INVALID;
|
||||
}
|
||||
return this.base;
|
||||
}
|
||||
else if (type == X86.DESC.ACC.TYPE.GATE_CALL || type == X86.DESC.ACC.TYPE.GATE386_CALL) {
|
||||
fGate = true;
|
||||
regPSClear = 0;
|
||||
if (rpl < this.cpl) rpl = this.cpl; // set RPL to max(RPL,CPL) for call gates
|
||||
}
|
||||
else if (type == X86.DESC.ACC.TYPE.GATE286_INT || type == X86.DESC.ACC.TYPE.GATE386_INT) {
|
||||
fGate = true;
|
||||
regPSClear = (X86.PS.NT | X86.PS.TF | X86.PS.IF);
|
||||
cpu.assert(!(acc & 0x1f));
|
||||
}
|
||||
else if (type == X86.DESC.ACC.TYPE.GATE286_TRAP || type == X86.DESC.ACC.TYPE.GATE386_TRAP) {
|
||||
fGate = true;
|
||||
regPSClear = (X86.PS.NT | X86.PS.TF);
|
||||
cpu.assert(!(acc & 0x1f));
|
||||
}
|
||||
else if (type == X86.DESC.ACC.TYPE.GATE_TASK) {
|
||||
if (!this.switchTSS(base & 0xffff, fCall)) {
|
||||
return X86.ADDR_INVALID;
|
||||
}
|
||||
return this.base;
|
||||
}
|
||||
|
||||
if (fGate) {
|
||||
/*
|
||||
* Since we are X86Seg.ID.CODE, we can use this.cpl instead of the more generic cpu.segCS.cpl
|
||||
* Note that since GATE_INT/GATE_TRAP descriptors should appear in the IDT only, that means sel
|
||||
* will actually be nIDT * 8, which means the rpl will always be zero; additionally, the nWords
|
||||
* portion of ACC should always be zero, but that's really dependent on the descriptor being properly
|
||||
* set (which we assert above).
|
||||
*/
|
||||
if (type >= X86.DESC.ACC.TYPE.CODE_EXECONLY) {
|
||||
rpl = sel & X86.SEL.RPL;
|
||||
if (rpl > this.cpl) {
|
||||
if (rpl <= dpl) {
|
||||
/*
|
||||
* TODO: Verify the PRESENT bit of the gate descriptor, and issue NP_FAULT as appropriate.
|
||||
*/
|
||||
cplOld = this.cpl;
|
||||
|
||||
/*
|
||||
* For gates, there is no "base" and "limit", but rather "selector" and "offset"; the selector
|
||||
* is located where the first 16 bits of base are normally stored, and the offset comes from the
|
||||
* original limit and ext fields.
|
||||
*/
|
||||
selCode = base & 0xffff;
|
||||
if (I386 && (type & X86.DESC.ACC.NONSEG_386)) {
|
||||
limit = limitOrig | (ext << 16);
|
||||
}
|
||||
|
||||
/*
|
||||
* At a minimum, we need to clear X86.PS.VM now, so that the following load will initialize the
|
||||
* new code segment properly.
|
||||
*/
|
||||
var regPS = cpu.regPS;
|
||||
cpu.regPS &= ~regPSClear;
|
||||
if (cpu.regPS & X86.PS.VM) {
|
||||
/*
|
||||
* If fCall is false, then we must have a RETF to a less privileged segment, which is OK.
|
||||
*
|
||||
* Otherwise, we must be dealing with a CALLF or JMPF to a less privileged segment, in which
|
||||
* case either DPL == CPL *or* the new segment is conforming and DPL <= CPL.
|
||||
* TODO: This seems a bit suspect in retrospect (ie, altering CPU flags before we know whether
|
||||
* the load() will succeed or generate a fault). Take another look.
|
||||
*/
|
||||
if (fCall !== false && !(dpl == this.cpl || (type & X86.DESC.ACC.TYPE.CONFORMING) && dpl <= this.cpl)) {
|
||||
base = addrDesc = X86.ADDR_INVALID;
|
||||
break;
|
||||
cpu.assert(false);
|
||||
cpu.regPS &= ~X86.PS.VM;
|
||||
cpu.setProtMode(true, false);
|
||||
}
|
||||
|
||||
var cplNew = (selCode & X86.SEL.RPL), selStack = 0, offStack = 0;
|
||||
|
||||
/*
|
||||
* If a stack switch is required, we must perform "probed" loads of both the new selCode
|
||||
* and selStack segments, so that if either probe fails, a fault will be generated while the
|
||||
* old code segment is still loaded.
|
||||
*/
|
||||
if (cplNew < cplOld) {
|
||||
if (this.loadProt(selCode, true) === X86.ADDR_INVALID) {
|
||||
return X86.ADDR_INVALID;
|
||||
}
|
||||
addrTSS = cpu.segTSS.base;
|
||||
if (!I386 || !(type & X86.DESC.ACC.NONSEG_386)) {
|
||||
offSP = (cplNew << 2) + X86.TSS286.CPL0_SP;
|
||||
lenSP = 2;
|
||||
cpu.assert(!(regPS & X86.PS.VM));
|
||||
} else {
|
||||
offSP = (cplNew << 2) + X86.TSS386.CPL0_ESP;
|
||||
lenSP = 4;
|
||||
}
|
||||
selStack = cpu.getShort(addrTSS + offSP + lenSP);
|
||||
if (cpu.segSS.loadProt(selStack, true) === X86.ADDR_INVALID) {
|
||||
return X86.ADDR_INVALID;
|
||||
}
|
||||
/*
|
||||
* It's critical that any stack switch occur with the operand size in effect at the time of
|
||||
* the current instruction, BEFORE any calls to updateMode() and resetSizes(), otherwise the
|
||||
* operand size (or operand override) in effect on an instruction like IRETD will be ignored.
|
||||
* Both probes succeeded, so we can proceed with "normal" loads for both selCode and
|
||||
* selStack (which should automatically use the values cached by the "probed" loads above).
|
||||
*/
|
||||
regSP = cpu.popWord();
|
||||
cpu.setSS(cpu.popWord(), true);
|
||||
cpu.setSP(regSP);
|
||||
offStack = (lenSP == 2)? cpu.getShort(addrTSS + offSP) : cpu.getLong(addrTSS + offSP);
|
||||
}
|
||||
|
||||
if (this.loadProt(selCode) === X86.ADDR_INVALID) {
|
||||
return X86.ADDR_INVALID;
|
||||
}
|
||||
|
||||
cpu.regEIP = limit;
|
||||
cpu.assert(this.cpl == cplNew);
|
||||
|
||||
if (this.cpl < cplOld) {
|
||||
if (fCall !== true) {
|
||||
cpu.assert(false);
|
||||
return X86.ADDR_INVALID;
|
||||
}
|
||||
|
||||
regSP = cpu.getSP();
|
||||
var i = 0, nWords = (acc & 0x1f);
|
||||
while (nWords--) {
|
||||
this.awParms[i++] = cpu.getSOWord(cpu.segSS, regSP);
|
||||
regSP += 2;
|
||||
}
|
||||
|
||||
regSSPrev = cpu.getSS();
|
||||
regSPPrev = cpu.getSP();
|
||||
|
||||
cpu.setSS(selStack, true);
|
||||
cpu.setSP(offStack);
|
||||
|
||||
/*
|
||||
* This call to resetSizes() used to appear before the parameter copying above, but
|
||||
* anything that was pushed on the old stack would have been pushed with the old sizes.
|
||||
*/
|
||||
cpu.resetSizes();
|
||||
|
||||
if (regPS & X86.PS.VM) {
|
||||
cpu.assert(I386 && cpu.model >= X86.MODEL_80386);
|
||||
cpu.pushWord(cpu.segGS.sel);
|
||||
cpu.setGS(0);
|
||||
cpu.pushWord(cpu.segFS.sel);
|
||||
cpu.setFS(0);
|
||||
cpu.pushWord(cpu.segDS.sel);
|
||||
cpu.setDS(0);
|
||||
cpu.pushWord(cpu.segES.sel);
|
||||
cpu.setES(0);
|
||||
}
|
||||
cpu.pushWord(regSSPrev);
|
||||
cpu.pushWord(regSPPrev);
|
||||
while (i) cpu.pushWord(this.awParms[--i]);
|
||||
this.fStackSwitch = true;
|
||||
}
|
||||
fGate = false;
|
||||
}
|
||||
else if (type == X86.DESC.ACC.TYPE.TSS286 || type == X86.DESC.ACC.TYPE.TSS386) {
|
||||
if (!this.switchTSS(sel, fCall)) {
|
||||
base = addrDesc = X86.ADDR_INVALID;
|
||||
break;
|
||||
}
|
||||
return this.base;
|
||||
}
|
||||
else if (type == X86.DESC.ACC.TYPE.GATE_CALL || type == X86.DESC.ACC.TYPE.GATE386_CALL) {
|
||||
fGate = true;
|
||||
regPSClear = 0;
|
||||
if (rpl < this.cpl) rpl = this.cpl; // set RPL to max(RPL,CPL) for call gates
|
||||
}
|
||||
else if (type == X86.DESC.ACC.TYPE.GATE286_INT || type == X86.DESC.ACC.TYPE.GATE386_INT) {
|
||||
fGate = true;
|
||||
regPSClear = (X86.PS.NT | X86.PS.TF | X86.PS.IF);
|
||||
cpu.assert(!(acc & 0x1f));
|
||||
}
|
||||
else if (type == X86.DESC.ACC.TYPE.GATE286_TRAP || type == X86.DESC.ACC.TYPE.GATE386_TRAP) {
|
||||
fGate = true;
|
||||
regPSClear = (X86.PS.NT | X86.PS.TF);
|
||||
cpu.assert(!(acc & 0x1f));
|
||||
}
|
||||
else if (type == X86.DESC.ACC.TYPE.GATE_TASK) {
|
||||
if (!this.switchTSS(base & 0xffff, fCall)) {
|
||||
base = addrDesc = X86.ADDR_INVALID;
|
||||
break;
|
||||
}
|
||||
return this.base;
|
||||
}
|
||||
if (fGate) {
|
||||
/*
|
||||
* Note that since GATE_INT/GATE_TRAP descriptors should appear in the IDT only, that means sel
|
||||
* will actually be nIDT * 8, which means the rpl will always be zero; additionally, the nWords
|
||||
* portion of ACC should always be zero, but that's really dependent on the descriptor being properly
|
||||
* set (which we assert above).
|
||||
*/
|
||||
if (rpl <= dpl) {
|
||||
/*
|
||||
* TODO: Verify the PRESENT bit of the gate descriptor, and issue NP_FAULT as appropriate.
|
||||
*/
|
||||
cplPrev = this.cpl;
|
||||
|
||||
/*
|
||||
* For gates, there is no "base" and "limit", but rather "selector" and "offset"; the selector
|
||||
* is located where the first 16 bits of base are normally stored, and the offset comes from the
|
||||
* original limit and ext fields.
|
||||
*/
|
||||
selCode = base & 0xffff;
|
||||
if (I386 && (type & X86.DESC.ACC.NONSEG_386)) {
|
||||
limit = limitOrig | (ext << 16);
|
||||
}
|
||||
|
||||
/*
|
||||
* At a minimum, we need to clear X86.PS.VM now, so that the following load will initialize the
|
||||
* new code segment properly.
|
||||
*/
|
||||
var regPS = cpu.regPS;
|
||||
cpu.regPS &= ~regPSClear;
|
||||
if (cpu.regPS & X86.PS.VM) {
|
||||
cpu.regPS &= ~X86.PS.VM;
|
||||
cpu.setProtMode(true, false);
|
||||
}
|
||||
|
||||
if (this.load(selCode, true) === X86.ADDR_INVALID) {
|
||||
cpu.assert(false);
|
||||
base = addrDesc = X86.ADDR_INVALID;
|
||||
break;
|
||||
}
|
||||
|
||||
cpu.regEIP = limit;
|
||||
if (this.cpl < cplPrev) {
|
||||
if (fCall !== true) {
|
||||
cpu.assert(false);
|
||||
base = addrDesc = X86.ADDR_INVALID;
|
||||
break;
|
||||
}
|
||||
cpu.resetSizes();
|
||||
regSP = cpu.getSP();
|
||||
var i = 0, nWords = (acc & 0x1f);
|
||||
while (nWords--) {
|
||||
this.awParms[i++] = cpu.getSOWord(cpu.segSS, regSP);
|
||||
regSP += 2;
|
||||
}
|
||||
addrTSS = cpu.segTSS.base;
|
||||
regSSPrev = cpu.getSS();
|
||||
regSPPrev = cpu.getSP();
|
||||
if (!I386 || !(type & X86.DESC.ACC.NONSEG_386)) {
|
||||
offSP = (this.cpl << 2) + X86.TSS286.CPL0_SP;
|
||||
offSS = offSP + 2;
|
||||
cpu.setSS(cpu.getShort(addrTSS + offSS), true);
|
||||
cpu.setSP(cpu.getShort(addrTSS + offSP));
|
||||
} else {
|
||||
offSP = (this.cpl << 2) + X86.TSS386.CPL0_ESP;
|
||||
offSS = offSP + 4;
|
||||
cpu.setSS(cpu.getShort(addrTSS + offSS), true);
|
||||
cpu.setSP(cpu.getLong(addrTSS + offSP));
|
||||
if (regPS & X86.PS.VM) {
|
||||
/*
|
||||
* segFS amd segGS exist only on 80386 machines
|
||||
*/
|
||||
cpu.assert(I386 && cpu.model >= X86.MODEL_80386);
|
||||
cpu.pushWord(cpu.segGS.sel);
|
||||
cpu.setGS(0);
|
||||
cpu.pushWord(cpu.segFS.sel);
|
||||
cpu.setFS(0);
|
||||
cpu.pushWord(cpu.segDS.sel);
|
||||
cpu.setDS(0);
|
||||
cpu.pushWord(cpu.segES.sel);
|
||||
cpu.setES(0);
|
||||
}
|
||||
}
|
||||
cpu.pushWord(regSSPrev);
|
||||
cpu.pushWord(regSPPrev);
|
||||
while (i) cpu.pushWord(this.awParms[--i]);
|
||||
this.fStackSwitch = true;
|
||||
}
|
||||
return this.base;
|
||||
}
|
||||
}
|
||||
if (fGate !== false) {
|
||||
cpu.assert(false);
|
||||
if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, (sel & X86.ERRCODE.SELMASK) | (fIDT? X86.ERRCODE.IDT : 0), true);
|
||||
base = addrDesc = X86.ADDR_INVALID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (this.id == X86Seg.ID.DATA) {
|
||||
if (selMasked) {
|
||||
if (!(acc & X86.DESC.ACC.PRESENT)) {
|
||||
if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.NP_FAULT, sel & X86.ERRCODE.SELMASK);
|
||||
base = addrDesc = X86.ADDR_INVALID;
|
||||
break;
|
||||
}
|
||||
if (type < X86.DESC.ACC.TYPE.SEG || (type & (X86.DESC.ACC.TYPE.CODE | X86.DESC.ACC.TYPE.READABLE)) == X86.DESC.ACC.TYPE.CODE) {
|
||||
/*
|
||||
* OS/2 1.0 triggers this "Empty Descriptor" GP_FAULT multiple times during boot; for example:
|
||||
*
|
||||
* Fault 0D (002F) on opcode 0x8E at 3190:3A05 (%112625)
|
||||
* stopped (11315208 ops, 41813627 cycles, 498270 ms, 83918 hz)
|
||||
* AX=0000 BX=0970 CX=0300 DX=0300 SP=0ABE BP=0ABA SI=0000 DI=001A
|
||||
* DS=19C0[177300,2C5F] ES=001F[1743A0,07FF] SS=0038[175CE0,0B5F]
|
||||
* CS=3190[10EC20,B89F] IP=3A05 V0 D0 I1 T0 S0 Z1 A0 P1 C0 PS=3246 MS=FFF3
|
||||
* LD=0028[174BC0,003F] GD=[11A4E0,490F] ID=[11F61A,03FF] TR=0010 A20=ON
|
||||
* 3190:3A05 8E4604 MOV ES,[BP+04]
|
||||
* 0038:0ABE 002F 19C0 0000 067C - 07FC 0AD2 0010 C420
|
||||
* dumpDesc(002F): %174BE8
|
||||
* base=000000 limit=0000 dpl=00 type=00 (undefined)
|
||||
*
|
||||
* If we allow the GP fault to be dispatched, it recovers, so until I'm able to investigate this
|
||||
* further, I'm going to assume this is normal behavior. If the segment (0x002F in the example)
|
||||
* simply needed to be "faulted" into memory, I would have expected OS/2 to build a descriptor
|
||||
* with the PRESENT bit clear, and rely on NP_FAULT rather than GP_FAULT, but maybe this was simpler.
|
||||
*
|
||||
* So, if the ACC field is zero, we won't set the last fnFault() parameter (fHalt) to true.
|
||||
*/
|
||||
if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel & X86.ERRCODE.SELMASK, !!acc);
|
||||
base = addrDesc = X86.ADDR_INVALID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fGate !== false) {
|
||||
var nError = sel & X86.ERRCODE.SELMASK;
|
||||
if (addrDesc >= cpu.addrIDT && addrDesc < cpu.addrIDTLimit) nError |= X86.ERRCODE.IDT;
|
||||
if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, nError, true);
|
||||
return X86.ADDR_INVALID;
|
||||
}
|
||||
else if (this.id == X86Seg.ID.STACK) {
|
||||
break;
|
||||
|
||||
case X86Seg.ID.DATA:
|
||||
if (selMasked) {
|
||||
if (!(acc & X86.DESC.ACC.PRESENT)) {
|
||||
if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.SS_FAULT, sel & X86.ERRCODE.SELMASK);
|
||||
base = addrDesc = X86.ADDR_INVALID;
|
||||
break;
|
||||
/*
|
||||
* OS/2 1.0 faults on segments with "empty descriptors" multiple times during boot; for example:
|
||||
*
|
||||
* Fault 0x0B (0x002C) on opcode 0x8E at 3190:3A05 (%112625)
|
||||
* AX=0000 BX=0970 CX=0300 DX=0300 SP=0ABE BP=0ABA SI=0000 DI=001A
|
||||
* SS=0038[175CE0,0B5F] DS=19C0[177300,2C5F] ES=001F[1743A0,07FF] A20=ON
|
||||
* CS=3190[10EC20,B89F] LD=0028[174BC0,003F] GD=[11A4E0,490F] ID=[11F61A,03FF]
|
||||
* TR=0010 MS=0000FFF3 PS=3256 V0 D0 I1 T0 S0 Z1 A1 P1 C0
|
||||
* 3190:3A05 8E4604 MOV ES,[BP+04]
|
||||
* ## dw ss:bp+4 l1
|
||||
* 0038:0ABE 002F 19C0 0000 067C 07FC 0AD2 0010 C420 /.....|....... .
|
||||
* ## ds 2f
|
||||
* dumpDesc(0x002F): %174BE8
|
||||
* base=000000 limit=0000 type=0x00 (undefined) ext=0x0000 dpl=0x00
|
||||
*
|
||||
* Before I added the X86.DESC.ACC.PRESENT check, I used to (incorrectly) dispatch this as a GP_FAULT,
|
||||
* but OS/2 still appeared to handle the fault OK. However, this condition is now properly handled as
|
||||
* an NP_FAULT.
|
||||
*/
|
||||
if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.NP_FAULT, sel & X86.ERRCODE.SELMASK);
|
||||
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) {
|
||||
if (type < X86.DESC.ACC.TYPE.SEG || (type & (X86.DESC.ACC.TYPE.CODE | X86.DESC.ACC.TYPE.READABLE)) == X86.DESC.ACC.TYPE.CODE) {
|
||||
if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel & X86.ERRCODE.SELMASK, true);
|
||||
base = addrDesc = X86.ADDR_INVALID;
|
||||
break;
|
||||
return X86.ADDR_INVALID;
|
||||
}
|
||||
}
|
||||
else if (this.id == 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) {
|
||||
if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel & X86.ERRCODE.SELMASK, true);
|
||||
base = addrDesc = X86.ADDR_INVALID;
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* For more efficient IOPM lookups, we cache the starting linear address in segTSS.addrIOPM, and the
|
||||
* last valid address in segTSS.addrIOPMLimit.
|
||||
*/
|
||||
if (typeTSS == X86.DESC.ACC.TYPE.TSS386) {
|
||||
this.addrIOPM = (base + cpu.getShort(base + X86.TSS386.TASK_IOPM + 2))|0;
|
||||
this.addrIOPMLimit = (base + this.limit)|0;
|
||||
}
|
||||
break;
|
||||
|
||||
case X86Seg.ID.STACK:
|
||||
if (!(acc & X86.DESC.ACC.PRESENT)) {
|
||||
if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.SS_FAULT, sel & X86.ERRCODE.SELMASK);
|
||||
return X86.ADDR_INVALID;
|
||||
}
|
||||
else if (this.id == X86Seg.ID.VER) {
|
||||
/*
|
||||
* For LSL, we must support any descriptor marked X86.DESC.ACC.TYPE.SEG, as well as TSS and LDT descriptors.
|
||||
*/
|
||||
if (!(type & X86.DESC.ACC.TYPE.SEG) && type > X86.DESC.ACC.TYPE.TSS286_BUSY && type != X86.DESC.ACC.TYPE.TSS386 && type != X86.DESC.ACC.TYPE.TSS386_BUSY) {
|
||||
base = addrDesc = X86.ADDR_INVALID;
|
||||
break;
|
||||
}
|
||||
if (!selMasked || type < X86.DESC.ACC.TYPE.SEG || (type & (X86.DESC.ACC.TYPE.CODE | X86.DESC.ACC.TYPE.WRITABLE)) != X86.DESC.ACC.TYPE.WRITABLE) {
|
||||
if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel & X86.ERRCODE.SELMASK, true);
|
||||
return X86.ADDR_INVALID;
|
||||
}
|
||||
break;
|
||||
|
||||
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) {
|
||||
if (this.id < X86Seg.ID.VER) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel & X86.ERRCODE.SELMASK, true);
|
||||
return X86.ADDR_INVALID;
|
||||
}
|
||||
/*
|
||||
* For more efficient IOPM lookups, we cache the starting linear address in segTSS.addrIOPM, and the
|
||||
* last valid address in segTSS.addrIOPMLimit.
|
||||
*/
|
||||
if (typeTSS == X86.DESC.ACC.TYPE.TSS386) {
|
||||
this.addrIOPM = (base + cpu.getShort(base + X86.TSS386.TASK_IOPM + 2))|0;
|
||||
this.addrIOPMLimit = (base + this.limit)|0;
|
||||
}
|
||||
break;
|
||||
|
||||
case X86Seg.ID.VER:
|
||||
/*
|
||||
* For LSL, we must support any descriptor marked X86.DESC.ACC.TYPE.SEG, as well as TSS and LDT descriptors.
|
||||
*/
|
||||
if (!(type & X86.DESC.ACC.TYPE.SEG) && type > X86.DESC.ACC.TYPE.TSS286_BUSY && type != X86.DESC.ACC.TYPE.TSS386 && type != X86.DESC.ACC.TYPE.TSS386_BUSY) {
|
||||
return X86.ADDR_INVALID;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
/*
|
||||
* The only other case should be X86Seg.ID.DBG, for which we do nothing.
|
||||
*/
|
||||
break;
|
||||
}
|
||||
|
||||
if (fProbe) {
|
||||
this.probe.sel = sel;
|
||||
this.probe.base = base;
|
||||
this.probe.limit = limit;
|
||||
this.probe.acc = acc;
|
||||
this.probe.type = type;
|
||||
this.probe.ext = ext;
|
||||
this.probe.addrDesc = addrDesc;
|
||||
} else {
|
||||
this.sel = sel;
|
||||
this.base = base;
|
||||
this.limit = limit;
|
||||
|
|
@ -833,8 +908,22 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fIDT)
|
|||
this.type = type;
|
||||
this.ext = ext;
|
||||
this.addrDesc = addrDesc;
|
||||
/*
|
||||
* A quick recap of what updateMode(fLoad=true, fProt=true, fV86=false) actually updates next:
|
||||
*
|
||||
* cpl
|
||||
* dpl
|
||||
* dataSize
|
||||
* dataMask
|
||||
* addrSize
|
||||
* addrMask
|
||||
* fExpDown
|
||||
* load()
|
||||
* loadIDT()
|
||||
* checkRead()
|
||||
* checkWrite()
|
||||
*/
|
||||
this.updateMode(true, true, false);
|
||||
break;
|
||||
}
|
||||
|
||||
if (DEBUG) this.messageSeg(sel, base, limit, type, ext);
|
||||
|
|
@ -861,8 +950,8 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fIDT)
|
|||
* Of course, that all could have been avoided if IBM had heeded Intel's advice and not used Intel-reserved IDT
|
||||
* entries for PC interrupts.
|
||||
*
|
||||
* TODO: Add TSS validity checks and appropriate generation of TS_FAULT exceptions; note that the only rudimentary
|
||||
* checks we currently perform are of the GP_FAULT variety.
|
||||
* TODO: Add TSS validity checks and appropriate generation of TS_FAULT exceptions; the only rudimentary checks
|
||||
* we currently perform are of the GP_FAULT variety.
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} selNew
|
||||
|
|
@ -979,7 +1068,7 @@ X86Seg.prototype.switchTSS = function switchTSS(selNew, fNest)
|
|||
cpu.setLong(addrOld + X86.TSS386.TASK_DS, cpu.segDS.sel);
|
||||
|
||||
/*
|
||||
* segFS amd segGS exist only on 80386 machines
|
||||
* segFS and segGS exist only on 80386 machines
|
||||
*/
|
||||
cpu.assert(I386 && cpu.model >= X86.MODEL_80386);
|
||||
cpu.setLong(addrOld + X86.TSS386.TASK_FS, cpu.segFS.sel);
|
||||
|
|
@ -1005,7 +1094,7 @@ X86Seg.prototype.switchTSS = function switchTSS(selNew, fNest)
|
|||
cpu.segDS.load(cpu.getShort(addrNew + X86.TSS386.TASK_DS));
|
||||
|
||||
/*
|
||||
* segFS amd segGS exist only on 80386 machines
|
||||
* segFS and segGS exist only on 80386 machines
|
||||
*/
|
||||
cpu.assert(I386 && cpu.model >= X86.MODEL_80386);
|
||||
cpu.segFS.load(cpu.getShort(addrNew + X86.TSS386.TASK_FS));
|
||||
|
|
|
|||
Loading…
Reference in a new issue