/** * @fileoverview Implements PCx86 X86 Segment Registers * @author Jeff Parsons * @copyright © Jeff Parsons 2012-2017 * * This file is part of PCjs, a computer emulation software project at . * * PCjs is free software: you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with PCjs. If not, * see . * * You are required to include the above copyright notice in every modified copy of this work * and to display that copyright notice when the software starts running; see COPYRIGHT in * . * * Some PCjs files also attempt to load external resource files, such as character-image files, * ROM files, and disk image files. Those external resource files are not considered part of PCjs * for purposes of the GNU General Public License, and the author does not claim any copyright * as to their contents. */ "use strict"; if (NODE) { var Str = require("../../shared/lib/strlib"); var Messages = require("./messages"); var X86 = require("./x86"); } /* * NOTE: The protected-mode support in this module was initially added for 80286 support, and is * currently being upgraded for 80386 support. In a perfect world, all 80386-related support would * be disabled/skipped whenever the processor is merely an 80286. And in fact, that's the case * with some of the early changes (eg, skipping X86.DESC.EXT.BASE2431 and X86.DESC.EXT.LIMIT1619 * fields unless the processor is an 80386). * * However, the reality is that I won't always be that strict, either because I'm lazy or I don't * want to risk a run-time performance hit or (more pragmatically) because any 80286 code you're likely * to run probably won't attempt to use descriptor types or other features unique to the 80386 anyway, * so the extra paranoia may not be worth the effort. Ultimately, I would like to see the code tailor * itself to the current CPU model, generally with model-specific functions, but that's a lot of work. */ /** * TODO: The Closure Compiler treats ES6 classes as 'struct' rather than 'dict' by default, * which would force us to declare all class properties in the constructor, as well as prevent * us from defining any named properties. So, for now, we mark all our classes as 'unrestricted'. * * @unrestricted */ class X86Seg { /** * X86Seg(cpu, sName) * * @this {X86Seg} * @param {X86CPU} cpu * @param {number} id * @param {string} [sName] segment register name * @param {boolean} [fProt] true if segment register used exclusively in protected-mode (eg, segLDT) */ constructor(cpu, id, sName, fProt) { this.cpu = cpu; this.dbg = cpu.dbg; this.id = id; this.sName = sName || ""; this.sel = 0; this.limit = 0xffff; this.offMax = this.limit + 1; this.base = 0; this.acc = this.type = 0; this.ext = 0; this.cpl = this.dpl = 0; this.addrDesc = X86.ADDR_INVALID; this.sizeData = this.sizeAddr = 2; this.maskData = this.maskAddr = 0xffff; this.loadV86 = this.loadReal; this.checkReadV86 = this.checkReadReal; this.checkWriteV86 = this.checkWriteReal; /* * Preallocated object for "probed" segment loads */ this.probe = { sel: -1, 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 * switch was the result of a CALL (ie, fCall is true) and one or more (up to 32) parameters are on * the old stack, they will be copied to awParms, and then once the stack is switched, the parameters * will be pushed from awParms onto the new stack. * * The typical ways of loading a new segment into CS are JMPF, CALLF (or INT), and RETF (or IRET), * via CPU functions setCSIP() and helpINT(), which use segCS.loadCode() and segCS.loadIDT(), respectively. * * loadCode() requires an fCall value: null means NO privilege level transition may occur, true * allows a stack switch and a privilege transition to a numerically lower privilege, and false allows * a stack restore and a privilege transition to a numerically greater privilege. * * loadIDT() sets fCall to true unconditionally in protected-mode (fCall has no meaning in real-mode). */ if (this.id == 1) { // X86Seg.ID.CODE (don't use until it's defined, or the Closure Compiler won't inline it) this.offIP = 0; this.fCall = null; this.fStackSwitch = false; this.awParms = new Array(32); this.aCallBreaks = []; } this.updateMode(true, fProt); } /** * addCallBreak(fn) * * Returns a "call break" address in an [off, sel] array. The given function, fn(), is called * whenever that address is called, and if fn() returns false, then the call is skipped. Otherwise, * the call is performed (ie, the old CS:[E]IP is pushed on the stack, and CS:[E]IP is set to the * "call break" address. Which is probably a bad idea, so your function should probably always * return false. Just sayin'. TODO: Should probably just force all "call break" calls to be skipped. * * @this {X86Seg} * @param {function()} fn * @return {Array.} containing offset and selector of call-break address */ addCallBreak(fn) { this.aCallBreaks.push(fn); return [this.aCallBreaks.length, X86Seg.CALLBREAK_SEL]; } /** * loadCode(off, sel, fCall) * * A simple wrapper function that encapsulates setting offIP and fCall for segCS loads. * * @this {X86Seg} * @param {number} off * @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 X86.ADDR_INVALID if error */ loadCode(off, sel, fCall) { this.offIP = off; this.fCall = fCall; return this.load(sel); } /** * loadReal(sel, fProbe) * * The default segment load() function for real-mode. * * @this {X86Seg} * @param {number} sel * @param {boolean} [fProbe] (here only to make the function signatures of loadReal() and loadProt() match) * @return {number} base address of selected segment */ loadReal(sel, fProbe) { this.sel = sel & 0xffff; /* * Loading a new value into a segment register in real-mode alters ONLY the selector and the base; * all other attributes (eg, limit, operand size, address size, etc) are unchanged. If you run any * code that switches to protected-mode, loads a 32-bit code segment, and then switches back to * real-mode, it is THAT code's responsibility to load a 16-bit segment into CS before returning to * real-mode; otherwise, your machine will probably be toast. */ return this.base = this.sel << 4; } /** * 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. * * Segments in protected-mode are referenced by selectors, which are indexes into descriptor tables (GDT or LDT) * whose descriptors are 4-word (8-byte) entries: * * word 0: segment limit (0-15) * word 1: base address low * word 2: base address high (0-7), segment type (8-11), descriptor type (12), DPL (13-14), present bit (15) * word 3: used only on 80386 and up (should be set to zero for upward compatibility) * * See X86.DESC for offset and bit definitions. * * IDT descriptor entries are handled separately by loadIDT(), which is mapped to loadIDTReal() or loadIDTProt(). * * @this {X86Seg} * @param {number} sel * @param {boolean} [fProbe] * @return {number} base address of selected segment, or X86.ADDR_INVALID if error */ loadProt(sel, fProbe) { var addrDT; var addrDTLimit; var cpu = this.cpu; /* * Some instructions (eg, CALLF) load a 32-bit value for the selector, while others (eg, LDS) do not; * however, in ALL cases, only the low 16 bits are significant. */ sel &= 0xffff; if (!(sel & X86.SEL.LDT)) { addrDT = cpu.addrGDT; addrDTLimit = cpu.addrGDTLimit; } else { addrDT = cpu.segLDT.base; addrDTLimit = (addrDT + cpu.segLDT.limit)|0; } /* * The ROM BIOS POST executes some test code in protected-mode without properly initializing the LDT, * which has no bearing on the ROM's own code, because it never loads any LDT selectors, but if at the same * time our Debugger attempts to validate a selector in one of its breakpoints, that could cause some grief. * * Fortunately, the Debugger now has its own interface, probeDesc(), so that should no longer be a concern. */ if (addrDT) { var addrDesc = (addrDT + (sel & X86.SEL.MASK))|0; if ((addrDTLimit - addrDesc)|0 >= 7) { /* * TODO: This is the first of many steps toward accurately counting cycles in protected mode; * I simply noted that "POP segreg" takes 5 cycles in real mode and 20 in protected mode, so I'm * starting with a 15-cycle difference. Obviously the difference will vary with the instruction, * and will be much greater whenever the load fails. */ cpu.nStepCycles -= 15; return this.loadDesc8(addrDesc, sel, fProbe); } if (this.id < X86Seg.ID.VER) { X86.helpFault.call(cpu, fProbe && this.id == X86Seg.ID.STACK? X86.EXCEPTION.TS_FAULT : X86.EXCEPTION.GP_FAULT, sel & X86.ERRCODE.SELMASK); } } return X86.ADDR_INVALID; } /** * loadIDTReal(nIDT) * * @this {X86Seg} * @param {number} nIDT * @return {number} address from selected vector */ loadIDTReal(nIDT) { var cpu = this.cpu; /* * NOTE: The COMPAQ DeskPro 386 ROM loads the IDTR for the real-mode IDT with a limit of 0xffff instead * of the normal 0x3ff. A limit higher than 0x3ff is OK, since all real-mode IDT entries are 4 bytes, and * there's no way to issue an interrupt with a vector > 0xff. Just something to be aware of. */ cpu.assert(nIDT >= 0 && nIDT < 256 && !cpu.addrIDT && cpu.addrIDTLimit >= 0x3ff); /* * 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" * * TODO: Verify that 80286 real-mode actually enforces the above. See http://www.pcjs.org/pubs/pc/reference/intel/80286/progref/#page-260 */ var addrIDT = cpu.addrIDT + (nIDT << 2); var off = cpu.getShort(addrIDT); cpu.regPS &= ~(X86.PS.TF | X86.PS.IF); return (this.load(cpu.getShort(addrIDT + 2)) + off)|0; } /** * loadIDTProt(nIDT) * * @this {X86Seg} * @param {number} nIDT * @return {number} address from selected vector, or X86.ADDR_INVALID if error */ loadIDTProt(nIDT) { var cpu = this.cpu; cpu.assert(nIDT >= 0 && nIDT < 256); nIDT <<= 3; var addrDesc = (cpu.addrIDT + nIDT)|0; if (((cpu.addrIDTLimit - addrDesc)|0) >= 7) { this.fCall = true; var addr = this.loadDesc8(addrDesc, nIDT); if (addr !== X86.ADDR_INVALID) addr += this.offIP; return addr; } X86.helpFault.call(cpu, X86.EXCEPTION.GP_FAULT, nIDT | X86.ERRCODE.IDT); return X86.ADDR_INVALID; } /** * checkReadReal(off, cb) * * TODO: Invoke X86.helpFault.call(this.cpu, X86.EXCEPTION.GP_FAULT) if off+cb is beyond offMax on 80186 and up; * also, determine whether helpFault() call should include an error code, since this is happening in real-mode. * * @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 X86.ADDR_INVALID if error (TODO: No error conditions yet) */ checkReadReal(off, cb) { return (this.base + off)|0; } /** * checkWriteReal(off, cb) * * TODO: Invoke X86.helpFault.call(this.cpu, X86.EXCEPTION.GP_FAULT) if off+cb is beyond offMax on 80186 and up; * also, determine whether helpFault() call should include an error code, since this is happening in real-mode. * * @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 X86.ADDR_INVALID if error (TODO: No error conditions yet) */ checkWriteReal(off, cb) { return (this.base + off)|0; } /** * 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, or X86.ADDR_INVALID if not */ checkReadProt(off, cb) { /* * Since off could be a 32-bit value with the sign bit (bit 31) set, we must convert * it to an unsigned value using ">>>"; offMax was already converted at segment load time. */ if ((off >>> 0) + cb <= this.offMax) { return (this.base + off)|0; } return this.checkReadProtDisallowed(off, cb); } /** * 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, X86.ADDR_INVALID if not */ checkReadProtDown(off, cb) { /* * Since off could be a 32-bit value with the sign bit (bit 31) set, we must convert * it to an unsigned value using ">>>"; offMax was already converted at segment load time. */ if ((off >>> 0) + cb > this.offMax) { return (this.base + off)|0; } return this.checkReadProtDisallowed(off, cb); } /** * checkReadProtDisallowed(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, X86.ADDR_INVALID if not */ checkReadProtDisallowed(off, cb) { X86.helpFault.call(this.cpu, X86.EXCEPTION.GP_FAULT, 0); return X86.ADDR_INVALID; } /** * 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, X86.ADDR_INVALID if not */ checkWriteProt(off, cb) { /* * Since off could be a 32-bit value with the sign bit (bit 31) set, we must convert * it to an unsigned value using ">>>"; offMax was already converted at segment load time. */ if ((off >>> 0) + cb <= this.offMax) { return (this.base + off)|0; } return this.checkWriteProtDisallowed(off, cb); } /** * 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, X86.ADDR_INVALID if not */ checkWriteProtDown(off, cb) { /* * Since off could be a 32-bit value with the sign bit (bit 31) set, we must convert * it to an unsigned value using ">>>"; offMax was already converted at segment load time. */ if ((off >>> 0) + cb > this.offMax) { return (this.base + off)|0; } return this.checkWriteProtDisallowed(off, cb); } /** * checkWriteProtDisallowed(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, X86.ADDR_INVALID if not */ checkWriteProtDisallowed(off, cb) { X86.helpFault.call(this.cpu, X86.EXCEPTION.GP_FAULT, 0); return X86.ADDR_INVALID; } /** * 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 X86.ADDR_INVALID if error */ checkReadDebugger(off, cb) { /* * The Debugger doesn't have separate "check" interfaces for real and protected mode, * since it's not performance-critical. If addrDesc is invalid, then we assume real mode. * * TODO: This doesn't actually check the segment for readability. */ if (DEBUGGER) { if (this.addrDesc === X86.ADDR_INVALID || this.fExpDown && (off >>> 0) + cb > this.offMax || !this.fExpDown && (off >>> 0) + cb <= this.offMax) { return (this.base + off)|0; } } return X86.ADDR_INVALID; } /** * checkWriteDebugger(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 X86.ADDR_INVALID if error */ checkWriteDebugger(off, cb) { /* * The Debugger doesn't have separate "check" interfaces for real and protected mode, * since it's not performance-critical. If addrDesc is invalid, then we assume real mode. * * TODO: This doesn't actually check the segment for writability. */ if (DEBUGGER) { if (this.addrDesc === X86.ADDR_INVALID || this.fExpDown && (off >>> 0) + cb > this.offMax || !this.fExpDown && (off >>> 0) + cb <= this.offMax) { return (this.base + off)|0; } } return X86.ADDR_INVALID; } /** * loadDesc(sel, acc, base, limit) * * Used to manually load a segment register from the data provided (see LOADALL386). * * @this {X86Seg} * @param {number} sel * @param {number} acc * @param {number} base * @param {number} limit */ loadDesc(sel, acc, base, limit) { this.sel = sel; this.base = base; this.limit = limit; this.offMax = (limit >>> 0) + 1; this.acc = acc; this.type = (acc & X86.DESC.ACC.TYPE.MASK); this.ext = (acc >> 16) & (X86.DESC.EXT.BIG | X86.DESC.EXT.LIMITPAGES); var addrDT = (sel & X86.SEL.LDT)? this.cpu.segLDT.base : this.cpu.addrGDT; this.addrDesc = (addrDT + (sel & X86.SEL.MASK))|0; /* * NOTE: This code must take care to leave the mode of the TSS, LDT, and VER segment registers alone; * in particular, we must not allow a real-mode LOADALL to modify their mode, because the rest of PCx86 * assumes that their mode will never change (they were allocated with fProt set to true). */ if (this.id < X86Seg.ID.TSS) this.updateMode(true); if (DEBUG) this.messageSeg(sel, base, limit, this.type); } /** * loadDesc6(addrDesc, sel) * * Used to load a protected-mode selector that refers to a 6-byte "descriptor cache" entry (see LOADALL286): * * word 0: base address low * word 1: base address high (0-7), segment type (8-11), descriptor type (12), DPL (13-14), present bit (15) * word 2: segment limit (0-15) * * @this {X86Seg} * @param {number} addrDesc is the descriptor address * @param {number} sel is the associated selector * @return {number} base address of selected segment */ loadDesc6(addrDesc, sel) { var cpu = this.cpu; var acc = cpu.getShort(addrDesc + 2); var base = cpu.getShort(addrDesc) | ((acc & 0xff) << 16); var limit = cpu.getShort(addrDesc + 4); this.sel = sel; this.base = base; this.limit = limit; this.offMax = (limit >>> 0) + 1; this.acc = acc; this.type = (acc & X86.DESC.ACC.TYPE.MASK); this.ext = 0; this.addrDesc = addrDesc; /* * NOTE: This code must take care to leave the mode of the TSS, LDT, and VER segment registers alone; * in particular, we must not allow a real-mode LOADALL to modify their mode, because the rest of PCx86 * assumes that their mode will never change (they were allocated with fProt set to true). */ if (this.id < X86Seg.ID.TSS) this.updateMode(true); if (DEBUG) this.messageSeg(sel, base, limit, this.type); return base; } /** * loadDesc8(addrDesc, sel, fProbe) * * Used to load a protected-mode selector that refers to an 8-byte "descriptor table" (GDT, LDT, IDT) entry: * * word 0: segment limit (0-15) * word 1: base address low * word 2: base address high (0-7), segment type (8-11), descriptor type (12), DPL (13-14), present bit (15) * word 3: used only on 80386 and up (should be set to zero for upward compatibility) * * See X86.DESC for offset and bit definitions. * * When fProbe is set, we do NOT modify the public properties of the X86Seg object (see class X86Seg above). * We will generate a fault if any of the usual error conditions are detected (and return X86.ADDR_INVALID), but * otherwise, we merely stash all the descriptor values it reads in the X86Seg's private "probe" object. * * Probed loads allow us to deal with complex segment load operations (ie, those involving an implied stack-switch * or task-switch), by allowing us to probe all the new selectors and generate the necessary faults before modifying * any segment registers; if all the probes succeed, then the original load can proceed. * * The next non-probed load of a probed selector will move those probed descriptor values into the X86Seg object, * saving us from having to reload and reparse the descriptor. However, if a different selector is loaded between * the probed and non-probed loads, the probed data is tossed. * * @this {X86Seg} * @param {number} addrDesc is the descriptor address * @param {number} sel is the associated selector, or nIDT*8 if IDT descriptor * @param {boolean} [fProbe] (true if this is a probe) * @return {number} base address of selected segment, or X86.ADDR_INVALID if error */ loadDesc8(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 the 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 = -1; this.updateMode(true, true, false); return this.base; } /* * Any other load, probed or otherwise, should "flush" the probe cache, by setting probe.sel to -1. */ this.probe.sel = -1; /* * 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); var base = cpu.getShort(addrDesc + X86.DESC.BASE.OFFSET) | ((acc & X86.DESC.ACC.BASE1623) << 16); var ext = cpu.getShort(addrDesc + X86.DESC.EXT.OFFSET); var selMasked = sel & X86.SEL.MASK; if (I386 && cpu.model >= X86.MODEL_80386) { var limitOrig = limit; base |= (ext & X86.DESC.EXT.BASE2431) << 16; limit |= (ext & X86.DESC.EXT.LIMIT1619) << 16; if (ext & X86.DESC.EXT.LIMITPAGES) limit = (limit << 12) | 0xfff; } switch (this.id) { case X86Seg.ID.CODE: /* * NOTE: Since we are X86Seg.ID.CODE, we can use this.cpl instead of the more convoluted * this.cpu.segCS.cpl. */ var fCall = this.fCall; this.fStackSwitch = false; /* * This special bit of code is currently used only by the Debugger, when it needs to inject * a 16:32 callback address into the machine that it can intercept calls to. We call these * "call break" addresses, because they're essentially breakpoints that only operate when * a particular address is called; specifically, an address with selector 0x0001 and an offset * that forms a (1-based) index into the aCallBreaks function table. * * In protected-mode, any null selector, including 0x0001 (null with an RPL of 1), is * an invalid CS selector, and while it's not inconceivable that an operating system might * use such a selector for some strange purpose, I've not seen such an operating system. * And in any case, those operating systems are not likely to trigger the Debugger's call to * addCallBreak(), so no call breaks will be generated, and this code will never execute. * * TODO: If we ever need this to be mode-independent, it can be moved somewhere where it will * trigger for both real and protected-mode code segment loads, because CALLBREAK_SEL (0x0001) * is also a very unlikely real-mode CS value (but again, not inconceivable). I think this is * a reasonable solution, and it's likely the best we can do without injecting code into the * machine that we could address -- and even then, it would not be a mode-independent address. */ if (fCall && sel == X86Seg.CALLBREAK_SEL && this.aCallBreaks.length) { var iBreak = this.offIP - 1; var fnCallBreak = this.aCallBreaks[iBreak]; cpu.assert(fnCallBreak); if (fnCallBreak && !fnCallBreak()) { return X86.ADDR_INVALID; } } var rpl = sel & X86.SEL.RPL; var dpl = (acc & X86.DESC.ACC.DPL.MASK) >> X86.DESC.ACC.DPL.SHIFT; var sizeGate = -1, selCode, cplOld, cplNew, fIDT; var addrTSS, offSP, lenSP, regSPPrev, regSSPrev, regPSClear, regSP; if (!selMasked) { /* * selMasked is really the descriptor table offset, and a zero offset is fine for the IDT, * and it's probably fine for the LDT, but it's definitely NOT fine for the GDT, because * that's a reference to the null selector. A null selector is allowed in DS, ES, FS, or GS, * but never CS or SS. Since there's no parameter that tells us which table we're using, * we have to check manually. * * If we ARE attempting to load a null selector from the GDT, then we zero type, ensuring that * sizeGate will remain invalid (-1), triggering a GP_FAULT below. */ if (addrDesc >= cpu.addrGDT && addrDesc < cpu.addrGDTLimit) type = 0; } if (type >= X86.DESC.ACC.TYPE.CODE_EXECONLY) { /* * There are three basic ways to load a new code segment (ignoring special cases like LOADALL): * * 1) CALLF (fCall is true) * 2) RETF (fCall is false) * 3) JMPF (fCall is undefined) * * Also, note that if fProbe is set, we're being called on behalf of a gate, in which case the * gate logic will examine the relative privileges. */ if (fProbe != null) { sizeGate = 0; } else if (fCall !== false) { /* * We deal with CALLF/JMPF first. We've already ascertained that the selector type is a * segment, not a gate, so the next important distinction is CONFORMING vs. non-CONFORMING. * * For a CONFORMING target, we must verify that its DPL <= CPL. For a non-CONFORMING target, * we must verify that RPL <= CPL and DPL == CPL. Assuming both those tests pass, we must also * ensure that the current CPL is recorded as the new RPL (that is, the RPL bits of sel must be * updated). */ if (type & X86.DESC.ACC.TYPE.CONFORMING) { if (dpl <= this.cpl) { sizeGate = 0; } } else { if (rpl <= this.cpl && dpl == this.cpl) { sizeGate = 0; } } if (!sizeGate) { sel = (sel & ~X86.SEL.RPL) | (this.cpl & X86.SEL.RPL); } } else { /* * We deal with RETF next. For starters, we must verify that RPL >= CPL. Moreover, if * RPL > CPL, then we have a privilege level change that requires a stack switch, assuming * the stack selector is acceptable. */ if (rpl >= this.cpl) { if (rpl > this.cpl) { /* * TODO: See if we can defer calling setSS() and setSP() until AFTER the final checks * below, because if, for example, the new CS is not PRESENT, we must generate a fault, * which in turn must restore the original stack, which means helpRETF() must snapshot * the stack registers. */ regSP = cpu.popWord(); cpu.setSS(cpu.popWord(), true); cpu.setSP(regSP); this.fStackSwitch = true; } sizeGate = 0; } } } 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) { sizeGate = 2; 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.GATE386_CALL) { sizeGate = 4; 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) { sizeGate = 2; regPSClear = (X86.PS.VM | X86.PS.NT | X86.PS.TF | X86.PS.IF); cpu.assert(!(acc & 0x1f)); } else if (type == X86.DESC.ACC.TYPE.GATE386_INT) { sizeGate = 4; regPSClear = (X86.PS.VM | X86.PS.NT | X86.PS.TF | X86.PS.IF); cpu.assert(!(acc & 0x1f)); } else if (type == X86.DESC.ACC.TYPE.GATE286_TRAP) { sizeGate = 2; regPSClear = (X86.PS.VM | X86.PS.NT | X86.PS.TF); cpu.assert(!(acc & 0x1f)); } else if (type == X86.DESC.ACC.TYPE.GATE386_TRAP) { sizeGate = 4; regPSClear = (X86.PS.VM | 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 (sizeGate > 0 && !(acc & X86.DESC.ACC.PRESENT)) sizeGate = 0; if (sizeGate > 0) { /* * 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). */ cplOld = this.cpl; fIDT = (addrDesc == cpu.addrIDT + sel); /* * Software interrupts (where fIDT is true and cpu.nFault < 0) require an additional test: * if DPL < CPL, then we must fall into the GP_FAULT code at the end of this case. */ if (rpl <= dpl && (!fIDT || cpu.nFault >= 0 || cplOld <= dpl)) { /* * 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. * * TODO: Verify the PRESENT bit of the gate descriptor, and issue NP_FAULT as appropriate. */ selCode = base & 0xffff; if (I386 && (type & X86.DESC.ACC.TYPE.NONSEG_386)) { limit = limitOrig | (ext << 16); } var selStack = 0, offStack = 0; cplNew = (selCode & X86.SEL.RPL); /* * 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) { /* * Intel pseudo-code suggests that selStack should be "probed" before selCode, but it also * implies that we need to have the DPL of selCode in order to select the correct selStack, * so who knows...? */ if (this.loadProt(selCode, true) === X86.ADDR_INVALID) { return X86.ADDR_INVALID; } /* * Intel pseudo-code suggests that the TSS stack pointer offset is based on the DPL of selCode * rather than the RPL of selCode. TODO: Check for instances where DPL and RPL of selCode differ, * and then figure out which should really be used. */ addrTSS = cpu.segTSS.base; if (!I386 || !(cpu.segTSS.type & X86.DESC.ACC.TYPE.NONSEG_386)) { offSP = (cplNew << 2) + X86.TSS286.CPL0_SP; lenSP = 2; } else { offSP = (cplNew << 3) + X86.TSS386.CPL0_ESP; lenSP = 4; } selStack = cpu.getShort(addrTSS + offSP + lenSP); /* * Intel pseudo-code indicates at least FIVE discrete selStack tests that could trigger * a TS_FAULT at this point: * * 1) Selector must not be null else #TS(O) * 2) Selector index must be within its descriptor table limits else #TS (SS selector) * 3) Selector's RPL must equal DPL of code segment else #TS (SS selector) * 4) Stack segment DPL must equal DPL of code segment else #TS (SS selector) * 5) Descriptor must indicate writable data segment else #TS (SS selector) */ if (!selStack) { X86.helpFault.call(cpu, X86.EXCEPTION.TS_FAULT, selStack); return X86.ADDR_INVALID; } if (cpu.segSS.loadProt(selStack, true) === X86.ADDR_INVALID) { return X86.ADDR_INVALID; } /* * 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). */ offStack = (lenSP == 2)? cpu.getShort(addrTSS + offSP) : cpu.getLong(addrTSS + offSP); } /* * Now that we're past all the probes, it should be safe to clear all flags that need clearing. */ var regPS = cpu.regPS; cpu.regPS &= ~regPSClear; if (regPS & X86.PS.VM) { cpu.setProtMode(true, false); } /* * TODO: Consider whether we can skip this loadProt() call if this.sel already contains selCode * (and the previous mode matches, which might require we cache the mode in the X86Seg object, too). */ if (this.loadProt(selCode, false) === X86.ADDR_INVALID) { return X86.ADDR_INVALID; } cpu.setDataSize(sizeGate); this.offIP = limit; // cpu.assert(this.cpl == cplNew); if (cplNew < 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); if (regPS & X86.PS.VM) { /* * Frames coming from V86-mode ALWAYS contain 32-bit values, and look like this: * * low: EIP * CS (upper 16 bits undefined) * EFLAGS * ESP * SS (upper 16 bits undefined) * ES (upper 16 bits undefined) * DS (upper 16 bits undefined) * FS (upper 16 bits undefined) * high: GS (upper 16 bits undefined) * * Our caller (eg, helpINT()) will take care of pushing the final bits (EFLAGS, CS, and EIP). */ cpu.setDataSize(4); cpu.assert(I386 && cpu.model >= X86.MODEL_80386); cpu.pushData(cpu.segGS.sel, 4, 2); cpu.setGS(0); cpu.pushData(cpu.segFS.sel, 4, 2); cpu.setFS(0); cpu.pushData(cpu.segDS.sel, 4, 2); cpu.setDS(0); cpu.pushData(cpu.segES.sel, 4, 2); cpu.setES(0); } cpu.pushData(regSSPrev, cpu.sizeData, 2); cpu.pushWord(regSPPrev); while (i) cpu.pushWord(this.awParms[--i]); this.fStackSwitch = true; } return this.base; } } if (sizeGate != 0) { X86.helpFault.call(cpu, X86.EXCEPTION.GP_FAULT, (sel & X86.ERRCODE.SELMASK) | (fIDT? X86.ERRCODE.IDT : 0)); return X86.ADDR_INVALID; } if (!(acc & X86.DESC.ACC.PRESENT)) { X86.helpFault.call(cpu, X86.EXCEPTION.NP_FAULT, (sel & X86.ERRCODE.SELMASK) | (fIDT? X86.ERRCODE.IDT : 0)); return X86.ADDR_INVALID; } break; case X86Seg.ID.DATA: if (selMasked) { /* * 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 * * And Windows 95 Setup, during the "Analyzing Your Computer" phase, will fault on an attempt to load * a GDT selector of type LDT (why it does this is a mystery I've not yet investigated): * * Fault 0x0D (0x26F0) on opcode 0x8E @039F:039B (%199E9B) * EAX=0000149F EBX=00000100 ECX=000026F3 EDX=0020149F * ESP=0000AA34 EBP=0000AA3C ESI=000026E7 EDI=00000080 * SS=155F[002AC9D0,C0BF] DS=149F[0031B470,9B1F] ES=0237[000C0000,FFFF] * CS=039F[00199B00,2ABF] FS=0000[00000000,0000] GS=0000[00000000,0000] * LD=0038[00FA4C50,FFEF] GD=[00FA0800,011F] ID=[00FA0000,07FF] TR=0088 A20=ON * CR0=0000FFF1 CR2=00000000 CR3=00000000 PS=00003246 V0 D0 I1 T0 S0 Z1 A0 P1 C0 * 039F:039B 8EC1 MOV ES,CX * ## ds cx * dumpDesc(0x26F3): %00FA2EF0 * base=0006C726 limit=0000 type=0x02 (ldt,not present) ext=0x0000 dpl=0x00 * * In both cases, the segment type is not valid for the target segment register *and* the PRESENT bit * is clear. OS/2 doesn't seem to care whether I report an NP_FAULT or GP_FAULT, but Windows 95 definitely * cares: it will resolve the fault only if a GP_FAULT is reported. And Intel's 80386 Programmers Reference * implies that, yes, GP_FAULT checks are supposed to be performed *before* NP_FAULT checks. */ if (type < X86.DESC.ACC.TYPE.SEG || (type & (X86.DESC.ACC.TYPE.CODE | X86.DESC.ACC.TYPE.READABLE)) == X86.DESC.ACC.TYPE.CODE) { X86.helpFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel & X86.ERRCODE.SELMASK); return X86.ADDR_INVALID; } /* * TODO: This would be a good place to perform some additional access rights checks, too. */ if (!(acc & X86.DESC.ACC.PRESENT)) { X86.helpFault.call(cpu, X86.EXCEPTION.NP_FAULT, sel & X86.ERRCODE.SELMASK); return X86.ADDR_INVALID; } } break; case X86Seg.ID.STACK: if (!selMasked || type < X86.DESC.ACC.TYPE.SEG || (type & (X86.DESC.ACC.TYPE.CODE | X86.DESC.ACC.TYPE.WRITABLE)) != X86.DESC.ACC.TYPE.WRITABLE) { X86.helpFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel & X86.ERRCODE.SELMASK); return X86.ADDR_INVALID; } if (!(acc & X86.DESC.ACC.PRESENT)) { X86.helpFault.call(cpu, X86.EXCEPTION.SS_FAULT, sel & X86.ERRCODE.SELMASK); return X86.ADDR_INVALID; } break; case X86Seg.ID.TSS: var typeTSS = type & ~X86.DESC.ACC.TYPE.TSS_BUSY; if (!selMasked || typeTSS != X86.DESC.ACC.TYPE.TSS286 && typeTSS != X86.DESC.ACC.TYPE.TSS386) { X86.helpFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel & X86.ERRCODE.SELMASK); 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 cases are: * * X86Seg.ID.NULL, X86Seg.ID.LDT, and X86Seg.ID.DBG * * which correspond to segNULL, segLDT and segDebugger; however, segLDT is the only one that might require further validation (TODO: Investigate). */ 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; this.offMax = (limit >>> 0) + 1; this.acc = acc; this.type = type; this.ext = ext; this.addrDesc = addrDesc; /* * A quick recap of what updateMode(fLoad=true, fProt=true, fV86=false) actually updates: * * cpl * dpl * dataSize * dataMask * addrSize * addrMask * fExpDown * load() * loadIDT() * checkRead() * checkWrite() */ this.updateMode(true, true, false); } if (DEBUG) this.messageSeg(sel, base, limit, type, ext); return base; } /** * switchTSS(selNew, fNest) * * Implements TSS (Task State Segment) task switching. * * NOTES: This typically occurs during double-fault processing, because the IDT entry for DF_FAULT normally * contains a task gate. Interestingly, if we force a GP_FAULT to occur at a sufficiently early point in the * OS/2 1.0 initialization code, OS/2 does a nice job of displaying the GP fault and then shutting down: * * 0090:067B FB STI * 0090:067C EBFD JMP 067B * * but it may not have yet reprogrammed the master PIC to re-vector hardware interrupts to IDT entries 0x50-0x57, * so when the next timer interrupt (IRQ 0) occurs, it vectors through IDT entry 0x08, which is the DF_FAULT * vector. A spurious double-fault is generated, and a clean shutdown turns into a messy crash. * * 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; the only rudimentary checks * we currently perform are of the GP_FAULT variety. * * @this {X86Seg} * @param {number} selNew * @param {boolean|null} [fNest] is true if nesting, false if un-nesting, null if neither * @return {boolean} true if successful, false if error */ switchTSS(selNew, fNest) { var cpu = this.cpu; cpu.assert(this === cpu.segCS); var cplOld = this.cpl; var selOld = cpu.segTSS.sel; var addrOld = cpu.segTSS.base; if (!fNest) { /* * TODO: Verify that it is (always) correct to require that the BUSY bit be currently set. */ if (!(cpu.segTSS.type & X86.DESC.ACC.TYPE.TSS_BUSY)) { X86.helpFault.call(cpu, X86.EXCEPTION.GP_FAULT, selNew & X86.ERRCODE.SELMASK); return false; } /* * TODO: Should I be more paranoid about writing our cached ACC value back into the descriptor? */ cpu.setShort(cpu.segTSS.addrDesc + X86.DESC.ACC.OFFSET, cpu.segTSS.acc &= ~X86.DESC.ACC.TYPE.TSS_BUSY); } if (cpu.segTSS.load(selNew) === X86.ADDR_INVALID) { return false; } var addrNew = cpu.segTSS.base; if (DEBUG && DEBUGGER && this.dbg && this.dbg.messageEnabled(Messages.TSS)) { this.dbg.message((fNest? "Task switch" : "Task return") + ": TR " + Str.toHexWord(selOld) + " (%" + Str.toHex(addrOld, 6) + "), new TR " + Str.toHexWord(selNew) + " (%" + Str.toHex(addrNew, 6) + ")"); } if (fNest !== false) { if (cpu.segTSS.type & X86.DESC.ACC.TYPE.TSS_BUSY) { X86.helpFault.call(cpu, X86.EXCEPTION.GP_FAULT, selNew & X86.ERRCODE.SELMASK); return false; } cpu.setShort(cpu.segTSS.addrDesc + X86.DESC.ACC.OFFSET, cpu.segTSS.acc |= X86.DESC.ACC.TYPE.TSS_BUSY); } /* * Now that we're done checking the TSS_BUSY bit in the TYPE field (which is a subset of the ACC field), * sync any changes made above in the ACC field to the TYPE field. */ cpu.segTSS.type = (cpu.segTSS.type & ~X86.DESC.ACC.TYPE.TSS_BUSY) | (cpu.segTSS.acc & X86.DESC.ACC.TYPE.TSS_BUSY); /* * Update the old TSS */ var offSS, offSP; if (cpu.segTSS.type == X86.DESC.ACC.TYPE.TSS286 || cpu.segTSS.type == X86.DESC.ACC.TYPE.TSS286_BUSY) { cpu.setShort(addrOld + X86.TSS286.TASK_IP, cpu.getIP()); cpu.setShort(addrOld + X86.TSS286.TASK_PS, cpu.getPS()); cpu.setShort(addrOld + X86.TSS286.TASK_AX, cpu.regEAX); cpu.setShort(addrOld + X86.TSS286.TASK_CX, cpu.regECX); cpu.setShort(addrOld + X86.TSS286.TASK_DX, cpu.regEDX); cpu.setShort(addrOld + X86.TSS286.TASK_BX, cpu.regEBX); cpu.setShort(addrOld + X86.TSS286.TASK_SP, cpu.getSP()); cpu.setShort(addrOld + X86.TSS286.TASK_BP, cpu.regEBP); cpu.setShort(addrOld + X86.TSS286.TASK_SI, cpu.regESI); cpu.setShort(addrOld + X86.TSS286.TASK_DI, cpu.regEDI); cpu.setShort(addrOld + X86.TSS286.TASK_ES, cpu.segES.sel); cpu.setShort(addrOld + X86.TSS286.TASK_CS, cpu.segCS.sel); cpu.setShort(addrOld + X86.TSS286.TASK_SS, cpu.segSS.sel); cpu.setShort(addrOld + X86.TSS286.TASK_DS, cpu.segDS.sel); /* * Reload all registers from the new TSS; it's important to reload the LDTR sooner * rather than later, so that as segment registers are reloaded, any LDT selectors will * will be located in the correct table. */ cpu.segLDT.load(cpu.getShort(addrNew + X86.TSS286.TASK_LDT)); cpu.setPS(cpu.getShort(addrNew + X86.TSS286.TASK_PS) | (fNest? X86.PS.NT : 0)); cpu.assert(!fNest || !!(cpu.regPS & X86.PS.NT)); cpu.regEAX = cpu.getShort(addrNew + X86.TSS286.TASK_AX); cpu.regECX = cpu.getShort(addrNew + X86.TSS286.TASK_CX); cpu.regEDX = cpu.getShort(addrNew + X86.TSS286.TASK_DX); cpu.regEBX = cpu.getShort(addrNew + X86.TSS286.TASK_BX); cpu.regEBP = cpu.getShort(addrNew + X86.TSS286.TASK_BP); cpu.regESI = cpu.getShort(addrNew + X86.TSS286.TASK_SI); cpu.regEDI = cpu.getShort(addrNew + X86.TSS286.TASK_DI); cpu.segES.load(cpu.getShort(addrNew + X86.TSS286.TASK_ES)); cpu.segDS.load(cpu.getShort(addrNew + X86.TSS286.TASK_DS)); cpu.setCSIP(cpu.getShort(addrNew + X86.TSS286.TASK_IP), cpu.getShort(addrNew + X86.TSS286.TASK_CS)); offSS = X86.TSS286.TASK_SS; offSP = X86.TSS286.TASK_SP; if (this.cpl < cplOld) { offSP = (this.cpl << 2) + X86.TSS286.CPL0_SP; offSS = offSP + 2; } cpu.setSS(cpu.getShort(addrNew + offSS), true); cpu.setSP(cpu.getShort(addrNew + offSP)); } else { cpu.assert(cpu.segTSS.type == X86.DESC.ACC.TYPE.TSS386 || cpu.segTSS.type == X86.DESC.ACC.TYPE.TSS386_BUSY); cpu.setLong(addrOld + X86.TSS386.TASK_CR3, cpu.regCR3); cpu.setLong(addrOld + X86.TSS386.TASK_EIP, cpu.getIP()); cpu.setLong(addrOld + X86.TSS386.TASK_PS, cpu.getPS()); cpu.setLong(addrOld + X86.TSS386.TASK_EAX, cpu.regEAX); cpu.setLong(addrOld + X86.TSS386.TASK_ECX, cpu.regECX); cpu.setLong(addrOld + X86.TSS386.TASK_EDX, cpu.regEDX); cpu.setLong(addrOld + X86.TSS386.TASK_EBX, cpu.regEBX); cpu.setLong(addrOld + X86.TSS386.TASK_ESP, cpu.getSP()); cpu.setLong(addrOld + X86.TSS386.TASK_EBP, cpu.regEBP); cpu.setLong(addrOld + X86.TSS386.TASK_ESI, cpu.regESI); cpu.setLong(addrOld + X86.TSS386.TASK_EDI, cpu.regEDI); cpu.setLong(addrOld + X86.TSS386.TASK_ES, cpu.segES.sel); cpu.setLong(addrOld + X86.TSS386.TASK_CS, cpu.segCS.sel); cpu.setLong(addrOld + X86.TSS386.TASK_SS, cpu.segSS.sel); cpu.setLong(addrOld + X86.TSS386.TASK_DS, cpu.segDS.sel); /* * 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); cpu.setLong(addrOld + X86.TSS386.TASK_GS, cpu.segGS.sel); /* * Reload all registers from the new TSS; it's important to reload the LDTR sooner * rather than later, so that as segment registers are reloaded, any LDT selectors will * will be located in the correct table. */ X86.helpLoadCR3.call(cpu, cpu.getLong(addrNew + X86.TSS386.TASK_CR3)); cpu.segLDT.load(cpu.getShort(addrNew + X86.TSS386.TASK_LDT)); cpu.setPS(cpu.getLong(addrNew + X86.TSS386.TASK_PS) | (fNest? X86.PS.NT : 0)); cpu.assert(!fNest || !!(cpu.regPS & X86.PS.NT)); cpu.regEAX = cpu.getLong(addrNew + X86.TSS386.TASK_EAX); cpu.regECX = cpu.getLong(addrNew + X86.TSS386.TASK_ECX); cpu.regEDX = cpu.getLong(addrNew + X86.TSS386.TASK_EDX); cpu.regEBX = cpu.getLong(addrNew + X86.TSS386.TASK_EBX); cpu.regEBP = cpu.getLong(addrNew + X86.TSS386.TASK_EBP); cpu.regESI = cpu.getLong(addrNew + X86.TSS386.TASK_ESI); cpu.regEDI = cpu.getLong(addrNew + X86.TSS386.TASK_EDI); cpu.segES.load(cpu.getShort(addrNew + X86.TSS386.TASK_ES)); cpu.segDS.load(cpu.getShort(addrNew + X86.TSS386.TASK_DS)); /* * 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)); cpu.segGS.load(cpu.getShort(addrNew + X86.TSS386.TASK_GS)); cpu.setCSIP(cpu.getLong(addrNew + X86.TSS386.TASK_EIP), cpu.getShort(addrNew + X86.TSS386.TASK_CS)); offSS = X86.TSS386.TASK_SS; offSP = X86.TSS386.TASK_ESP; if (this.cpl < cplOld) { offSP = (this.cpl << 3) + X86.TSS386.CPL0_ESP; offSS = offSP + 4; } cpu.setSS(cpu.getShort(addrNew + offSS), true); cpu.setSP(cpu.getLong(addrNew + offSP)); } /* * Fortunately, X86.TSS286.PREV_TSS and X86.TSS386.PREV_TSS refer to the same TSS offset. */ if (fNest) cpu.setShort(addrNew + X86.TSS286.PREV_TSS, selOld); cpu.regCR0 |= X86.CR0.MSW.TS; return true; } /** * setBase(addr) * * 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. * * For example, in resetRegs(), the real-mode CS selector must be reset to 0xF000 for an 80286 or 80386, * but the CS base must be set to 0x00FF0000 or 0xFFFF0000, respectively. To simplify life for setBase() * callers, we allow them to specify 32-bit bases, which we then truncate to 24 bits as needed. * * WARNING: Since the CPU must maintain regLIP as the sum of the CS base and the current IP, all calls * to segCS.setBase() need to go through cpu.setCSBase(). * * @this {X86Seg} * @param {number} addr * @return {number} addr, truncated as needed */ setBase(addr) { if (this.cpu.model < X86.MODEL_80386) addr &= 0xffffff; return this.base = addr; } /** * save() * * Early versions of PCx86 saved only segment selectors, since that's all that mattered in real-mode; * newer versions need to save/restore all the "core" properties of the X86Seg object (ie, properties other * than those that updateMode() will take care of restoring later). * * @this {X86Seg} * @return {Array} */ save() { return [ this.sel, this.base, this.limit, this.acc, this.id, this.sName, this.cpl, this.dpl, this.addrDesc, this.sizeAddr, this.maskAddr, this.sizeData, this.maskData, this.type, this.offMax ]; } /** * restore(a) * * Early versions of PCx86 saved only segment selectors, since that's all that mattered in real-mode; * newer versions need to save/restore all the "core" properties of the X86Seg object (ie, properties other * than those that updateMode() will take care of restoring later). * * @this {X86Seg} * @param {Array|number} a */ restore(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.id = a[4]; this.sName = a[5]; this.cpl = a[6]; this.dpl = a[7]; this.addrDesc = a[8]; this.sizeAddr = a[9] || 2; this.maskAddr = a[10] || 0xffff; this.sizeData = a[11] || 2; this.maskData = a[12] || 0xffff; this.type = a[13] || (this.acc & X86.DESC.ACC.TYPE.MASK); this.offMax = a[14] || (this.limit >>> 0) + 1; } } /** * updateMode(fLoad, fProt, fV86) * * Ensures that the segment register's access (ie, load and check methods) matches the specified (or current) * operating mode (real or protected). * * @this {X86Seg} * @param {boolean} [fLoad] true if the segment was just (re)loaded, false if not * @param {boolean} [fProt] true for protected-mode access, false for real-mode access, undefined for current mode * @param {boolean} [fV86] true for V86-mode access, false for protected-mode access, undefined for current mode */ updateMode(fLoad, fProt, fV86) { if (fProt === undefined) { fProt = !!(this.cpu.regCR0 & X86.CR0.MSW.PE); } /* * The fExpDown property is used for STACK segments only (ie, segSS); we want to make it easier for * setSS() to set stack lower and upper limits, which requires knowing whether or not the segment is * marked as EXPDOWN. */ this.fExpDown = false; if (fProt) { this.load = this.loadProt; this.loadIDT = this.loadIDTProt; this.checkRead = this.checkReadProt; this.checkWrite = this.checkWriteProt; if (fV86 === undefined) { fV86 = !!(this.cpu.regPS & X86.PS.VM); } if (fV86) { this.load = this.loadV86; this.checkRead = this.checkReadV86; this.checkWrite = this.checkWriteV86; /* * One important feature of V86-mode (as compared to real-mode) are that other segment attributes * (eg, limit, operand size, address size, etc) ARE updated, whereas in real-mode, segment attributes * remain set to whatever was in effect in protected-mode. */ this.cpl = this.dpl = 3; this.sizeData = this.sizeAddr = 2; this.maskData = this.maskAddr = 0xffff; this.limit = 0xffff; this.offMax = this.limit + 1; this.sizeAddr = this.sizeData; this.addrDesc = X86.ADDR_INVALID; this.fStackSwitch = false; return; } /* * TODO: For null GDT selectors, should we rely on the descriptor being invalid, or should we assume that * the null descriptor might contain uninitialized (or other) data? I'm assuming the latter, hence the * following null selector test. However, if we're not going to consult the descriptor, is there anything * else we should (or should not) be doing for null GDT selectors? */ if (!(this.sel & ~X86.SEL.RPL)) { this.checkRead = this.checkReadProtDisallowed; this.checkWrite = this.checkWriteProtDisallowed; } else if (this.type & X86.DESC.ACC.TYPE.SEG) { /* * If the READABLE bit of CODE_READABLE is not set, then disallow reads. */ if ((this.type & X86.DESC.ACC.TYPE.CODE_READABLE) == X86.DESC.ACC.TYPE.CODE_EXECONLY) { this.checkRead = this.checkReadProtDisallowed; } /* * If the CODE bit is set, or the the WRITABLE bit is not set, then disallow writes. */ if ((this.type & X86.DESC.ACC.TYPE.CODE) || !(this.type & X86.DESC.ACC.TYPE.WRITABLE)) { this.checkWrite = this.checkWriteProtDisallowed; } /* * If the CODE bit is not set *and* the EXPDOWN bit is set, then invert the limit check. */ if ((this.type & (X86.DESC.ACC.TYPE.CODE | X86.DESC.ACC.TYPE.EXPDOWN)) == X86.DESC.ACC.TYPE.EXPDOWN) { if (this.checkRead == this.checkReadProt) this.checkRead = this.checkReadProtDown; if (this.checkWrite == this.checkWriteProt) this.checkWrite = this.checkWriteProtDown; this.fExpDown = true; } if (fLoad && this.id < X86Seg.ID.VER) { /* * We must update the descriptor's ACCESSED bit whenever the segment is "accessed" (ie, * loaded); unlike the ACCESSED and DIRTY bits in PTEs, a descriptor ACCESSED bit is only * updated on loads, not on every memory access. * * We compute the address of the descriptor byte containing the ACCESSED bit (offset 0x5); * note that it's perfectly normal for addrDesc to occasionally be invalid (eg, when the CPU * is creating protected-mode-only segment registers like LDT and TSS, or when the CPU has * transitioned from real-mode to protected-mode and new selector(s) have not been loaded yet). * * NOTE: I do NOT update the ACCESSED bit for null GDT selectors, because I'm assuming the * hardware does not update it either. In fact, I've seen code that uses the null GDT descriptor * for other purposes, on the assumption that that descriptor is completely unused. */ if ((this.sel & ~X86.SEL.RPL) && this.addrDesc !== X86.ADDR_INVALID) { var addrType = this.addrDesc + X86.DESC.ACC.TYPE.OFFSET; var bType = this.cpu.getByte(addrType); /* * This code used to ALWAYS call setByte(), but that's a waste of time if ACCESSED is already * set. TODO: It would also be nice if we could simply use the cached type value, and eliminate * the getByte() call; that seems a bit risky, but I think we should still try it someday. */ if (!(bType & (X86.DESC.ACC.TYPE.ACCESSED >> 8))) { this.cpu.setByte(addrType, bType | (X86.DESC.ACC.TYPE.ACCESSED >> 8)); } } } } /* * TODO: For non-SEG descriptors, are there other checks or functions we should establish? */ /* * Any update to the following properties must occur only on segment loads, not simply when * we're updating segment registers as part of a mode change. */ if (fLoad) { this.cpl = this.sel & X86.SEL.RPL; this.dpl = (this.acc & X86.DESC.ACC.DPL.MASK) >> X86.DESC.ACC.DPL.SHIFT; if (this.cpu.model < X86.MODEL_80386 || !(this.ext & X86.DESC.EXT.BIG)) { this.sizeData = 2; this.maskData = 0xffff; } else { this.sizeData = 4; this.maskData = (0xffffffff|0); } this.sizeAddr = this.sizeData; this.maskAddr = this.maskData; } return; } /* * One important feature of real-mode (as compared to V86-mode) are that other segment attributes * (eg, limit, operand size, address size, etc) are NOT updated, enabling features like "big real-mode" * (aka "unreal mode"), which is used by system software like HIMEM.SYS to access extended memory from * real-mode. */ this.load = this.loadReal; this.loadIDT = this.loadIDTReal; this.checkRead = this.checkReadReal; this.checkWrite = this.checkWriteReal; this.cpl = this.dpl = 0; this.addrDesc = X86.ADDR_INVALID; this.fStackSwitch = false; } /** * messageSeg(sel, base, limit, type, ext) * * @this {X86Seg} * @param {number} sel * @param {number} base * @param {number} limit * @param {number} type * @param {number} [ext] */ messageSeg(sel, base, limit, type, ext) { if (DEBUG) { if (DEBUGGER && this.dbg && this.dbg.messageEnabled(Messages.SEG)) { var ch = (this.sName.length < 3? " " : ""); var sDPL = " dpl=" + this.dpl; if (this.id == X86Seg.ID.CODE) sDPL += " cpl=" + this.cpl; this.dbg.message("loadSeg(" + this.sName + "):" + ch + "sel=" + Str.toHexWord(sel) + " base=" + Str.toHex(base) + " limit=" + Str.toHexWord(limit) + " type=" + Str.toHexWord(type) + sDPL, true); } /* * Unless I've got a bug that's causing descriptor corruption, it appears that Windows 3.0 may be setting the * EXT field of descriptors, even when the processor is an 80286; eg, the EXT field below has been set to 0x000F: * * ## ds 1bd * dumpSel(0x01BD): %1101B8 * %001101B8 FFFF C090 B317 000F * * So I've disabled this assert (I had already disabled the "base !== X86.ADDR_INVALID" check). * * this.cpu.assert(base !== X86.ADDR_INVALID && (this.cpu.model >= X86.MODEL_80386 || !ext || ext == X86.DESC.EXT.AVAIL)); */ } } /** * probeDesc(sel) * * This is a neutered version of loadProt() designed for the Debugger. * * @this {X86Seg} * @param {number} sel * @return {number} base address of selected segment, or X86.ADDR_INVALID if error */ probeDesc(sel) { if (DEBUGGER) { var addrDT; var addrDTLimit; var cpu = this.cpu; sel &= 0xffff; if (!(sel & X86.SEL.LDT)) { addrDT = cpu.addrGDT; addrDTLimit = cpu.addrGDTLimit; } else { addrDT = cpu.segLDT.base; addrDTLimit = (addrDT + cpu.segLDT.limit)|0; } var addrDesc = (addrDT + (sel & X86.SEL.MASK))|0; if ((addrDTLimit - addrDesc)|0 >= 7) { /* * Load the descriptor from memory using probeAddr(). */ var limit = cpu.probeAddr(addrDesc + X86.DESC.LIMIT.OFFSET, 2); var acc = cpu.probeAddr(addrDesc + X86.DESC.ACC.OFFSET, 2); var type = (acc & X86.DESC.ACC.TYPE.MASK); var base = cpu.probeAddr(addrDesc + X86.DESC.BASE.OFFSET, 2) | ((acc & X86.DESC.ACC.BASE1623) << 16); var ext = cpu.probeAddr(addrDesc + X86.DESC.EXT.OFFSET, 2); if (I386 && cpu.model >= X86.MODEL_80386) { base |= (ext & X86.DESC.EXT.BASE2431) << 16; limit |= (ext & X86.DESC.EXT.LIMIT1619) << 16; if (ext & X86.DESC.EXT.LIMITPAGES) limit = (limit << 12) | 0xfff; } this.sel = sel; this.base = base; this.limit = limit; this.offMax = (limit >>> 0) + 1; this.acc = acc; this.type = type; this.ext = ext; this.addrDesc = addrDesc; this.updateMode(true, true, false); return base; } } return X86.ADDR_INVALID; } /** * loadAcc(sel, fGDT) * * this {X86Seg} * param {number} sel (protected-mode only) * param {boolean} [fGDT] is true if sel must be in the GDT * return {number} ACC field from descriptor, or X86.DESC.ACC.INVALID if error * loadAcc(sel, fGDT) { var addrDT; var addrDTLimit; var cpu = this.cpu; if (!(sel & X86.SEL.LDT)) { addrDT = cpu.addrGDT; addrDTLimit = cpu.addrGDTLimit; } else if (!fGDT) { addrDT = cpu.segLDT.base; addrDTLimit = (addrDT + cpu.segLDT.limit)|0; } if (addrDT !== undefined) { var addrDesc = (addrDT + (sel & X86.SEL.MASK))|0; if (((addrDTLimit - addrDesc)|0) >= 7) { return cpu.getShort(addrDesc + X86.DESC.ACC.OFFSET); } } X86.helpFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel & X86.ERRCODE.SELMASK); return X86.DESC.ACC.INVALID; } */ } X86Seg.ID = { NULL: 0, // "NULL" CODE: 1, // "CS" DATA: 2, // "DS", "ES", "FS", "GS" STACK: 3, // "SS" TSS: 4, // "TSS" LDT: 5, // "LDT" VER: 6, // "VER" DBG: 7 // "DBG" }; X86Seg.CALLBREAK_SEL = 0x0001; if (NODE) module.exports = X86Seg;