From 6660b9479fee60eca26c8e0bd91ec51e9711a3de Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Tue, 18 Nov 2014 18:35:00 -0800 Subject: [PATCH] OS/2 1.0 debugging begins --- blog/2014/09/30/README.md | 1 + modules/pcjs/lib/debugger.js | 43 ++++--- modules/pcjs/lib/x86.js | 31 ++++- modules/pcjs/lib/x86cpu.js | 86 +++---------- modules/pcjs/lib/x86help.js | 232 ++++++++++++++++++++++++++++++----- modules/pcjs/lib/x86op0f.js | 5 +- modules/pcjs/lib/x86opxx.js | 8 +- modules/pcjs/lib/x86seg.js | 136 ++++++++++++-------- 8 files changed, 365 insertions(+), 177 deletions(-) diff --git a/blog/2014/09/30/README.md b/blog/2014/09/30/README.md index 075864342..33baae69d 100644 --- a/blog/2014/09/30/README.md +++ b/blog/2014/09/30/README.md @@ -204,6 +204,7 @@ about a few: - Unfiltered for…in loop - Bitwise operator usage - Comma expressions +- loop statement that doesn't loop - “throw” of exception caught locally I acknowledge those those features can introduce bugs if you're not careful, so I make sure I'm careful. I don't diff --git a/modules/pcjs/lib/debugger.js b/modules/pcjs/lib/debugger.js index 1b22b5120..6be04d4b7 100644 --- a/modules/pcjs/lib/debugger.js +++ b/modules/pcjs/lib/debugger.js @@ -160,12 +160,21 @@ function Debugger(parmsDbg) * things down, but by that point, you've presumably already captured the info you need * and are willing to wait. */ - if (DEBUG) { - this.traceInit(); - } + if (DEBUG) this.traceInit(); this.sInitCommands = parmsDbg['commands']; + /* + * Make it easier to access Debugger commands from an external REPL (eg, the WebStorm + * "live" console window); eg: + * + * $('r') + */ + var dbg = this; + if (window && window['$'] === undefined) { + window['$'] = function(s) { return dbg.doCommand(s); }; + } + } // endif DEBUGGER } @@ -2145,7 +2154,7 @@ if (DEBUGGER) { if (sel == this.cpu.segDS.sel) return this.cpu.segDS; if (sel == this.cpu.segES.sel) return this.cpu.segES; if (sel == this.cpu.segSS.sel) return this.cpu.segSS; - var seg = new X86Seg(this.cpu); + var seg = new X86Seg(this.cpu, X86Seg.ID.OTHER, "DBG"); /* * TODO: Confirm that it's OK for this function to drop any error from seg.load() on the floor.... */ @@ -2902,7 +2911,8 @@ if (DEBUGGER) { s += (fProt? '\n' : ' '); s += this.getSegStr(this.cpu.segCS, fProt) + " IP=" + str.toHexWord(this.cpu.regIP) + this.getFlagStr("V") + this.getFlagStr("D") + this.getFlagStr("I") + this.getFlagStr("T") + - this.getFlagStr("S") + this.getFlagStr("Z") + this.getFlagStr("A") + this.getFlagStr("P") + this.getFlagStr("C"); + this.getFlagStr("S") + this.getFlagStr("Z") + this.getFlagStr("A") + this.getFlagStr("P") + this.getFlagStr("C") + + " PS=" + str.toHexWord(this.cpu.getPS()); if (fProt) { s += " MS=" + str.toHexWord(this.cpu.regMSW) + '\n' + this.getDTRStr("LD", this.cpu.segLDT.sel, this.cpu.segLDT.base, this.cpu.segLDT.base + this.cpu.segLDT.limit) + ' ' + @@ -3517,14 +3527,6 @@ if (DEBUGGER) { if (aAddr[0] == null) return; if (sCmd == "ds") { - /* - * We used to call: - * - * var seg = new X86Seg(this.cpu); - * if (seg.load(aAddr[0], true) >= 0) { ... } - * - * but using getSegment() allows us to dump active segment registers, too. - */ var seg = this.getSegment(aAddr[0]); if (seg.sel != null) { var s = "selector=" + str.toHexWord(aAddr[0]) + " limit=" + str.toHexWord(seg.limit) + " base=" + str.toHex(seg.base); @@ -4455,8 +4457,8 @@ if (DEBUGGER) { function onCountStepComplete() { /* * We explicitly called stepCPU() with fUpdateCPU === false, because repeatedly - * calling updateCPU() is very slow, so once the repeat count has been exhausted, - * we need to perform a final updateCPU(). + * calling updateCPU() can be very slow, especially when fDisplayLiveRegs is true, + * so once the repeat count has been exhausted, we must perform a final updateCPU(). */ dbg.cpu.updateCPU(); dbg.setBusy(false); @@ -4580,10 +4582,19 @@ if (DEBUGGER) { this.println("ended assemble @" + this.hexAddr(this.aAddrAssemble)); this.aAddrNextCode = this.aAddrAssemble; this.fAssemble = false; + } else { + sCmd = '?'; } } + sCmd = sCmd.toLowerCase(); - if (this.isReady() && !this.isBusy(true) && sCmd.length > 0) { + + /* + * I'm going to try relaxing the !isBusy() requirement for doCommand(), to maximize our + * ability to issue Debugger commands externally. + */ + if (this.isReady() /* && !this.isBusy(true) */ && sCmd.length > 0) { + if (this.fAssemble) { sCmd = "a " + this.hexAddr(this.aAddrAssemble) + " " + sCmd; } diff --git a/modules/pcjs/lib/x86.js b/modules/pcjs/lib/x86.js index 6d543d9cc..b4bbc8133 100644 --- a/modules/pcjs/lib/x86.js +++ b/modules/pcjs/lib/x86.js @@ -58,11 +58,11 @@ var X86 = { DF: 0x0400, // bit 10: Direction flag OF: 0x0800, // bit 11: Overflow flag IOPL: { - MASK: 0x3000, // 12-13: I/O Privilege Level (always set on 8086/80186, clear on 80286) + MASK: 0x3000, // bits 12-13: I/O Privilege Level (always set on 8086/80186, clear on 80286 reset) SHIFT: 12 }, - NT: 0x4000, // bit 14: Nested Task flag, always set on 8086/80186, clear on 80286 - BIT15: 0x8000 // bit 15: reserved, always set on 8086/80186, clear otherwise + NT: 0x4000, // bit 14: Nested Task flag (always set on 8086/80186, clear on 80286 reset) + BIT15: 0x8000 // bit 15: reserved (always set on 8086/80186, clear otherwise) }, /* * Machine Status Word definitions (stored in regMSW) @@ -109,7 +109,6 @@ var X86 = { */ TSS: 0x0100, LDT: 0x0200, - TSS_LDT: 0x0300, TSS_BUSY: 0x0300, GATE_CALL: 0x0400, GATE_TASK: 0x0500, @@ -139,6 +138,30 @@ var X86 = { BASE2431: 0xff00 } }, + TSS: { + PREV_TSS: 0x00, + CPL0_SP: 0x02, // start of values altered by task switches + CPL0_SS: 0x04, + CPL1_SP: 0x06, + CPL1_SS: 0x08, + CPL2_SP: 0x0a, + CPL2_SS: 0x0c, + CURR_IP: 0x0e, + CURR_PS: 0x10, + CURR_AX: 0x12, + CURR_CX: 0x14, + CURR_DX: 0x16, + CURR_BX: 0x18, + CURR_SP: 0x1a, + CURR_BP: 0x1c, + CURR_SI: 0x1e, + CURR_DI: 0x20, + CURR_ES: 0x22, + CURR_CS: 0x24, + CURR_SS: 0x26, + CURR_DS: 0x28, // end of values altered by task switches + CURR_LDT: 0x2a + }, /* * Processor Exception Interrupts * diff --git a/modules/pcjs/lib/x86cpu.js b/modules/pcjs/lib/x86cpu.js index 6a2970d64..f2c09be64 100644 --- a/modules/pcjs/lib/x86cpu.js +++ b/modules/pcjs/lib/x86cpu.js @@ -784,7 +784,7 @@ X86CPU.prototype.resetRegs = function() */ this.regMSW = X86.MSW.SET; this.addrIDT = 0; this.addrIDTLimit = 0x03FF; - this.descIDT = {off: 0, sel: 0, acc: 0, maskPS: -1}; + this.descIDT = {off: 0, sel: 0, acc: 0, maskPS: 0}; this.nIOPL = 0; // this should be set before the first setPS() call /* @@ -799,11 +799,11 @@ X86CPU.prototype.resetRegs = function() * segment number and base physical address, respectively), but all segment registers are now defined * as X86Seg objects. */ - this.segCS = new X86Seg(this, "CS"); - this.segDS = new X86Seg(this, "DS"); - this.segSS = new X86Seg(this, "SS"); - this.segES = new X86Seg(this, "ES"); - this.segZERO = new X86Seg(this, "ZERO"); + this.segCS = new X86Seg(this, X86Seg.ID.CODE, "CS"); + this.segDS = new X86Seg(this, X86Seg.ID.DATA, "DS"); + this.segES = new X86Seg(this, X86Seg.ID.DATA, "ES"); + this.segSS = new X86Seg(this, X86Seg.ID.STACK, "SS"); + this.segNULL = new X86Seg(this, X86Seg.ID.NULL, "NULL"); this.setCSIP(0, 0xFFFF); // this should be called before the first setPS() call /* @@ -826,10 +826,10 @@ X86CPU.prototype.resetRegs = function() /* * TODO: Verify what the 80286 actually sets addrGDT and addrGDTLimit to on reset (or if it leaves them alone). */ - this.addrGDT = 0; this.addrGDTLimit = 0xFFFF; // GDTR - this.segLDT = new X86Seg(this, "LDT", true); // LDTR - this.segTSS = new X86Seg(this, "TSS", true); // TR - this.segVER = new X86Seg(this, "VER", true); // a scratch segment register for VERR and VERW instructions + this.addrGDT = 0; this.addrGDTLimit = 0xFFFF; // GDTR + this.segLDT = new X86Seg(this, X86Seg.ID.LDT, "LDT", true); // LDTR + this.segTSS = new X86Seg(this, X86Seg.ID.TSS, "TSS", true); // TR + this.segVER = new X86Seg(this, X86Seg.ID.OTHER, "VER", true); // a scratch segment register for VERR and VERW instructions this.setCSIP(0xFFF0, 0xF000); // in real-mode, 0xF000 defaults the CS base address to 0x0F0000 this.segCS.setBase(0xFF0000); // which is why we must manually adjust the CS base address to 0xFF0000 } @@ -1180,8 +1180,8 @@ X86CPU.prototype.getSeg = function(sName) return this.segSS; case "ES": return this.segES; - case "ZERO": - return this.segZERO; + case "NULL": + return this.segNULL; default: /* * HACK: We return a fake segment register object in which only the base physical address is valid, @@ -1192,62 +1192,6 @@ X86CPU.prototype.getSeg = function(sName) } }; -/** - * loadIDTEntry(nIDT) - * - * Updates descIDT as follows: - * - * descIDT.off 0x0-0x1 offset of interrupt handler - * descIDT.sel 0x2-0x3 selector of interrupt handler - * descIDT.acc 0x4-0x5 access word (protected-mode only) - * descIDT.maskPS mask to apply PS after saving current PS - * - * @this {X86CPU} - * @param {number} nIDT - * @return {boolean} true if successful, false if not (all failure cases currently limited to protected mode) - */ -X86CPU.prototype.loadIDTEntry = function(nIDT) -{ - var offIDT; - - if (DEBUG) this.assert(nIDT >= 0 && nIDT < 256); - - if (this.regMSW & X86.MSW.PE) { - offIDT = this.addrIDT + (nIDT << 3); - if (offIDT + 7 <= this.addrIDTLimit) { - this.descIDT.off = this.getWord(offIDT); - this.descIDT.sel = this.getWord(offIDT + 2); - this.descIDT.acc = this.getWord(offIDT + 4); - switch (this.descIDT.acc & X86.DESC.ACC.TYPE.MASK) { - case X86.DESC.ACC.TYPE.GATE_INT: - this.descIDT.maskPS = ~(X86.PS.NT | X86.PS.TF | X86.PS.IF); - break; - case X86.DESC.ACC.TYPE.GATE_TRAP: - this.descIDT.maskPS = ~(X86.PS.NT | X86.PS.TF); - break; - default: - if (DEBUG) this.assert(false); - return false; - } - return true; - } - return false; - } - if (DEBUG) this.assert(!this.addrIDT && this.addrIDTLimit == 0x03FF); - /* - * Intel documentation for INT/INTO under "REAL ADDRESS MODE EXCEPTIONS" says: - * - * "[T]he 80286 will shut down if the SP = 1, 3, or 5 before executing the INT or INTO instruction--due to lack of stack space" - * - * Huh? Why would real-mode care? See http://localhost:8088/pubs/pc/reference/intel/80286/progref/#page-260 - */ - offIDT = this.addrIDT + (nIDT << 2); - this.descIDT.off = this.getWord(offIDT); - this.descIDT.sel = this.getWord(offIDT + 2); - this.descIDT.maskPS = ~(X86.PS.TF | X86.PS.IF); - return true; -}; - /** * setCS(sel) * @@ -1973,7 +1917,7 @@ X86CPU.prototype.modEAWordEnabled = function modEAWordEnabled(seg, off) X86CPU.prototype.setEAByteEnabled = function setEAByteEnabled(b) { if (!EAFUNCS && (this.opFlags & X86.OPFLAG.NOWRITE)) return; - this.setByte(this.segEA.checkWrite(this.offEA, 1), b); + this.setByte(this.segEA.checkWrite(this.offEA, 0), b); }; /** @@ -1985,7 +1929,7 @@ X86CPU.prototype.setEAByteEnabled = function setEAByteEnabled(b) X86CPU.prototype.setEAWordEnabled = function setEAWordEnabled(w) { if (!EAFUNCS && (this.opFlags & X86.OPFLAG.NOWRITE)) return; - this.setWord(this.segEA.checkWrite(this.offEA, 2), w); + this.setWord(this.segEA.checkWrite(this.offEA, 1), w); }; /** @@ -2593,7 +2537,7 @@ X86CPU.prototype.stepCPU = function(nMinCycles) /* * Make sure that every instruction is assessing a cycle cost, and that the cost is a net positive. */ - if (this.nStepCycles >= this.nSnapCycles && !(this.opFlags & X86.OPFLAG.PREFIXES)) { + if (this.aFlags.fComplete && this.nStepCycles >= this.nSnapCycles && !(this.opFlags & X86.OPFLAG.PREFIXES)) { this.println("cycle miscount: " + (this.nSnapCycles - this.nStepCycles)); this.setIP(this.opEA - this.segCS.base); this.stopCPU(); diff --git a/modules/pcjs/lib/x86help.js b/modules/pcjs/lib/x86help.js index e76f5cbb5..ec1751446 100644 --- a/modules/pcjs/lib/x86help.js +++ b/modules/pcjs/lib/x86help.js @@ -410,29 +410,6 @@ var X86Help = { } return src; }, - /** - * @this {X86CPU} - * @param {number} nIDT - * @param {number|null|undefined} nError - * @param {number} nCycles (in addition to the default of nOpCyclesInt) - */ - opHelpINT: function(nIDT, nError, nCycles) { - /* - * TODO: We assess the cycle cost up front, because if loadIDTEntry() fails and we end up in opHelpFault(), - * no cost may get assessed. opHelpFault() needs to determine an appropriate cycle cost. - */ - this.nStepCycles -= this.CYCLES.nOpCyclesInt + nCycles; - if (this.loadIDTEntry(nIDT)) { - this.pushWord(this.getPS()); - this.regPS &= this.descIDT.maskPS; - this.pushWord(this.segCS.sel); - this.pushWord(this.regIP); - if (nError != null) this.pushWord(nError); - this.setCSIP(this.descIDT.off, this.descIDT.sel); - return; - } - X86Help.opHelpFault.call(this, X86.EXCEPTION.GP_FAULT, (nIDT << 3) | X86.ERRCODE.IDT | X86.ERRCODE.EXT, true); - }, /** * opHelpLMSW(w) * @@ -459,6 +436,8 @@ var X86Help = { }, /** + * opHelpDIVOverflow() + * * @this {X86CPU} */ opHelpDIVOverflow: function() { @@ -468,6 +447,202 @@ var X86Help = { */ X86Help.opHelpINT.call(this, X86.EXCEPTION.DIV_ERR, null, 2); }, + /** + * opHelpINT(nIDT, nError, nCycles) + * + * @this {X86CPU} + * @param {number} nIDT + * @param {number|null|undefined} nError + * @param {number} nCycles (in addition to the default of nOpCyclesInt) + */ + opHelpINT: function(nIDT, nError, nCycles) { + /* + * TODO: We assess the cycle cost up front, because otherwise, if opHelpLoadIDT() fails and we end up in + * opHelpFault(), no cost may be assessed. Ultimately, opHelpFault() needs to determine an appropriate cost. + */ + this.nStepCycles -= this.CYCLES.nOpCyclesInt + nCycles; + if (X86Help.opHelpLoadIDT.call(this, nIDT)) { + if (this.descIDT.maskPS) { + X86Help.opHelpPushPS.call(this, nError); + } else { + X86Help.opHelpSwitchTSS.call(this, this.descIDT.sel, true); + } + return; + } + X86Help.opHelpFault.call(this, X86.EXCEPTION.GP_FAULT, (nIDT << 3) | X86.ERRCODE.IDT | X86.ERRCODE.EXT, true); + }, + /** + * opHelpIRET() + * + * @this {X86CPU} + */ + opHelpIRET: function() { + /* + * TODO: We assess a fixed cycle cost up front, because at the moment, opHelpSwitchTSS() doesn't assess anything. + */ + this.nStepCycles -= this.CYCLES.nOpCyclesIRet; + if (this.regMSW & X86.MSW.PE) { + if (this.regPS & X86.PS.NT) { + var addrNew = this.segTSS.base; + var sel = this.getWord(addrNew + X86.TSS.PREV_TSS); + X86Help.opHelpSwitchTSS.call(this, sel); + return; + } + } + this.setCSIP(this.popWord(), this.popWord()); + this.setPS(this.popWord()); + if (this.cIntReturn) this.checkIntReturn(this.regEIP); + }, + /** + * opHelpLoadIDT(nIDT) + * + * Updates descIDT as follows: + * + * descIDT.off 0x0-0x1 offset of interrupt handler + * descIDT.sel 0x2-0x3 selector of interrupt handler + * descIDT.acc 0x4-0x5 access word (protected-mode only) + * descIDT.maskPS mask to apply PS after saving current PS (0 if none; ie, task switch) + * + * @this {X86CPU} + * @param {number} nIDT + * @return {boolean} true if successful, false if not (all failure cases currently limited to protected mode) + */ + opHelpLoadIDT: function(nIDT) { + var offIDT; + + if (DEBUG) this.assert(nIDT >= 0 && nIDT < 256); + + if (this.regMSW & X86.MSW.PE) { + offIDT = this.addrIDT + (nIDT << 3); + if (offIDT + 7 > this.addrIDTLimit) { + return false; + } + this.descIDT.off = this.getWord(offIDT); + this.descIDT.sel = this.getWord(offIDT + 2); + this.descIDT.acc = this.getWord(offIDT + 4); + this.descIDT.maskPS = 0; + + switch (this.descIDT.acc & X86.DESC.ACC.TYPE.MASK) { + case X86.DESC.ACC.TYPE.GATE_INT: + this.descIDT.maskPS = ~(X86.PS.NT | X86.PS.TF | X86.PS.IF); + break; + case X86.DESC.ACC.TYPE.GATE_TRAP: + this.descIDT.maskPS = ~(X86.PS.NT | X86.PS.TF); + break; + case X86.DESC.ACC.TYPE.GATE_TASK: + break; + default: + if (DEBUG) this.assert(false); + return false; + } + return true; + } + if (DEBUG) this.assert(!this.addrIDT && this.addrIDTLimit == 0x03FF); + /* + * Intel documentation for INT/INTO under "REAL ADDRESS MODE EXCEPTIONS" says: + * + * "[T]he 80286 will shut down if the SP = 1, 3, or 5 before executing the INT or INTO instruction--due to lack of stack space" + * + * Huh? Why would real-mode care? See http://localhost:8088/pubs/pc/reference/intel/80286/progref/#page-260 + */ + offIDT = this.addrIDT + (nIDT << 2); + this.descIDT.off = this.getWord(offIDT); + this.descIDT.sel = this.getWord(offIDT + 2); + this.descIDT.maskPS = ~(X86.PS.TF | X86.PS.IF); + return true; + }, + /** + * @this {X86CPU} + * @param {number|null|undefined} nError + */ + opHelpPushPS: function(nError) { + this.pushWord(this.getPS()); + this.regPS &= this.descIDT.maskPS; + this.pushWord(this.segCS.sel); + this.pushWord(this.regIP); + if (nError != null) this.pushWord(nError); + this.setCSIP(this.descIDT.off, this.descIDT.sel); + this.nFault = -1; + }, + /** + * opHelpSwitchTSS(selNew, fNest) + * + * @this {X86CPU} + * @param {number} selNew + * @param {boolean} [fNest] + * @return {boolean} true if successful, false if error + */ + opHelpSwitchTSS: function(selNew, fNest) { + var addrOld = this.segTSS.base; + var cplOld = this.segCS.cpl; + var selOld = this.segTSS.sel; + if (!fNest) { + if (this.segTSS.type != X86.DESC.ACC.TYPE.TSS_BUSY) { + X86Help.opHelpFault.call(this, X86.EXCEPTION.TS_FAULT, selNew, true); + return false; + } + this.setWord(this.segTSS.addrDesc + X86.DESC.ACC.OFFSET, this.segTSS.acc &= ~X86.DESC.ACC.TYPE.LDT); + this.segTSS.type = X86.DESC.ACC.TYPE.TSS; + } + if (this.segTSS.load(selNew) == null) return false; + var addrNew = this.segTSS.base; + if (DEBUG) { + this.messageDebugger((fNest? "switchTSS" : "returnTSS") + ": old TR=" + str.toHexWord(selOld) + " TSS=" + str.toHex(addrOld, 6) + ", new TR=" + str.toHexWord(selNew) + " TSS=" + str.toHex(addrNew, 6)); + } + if (fNest) { + if (this.segTSS.type == X86.DESC.ACC.TYPE.TSS_BUSY) { + X86Help.opHelpFault.call(this, X86.EXCEPTION.GP_FAULT, selNew, true); + return false; + } + this.setWord(this.segTSS.addrDesc + X86.DESC.ACC.OFFSET, this.segTSS.acc |= X86.DESC.ACC.TYPE.LDT); + this.segTSS.type = X86.DESC.ACC.TYPE.TSS_BUSY; + } + this.setWord(addrOld + X86.TSS.CURR_IP, this.regIP); + this.setWord(addrOld + X86.TSS.CURR_PS, this.getPS()); + this.setWord(addrOld + X86.TSS.CURR_AX, this.regAX); + this.setWord(addrOld + X86.TSS.CURR_CX, this.regCX); + this.setWord(addrOld + X86.TSS.CURR_DX, this.regDX); + this.setWord(addrOld + X86.TSS.CURR_BX, this.regBX); + this.setWord(addrOld + X86.TSS.CURR_SP, this.regSP); + this.setWord(addrOld + X86.TSS.CURR_BP, this.regBP); + this.setWord(addrOld + X86.TSS.CURR_SI, this.regSI); + this.setWord(addrOld + X86.TSS.CURR_DI, this.regDI); + this.setWord(addrOld + X86.TSS.CURR_ES, this.segES.sel); + this.setWord(addrOld + X86.TSS.CURR_CS, this.segCS.sel); + this.setWord(addrOld + X86.TSS.CURR_SS, this.segSS.sel); + this.setWord(addrOld + X86.TSS.CURR_DS, this.segDS.sel); + var offSS = X86.TSS.CURR_SS; + var offSP = X86.TSS.CURR_SP; + var regPS = this.getWord(addrNew + X86.TSS.CURR_PS); + this.setPS(regPS); + /* + * We have to set the NT (Nested Task) flag manually, because setPS() doesn't allow it. + */ + this.regPS = (this.regPS & ~X86.PS.NT) | (regPS | X86.PS.NT); + this.regAX = this.getWord(addrNew + X86.TSS.CURR_AX); + this.regCX = this.getWord(addrNew + X86.TSS.CURR_CX); + this.regDX = this.getWord(addrNew + X86.TSS.CURR_DX); + this.regBX = this.getWord(addrNew + X86.TSS.CURR_BX); + this.regBP = this.getWord(addrNew + X86.TSS.CURR_BP); + this.regSI = this.getWord(addrNew + X86.TSS.CURR_SI); + this.regDI = this.getWord(addrNew + X86.TSS.CURR_DI); + this.segES.load(this.getWord(addrNew + X86.TSS.CURR_ES)); + this.segDS.load(this.getWord(addrNew + X86.TSS.CURR_DS)); + this.setCSIP(this.getWord(addrNew + X86.TSS.CURR_IP), this.getWord(addrNew + X86.TSS.CURR_CS)); + if (this.segCS.cpl < cplOld) { + offSP = (this.segCS.cpl << 2) + X86.TSS.CPL0_SP; + offSS = offSP + 2; + } + this.regSP = this.getWord(addrNew + offSP); + this.segSS.load(this.getWord(addrNew + offSS)); + this.segLDT.load(this.getWord(addrNew + X86.TSS.CURR_LDT)); + if (fNest) { + this.setWord(addrNew + X86.TSS.PREV_TSS, selOld); + this.regPS |= X86.PS.NT; + } + this.regMSW |= X86.MSW.TS; + return true; + }, /** * @this {X86CPU} * @param {number} nFault @@ -479,7 +654,7 @@ var X86Help = { if (this.model >= X86.MODEL_80186) { if (this.nFault < 0) { /* - * Single-fault (error code is passed through, and the original instruction is restartable) + * Single-fault (error code is passed through, and the responsible instruction is restartable) */ this.setIP(this.opEA - this.segCS.base); fFault = true; @@ -516,18 +691,19 @@ var X86Help = { /* * OS/2 1.0 uses an INT3 (0xCC) opcode in conjunction with an invalid IDT to trigger a triple-fault * reset and return to real-mode, and these resets happen quite frequently during boot; for example, OS/2 - * startup messages are displayed using INT 0x10 BIOS calls for each character, and each call requires a - * round-trip mode switch. + * startup messages are displayed using a series of INT 0x10 BIOS calls for each character, and each + * series of BIOS calls requires a round-trip mode switch. * * Since we really only want to halt on "bad" faults, not "good" (ie, intentional) faults, we take * advantage of the fact that all 3 faults comprising the triple-fault point to the INT3 (0xCC) opcode, - * and so whenever we see that opcode, we ignore the caller's fHalt flag. + * and so whenever we see that opcode, we ignore the caller's fHalt flag, and suppress FAULT messages + * unless CPU messages are also enabled. */ if (bOpcode == X86.OPCODE.INT3) { fHalt = false; bitsMessage |= Debugger.MESSAGE.CPU; } - this.messageDebugger("Fault 0x" + str.toHexByte(nFault) + (nError != null? " (0x" + str.toHexWord(nError) + ")" : "") + " on opcode 0x" + str.toHexByte(bOpcode) + " at " + str.toHexAddr(this.regIP, this.segCS.sel) + " (%" + str.toHex(this.regEIP) + ")", bitsMessage); + this.messageDebugger("Fault 0x" + str.toHexByte(nFault) + (nError != null? " (0x" + str.toHexWord(nError) + ")" : "") + " on opcode 0x" + str.toHexByte(bOpcode) + " at " + str.toHexAddr(this.regIP, this.segCS.sel) + " (" + str.toHex(this.regEIP, 6) + ")", bitsMessage); if (fHalt) this.dbg.stopCPU(); } } diff --git a/modules/pcjs/lib/x86op0f.js b/modules/pcjs/lib/x86op0f.js index ccd03b6b8..77e07811c 100644 --- a/modules/pcjs/lib/x86op0f.js +++ b/modules/pcjs/lib/x86op0f.js @@ -199,7 +199,10 @@ var X86Op0F = { */ opLTR: function(dst, src) { if (EAFUNCS) this.setEAWord = this.setEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE; - this.segTSS.load(dst); + if (this.segTSS.load(dst) != null) { + this.setWord(this.segTSS.addrDesc + X86.DESC.ACC.OFFSET, this.segTSS.acc |= X86.DESC.ACC.TYPE.LDT); + this.segTSS.type = X86.DESC.ACC.TYPE.TSS_BUSY; + } this.nStepCycles -= (17 + (this.regEA < 0? 0 : 2)); return dst; }, diff --git a/modules/pcjs/lib/x86opxx.js b/modules/pcjs/lib/x86opxx.js index 23f1588d5..453298f2d 100644 --- a/modules/pcjs/lib/x86opxx.js +++ b/modules/pcjs/lib/x86opxx.js @@ -1758,7 +1758,7 @@ var X86OpXX = { */ opLEA: function() { if (EAFUNCS) this.getEAWord = this.getEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOREAD; - this.segData = this.segStack = this.segZERO; // we can't have the EA calculation, if any, "polluted" by segment arithmetic + this.segData = this.segStack = this.segNULL; // we can't have the EA calculation, if any, "polluted" by segment arithmetic X86Mods.aOpModsRegWord[this.getIPByte()].call(this, X86Help.opHelpLEA); if (EAFUNCS) this.getEAWord = this.getEAWordEnabled; }, @@ -2844,11 +2844,7 @@ var X86OpXX = { * op=0xCF (iret) */ opIRET: function() { - this.setCSIP(this.popWord(), this.popWord()); - this.setPS(this.popWord()); - this.nFault = -1; - if (this.cIntReturn) this.checkIntReturn(this.regEIP); - this.nStepCycles -= this.CYCLES.nOpCyclesIRet; + X86Help.opHelpIRET.call(this); }, /** * @this {X86CPU} diff --git a/modules/pcjs/lib/x86seg.js b/modules/pcjs/lib/x86seg.js index 19bb4af06..ba16d3d80 100644 --- a/modules/pcjs/lib/x86seg.js +++ b/modules/pcjs/lib/x86seg.js @@ -43,23 +43,35 @@ if (typeof module !== 'undefined') { * * @constructor * @param {X86CPU} cpu + * @param {number} id * @param {string} [sName] segment name * @param {boolean} [fProt] true if segment register used exclusively in protected-mode */ -function X86Seg(cpu, sName, fProt) +function X86Seg(cpu, id, sName, fProt) { this.cpu = cpu; + this.id = id; + this.sName = sName || ""; this.sel = 0; this.base = 0; this.limit = 0xffff; this.acc = 0; - this.fCode = (sName == "CS"); - this.sName = sName; + this.addrDesc = null; this.cpl = 0; this.dpl = 0; this.updateAccess(fProt); } +X86Seg.ID = { + NULL: 0, // "NULL" + CODE: 1, // "CS" + DATA: 2, // "DS", "ES" + STACK: 3, // "SS" + TSS: 4, // "TSS" + LDT: 5, // "LDT" + OTHER: 6 // "VER", "DBG", etc +}; + /* * Class methods */ @@ -72,7 +84,7 @@ function X86Seg(cpu, sName, fProt) * @this {X86Seg} * @param {number} sel * @param {boolean} [fSuppress] is true to suppress any errors - * @return {number} base address of selected segment, or -1 if error + * @return {number|null} base address of selected segment, or null if error */ X86Seg.loadReal = function loadReal(sel, fSuppress) { @@ -101,7 +113,7 @@ X86Seg.loadReal = function loadReal(sel, fSuppress) * @this {X86Seg} * @param {number} sel * @param {boolean} [fSuppress] is true to suppress any errors - * @return {number} base address of selected segment, or -1 if error + * @return {number|null} base address of selected segment, or null if error */ X86Seg.loadProt = function loadProt(sel, fSuppress) { @@ -124,7 +136,7 @@ X86Seg.loadProt = function loadProt(sel, fSuppress) this.cpu.nStepCycles -= 15; return this.loadDesc8(sel, addrDesc); } - return -1; + return null; }; /** @@ -137,7 +149,7 @@ X86Seg.loadProt = function loadProt(sel, fSuppress) * @param {number} off is a segment-relative offset * @param {number} cb is number of extra bytes to check (0 or 1) * @param {boolean} [fSuppress] is true to suppress any errors - * @return {number} corresponding physical address if valid, -1 if not + * @return {number|null} corresponding physical address if valid, null if not */ X86Seg.checkReadReal = function checkReadReal(off, cb, fSuppress) { @@ -154,7 +166,7 @@ X86Seg.checkReadReal = function checkReadReal(off, cb, fSuppress) * @param {number} off is a segment-relative offset * @param {number} cb is number of extra bytes to check (0 or 1) * @param {boolean} [fSuppress] is true to suppress any errors - * @return {number} corresponding physical address if valid, -1 if not + * @return {number|null} corresponding physical address if valid, null if not */ X86Seg.checkWriteReal = function checkWriteReal(off, cb, fSuppress) { @@ -168,7 +180,7 @@ X86Seg.checkWriteReal = function checkWriteReal(off, cb, fSuppress) * @param {number} off is a segment-relative offset * @param {number} cb is number of extra bytes to check (0 or 1) * @param {boolean} [fSuppress] is true to suppress any errors - * @return {number} corresponding physical address if valid, -1 if not + * @return {number|null} corresponding physical address if valid, null if not */ X86Seg.checkReadProtEnabled = function checkReadProtEnabled(off, cb, fSuppress) { @@ -185,14 +197,14 @@ X86Seg.checkReadProtEnabled = function checkReadProtEnabled(off, cb, fSuppress) * @param {number} off is a segment-relative offset * @param {number} cb is number of extra bytes to check (0 or 1) * @param {boolean} [fSuppress] is true to suppress any errors - * @return {number} corresponding physical address if valid, -1 if not + * @return {number|null} corresponding physical address if valid, null if not */ X86Seg.checkReadProtDisabled = function checkReadProtDisabled(off, cb, fSuppress) { if (!fSuppress) { X86Help.opHelpFault.call(this.cpu, X86.EXCEPTION.GP_FAULT, 0); } - return -1; + return null; }; /** @@ -202,7 +214,7 @@ X86Seg.checkReadProtDisabled = function checkReadProtDisabled(off, cb, fSuppress * @param {number} off is a segment-relative offset * @param {number} cb is number of extra bytes to check (0 or 1) * @param {boolean} [fSuppress] is true to suppress any errors - * @return {number} corresponding physical address if valid, -1 if not + * @return {number|null} corresponding physical address if valid, null if not */ X86Seg.checkWriteProtEnabled = function checkWriteProtEnabled(off, cb, fSuppress) { @@ -219,14 +231,14 @@ X86Seg.checkWriteProtEnabled = function checkWriteProtEnabled(off, cb, fSuppress * @param {number} off is a segment-relative offset * @param {number} cb is number of extra bytes to check (0 or 1) * @param {boolean} [fSuppress] is true to suppress any errors - * @return {number} corresponding physical address if valid, -1 if not + * @return {number|null} corresponding physical address if valid, null if not */ X86Seg.checkWriteProtDisabled = function checkWriteProtDisabled(off, cb, fSuppress) { if (!fSuppress) { X86Help.opHelpFault.call(this.cpu, X86.EXCEPTION.GP_FAULT, 0); } - return -1; + return null; }; /* @@ -245,7 +257,7 @@ X86Seg.checkWriteProtDisabled = function checkWriteProtDisabled(off, cb, fSuppre * @this {X86Seg} * @param {number} sel is the selector * @param {number} addrDesc is the offset - * @return {number} base address of selected segment, or -1 if error + * @return {number} base address of selected segment */ X86Seg.prototype.loadDesc6 = function(sel, addrDesc) { @@ -253,16 +265,15 @@ X86Seg.prototype.loadDesc6 = function(sel, addrDesc) var base = this.cpu.getWord(addrDesc + 0) | ((acc & 0xff) << 16); var limit = this.cpu.getWord(addrDesc + 4); - if (DEBUG) { - this.cpu.messageDebugger("loadDesc6(" + this.sName + "): base=" + str.toHex(base) + " limit=" + str.toHexWord(limit) + " acc=" + str.toHexWord(acc), Debugger.MESSAGE.SEG); - } - this.sel = sel; this.base = base; this.limit = limit; this.acc = acc & X86.DESC.ACC.MASK; + this.addrDesc = addrDesc; this.updateAccess(); + this.messageDebugger(base, limit, acc); + return base; }; @@ -281,40 +292,40 @@ X86Seg.prototype.loadDesc6 = function(sel, addrDesc) * @this {X86Seg} * @param {number} sel is the selector * @param {number} addrDesc is the offset - * @return {number} base address of selected segment, or -1 if error + * @return {number|null} base address of selected segment, or null if error */ X86Seg.prototype.loadDesc8 = function(sel, addrDesc) { var limit = this.cpu.getWord(addrDesc + X86.DESC.LIMIT.OFFSET); var acc = this.cpu.getWord(addrDesc + X86.DESC.ACC.OFFSET); + var type = (acc & X86.DESC.ACC.TYPE.MASK); var base = this.cpu.getWord(addrDesc + X86.DESC.BASE.OFFSET) | ((acc & X86.DESC.ACC.BASE1623) << 16); var ext = (DEBUG? this.cpu.getWord(addrDesc + X86.DESC.EXT.OFFSET) : 0); - if (DEBUG) { - var ch = (this.sName.length < 3? " " : ""); - this.cpu.messageDebugger("loadDesc8(" + this.sName + "):" + ch + " base=" + str.toHex(base) + " limit=" + str.toHexWord(limit) + " acc=" + str.toHexWord(acc) + (ext? " ext=" + str.toHexWord(ext) : ""), Debugger.MESSAGE.SEG); - this.cpu.assert(!ext || ext == X86.DESC.EXT.AVAIL); - } - - /* - * For LSL (which uses fSuppress), we must support X86.DESC.ACC.TYPE.SEG as well as TSS and LDT. - */ - var accType; - if ((acc & X86.DESC.ACC.TYPE.SEG) || (accType = (acc & X86.DESC.ACC.TYPE.MASK)) && accType <= X86.DESC.ACC.TYPE.TSS_BUSY) { + while (true) { + /* + * For LSL, we must support X86.DESC.ACC.TYPE.SEG as well as TSS and LDT. + */ + if (!(acc & X86.DESC.ACC.TYPE.SEG) && type > X86.DESC.ACC.TYPE.TSS_BUSY) { + base = null; + break; + } + if (this.id == X86Seg.ID.TSS) { + if (type != X86.DESC.ACC.TYPE.TSS && type != X86.DESC.ACC.TYPE.TSS_BUSY) { + X86Help.opHelpFault.call(this.cpu, X86.EXCEPTION.TS_FAULT, sel, true); + base = null; + break; + } + } this.sel = sel; this.base = base; this.limit = limit; - /* - * Note that bits 0-7 of acc will usually contain the BASE1623 bits from the descriptor entry, - * but it doesn't matter, because the only acc bits we pay attention to are bits 8-15; however, - * to keep things tidy, we zero bits 0-7. Perhaps we'll find other (internal) uses for those bits. - */ - this.acc = acc & X86.DESC.ACC.MASK; - this.type = acc & X86.DESC.ACC.TYPE.MASK; + this.acc = acc; + this.type = type; + this.addrDesc = addrDesc; this.updateAccess(); - } - else { - base = -1; + this.messageDebugger(base, limit, acc, ext); + break; } return base; }; @@ -325,7 +336,7 @@ X86Seg.prototype.loadDesc8 = function(sel, addrDesc) * This is used in unusual situations where the base must be set independently; normally, the base * is set according to the selector provided to load(), but there are a few cases where setBase() is * required (eg, in resetRegs() where the 80286 wants the real-mode CS selector to be 0xF000 but the - * CS base must be 0xFF0000, and LOADALL). + * CS base must be 0xFF0000, and possibly LOADALL). * * @this {X86Seg} * @param {number} addr @@ -346,7 +357,7 @@ X86Seg.prototype.setBase = function(addr) */ X86Seg.prototype.save = function() { - return [this.sel, this.base, this.limit, this.acc, this.fCode, this.sName, this.cpl, this.dpl]; + return [this.sel, this.base, this.limit, this.acc, this.id, this.sName, this.cpl, this.dpl, this.addrDesc]; }; /** @@ -363,14 +374,15 @@ X86Seg.prototype.restore = function(a) if (typeof a == "number") { this.load(a); } else { - this.sel = a[0]; - this.base = a[1]; - this.limit = a[2]; - this.acc = a[3]; - this.fCode = a[4]; - this.sName = a[5]; - this.cpl = a[6]; - this.dpl = a[7]; + this.sel = a[0]; + this.base = a[1]; + this.limit = a[2]; + this.acc = a[3]; + this.id = a[4]; + this.sName = a[5]; + this.cpl = a[6]; + this.dpl = a[7]; + this.addrDesc = a[8]; } }; @@ -414,8 +426,30 @@ X86Seg.prototype.updateAccess = function(fProt) this.checkRead = X86Seg.checkReadReal; this.checkWrite = X86Seg.checkWriteReal; this.cpl = this.dpl = 0; + this.addrDesc = null; } return fProt; }; +/** + * messageDebugger(base, limit, acc, ext) + * + * @param {number} base + * @param {number} limit + * @param {number} acc + * @param {number} [ext] + */ +X86Seg.prototype.messageDebugger = function(base, limit, acc, ext) +{ + if (DEBUG) { + if (DEBUGGER) { + var ch = (this.sName.length < 3? " " : ""); + var sDPL = " dpl=" + this.dpl; + if (this.id == X86Seg.ID.CODE) sDPL += " cpl=" + this.cpl; + this.cpu.messageDebugger("loadDesc(" + this.sName + "):" + ch + " base=" + str.toHex(base) + " limit=" + str.toHexWord(limit) + " acc=" + str.toHexWord(acc) + sDPL, Debugger.MESSAGE.SEG); + } + this.cpu.assert(!ext || ext == X86.DESC.EXT.AVAIL); + } +}; + if (typeof module !== 'undefined') module.exports = X86Seg;