912 lines
36 KiB
JavaScript
912 lines
36 KiB
JavaScript
/**
|
|
* @fileoverview Implements PCjs X86 Segment Registers
|
|
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
|
* @version 1.0
|
|
* Created 2014-Sep-10
|
|
*
|
|
* Copyright © 2012-2015 Jeff Parsons <Jeff@pcjs.org>
|
|
*
|
|
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
|
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/gpl.html>.
|
|
*
|
|
* You are required to include the above copyright notice in every source code file of every
|
|
* copy or modified version of this work, and to display that copyright notice on every screen
|
|
* that loads or runs any version of this software (see Computer.sCopyright).
|
|
*
|
|
* 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 the
|
|
* PCjs program for purposes of the GNU General Public License, and the author does not claim
|
|
* any copyright as to their contents.
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
if (typeof module !== 'undefined') {
|
|
var str = require("../../shared/lib/strlib");
|
|
var Messages = require("./messages");
|
|
var X86 = require("./x86");
|
|
}
|
|
|
|
/**
|
|
* X86Seg(cpu, sName)
|
|
*
|
|
* @constructor
|
|
* @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)
|
|
*/
|
|
function X86Seg(cpu, id, sName, fProt)
|
|
{
|
|
this.cpu = cpu;
|
|
this.dbg = cpu.dbg;
|
|
this.id = id;
|
|
this.sName = sName || "";
|
|
this.sel = 0;
|
|
this.base = 0;
|
|
this.limit = 0xffff;
|
|
this.acc = 0;
|
|
this.ext = 0;
|
|
this.addrDesc = X86.ADDR_INVALID;
|
|
this.cpl = 0;
|
|
this.dpl = 0;
|
|
/*
|
|
* 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);
|
|
* prior to calling segCS.load(), each of those operations must first set segCS.fCall to one of null,
|
|
* true, or false, respectively.
|
|
*
|
|
* It's critical that fCall be properly set prior to calling segCS.load(); fCall === null means NO
|
|
* privilege level transition may occur, fCall === true allows a stack switch and a privilege transition
|
|
* to a numerically lower privilege, and fCall === false allows a stack restore and a privilege transition
|
|
* to a numerically greater privilege.
|
|
*
|
|
* As long as setCSIP() or fnINT() are used for all CS changes, fCall is set automatically.
|
|
*
|
|
* TODO: Consider making fCall a parameter to load(), instead of a property that must be set prior to
|
|
* calling load(); the downside is that such a parameter is meaningless for segments other than segCS.
|
|
*/
|
|
this.awParms = (this.id == X86Seg.ID.CODE? new Array(32) : []);
|
|
this.fCall = null;
|
|
this.fStackSwitch = false;
|
|
/*
|
|
* The following properties are 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;
|
|
this.updateMode(fProt);
|
|
}
|
|
|
|
X86Seg.ID = {
|
|
NULL: 0, // "NULL"
|
|
CODE: 1, // "CS"
|
|
DATA: 2, // "DS", "ES", "FS", "GS"
|
|
STACK: 3, // "SS"
|
|
TSS: 4, // "TSS"
|
|
LDT: 5, // "LDT"
|
|
OTHER: 6, // "VER"
|
|
DEBUG: 7 // "DBG"
|
|
};
|
|
|
|
/**
|
|
* loadReal(sel, fSuppress)
|
|
*
|
|
* The default segment load() function for real-mode.
|
|
*
|
|
* @this {X86Seg}
|
|
* @param {number} sel
|
|
* @param {boolean} [fSuppress] is true to suppress any errors
|
|
* @return {number} base address of selected segment, or ADDR_INVALID if error (TODO: No error conditions exist yet)
|
|
*/
|
|
X86Seg.prototype.loadReal = function loadReal(sel, fSuppress)
|
|
{
|
|
this.sel = sel & 0xffff;
|
|
this.dataSize = this.addrSize = 2;
|
|
this.dataMask = this.addrMask = 0xffff;
|
|
return this.base = this.sel << 4;
|
|
};
|
|
|
|
/**
|
|
* loadProt(sel, fSuppress)
|
|
*
|
|
* 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} [fSuppress] is true to suppress any errors, cycle assessment, etc
|
|
* @return {number} base address of selected segment, or ADDR_INVALID if error
|
|
*/
|
|
X86Seg.prototype.loadProt = function loadProt(sel, fSuppress)
|
|
{
|
|
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;
|
|
}
|
|
/*
|
|
* 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 here. We avoid that grief by 1) relying on the Debugger setting fSuppress to true, and 2) skipping
|
|
* segment lookup if the descriptor table being referenced is zero.
|
|
*
|
|
* TODO: This could probably be simplified to a test of addrDT; however, there's nothing in the design
|
|
* of the CPU that prevents the GDT or LDT being located at physical address zero.
|
|
*/
|
|
if (!fSuppress || addrDT) {
|
|
var addrDesc = addrDT + (sel & X86.SEL.MASK);
|
|
if (addrDesc + 7 <= addrDTLimit) {
|
|
/*
|
|
* TODO: This is only 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.
|
|
*/
|
|
if (!fSuppress) cpu.nStepCycles -= 15;
|
|
return this.loadDesc8(addrDesc, sel, fSuppress);
|
|
}
|
|
if (!fSuppress) {
|
|
X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel);
|
|
}
|
|
}
|
|
return X86.ADDR_INVALID;
|
|
};
|
|
|
|
/**
|
|
* loadIDTReal(nIDT)
|
|
*
|
|
* @this {X86Seg}
|
|
* @param {number} nIDT
|
|
* @return {number} address from selected vector, or ADDR_INVALID if error (TODO: No error conditions exist yet)
|
|
*/
|
|
X86Seg.prototype.loadIDTReal = function 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://localhost:8088/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 ADDR_INVALID if error (TODO: No error conditions exist yet)
|
|
*/
|
|
X86Seg.prototype.loadIDTProt = function loadIDTProt(nIDT)
|
|
{
|
|
var cpu = this.cpu;
|
|
cpu.assert(nIDT >= 0 && nIDT < 256);
|
|
|
|
nIDT <<= 3;
|
|
var addrDesc = cpu.addrIDT + nIDT;
|
|
if (addrDesc + 7 <= cpu.addrIDTLimit) {
|
|
return this.loadDesc8(addrDesc, nIDT) + cpu.regEIP;
|
|
}
|
|
X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, nIDT | X86.ERRCODE.IDT | X86.ERRCODE.EXT, true);
|
|
return X86.ADDR_INVALID;
|
|
};
|
|
|
|
/**
|
|
* checkReadReal(off, cb, fSuppress)
|
|
*
|
|
* TODO: Invoke X86.fnFault.call(this.cpu, X86.EXCEPTION.GP_FAULT) if off is 0xffff and cb is 1;
|
|
* also, whether or not the fnFault() 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 extra bytes to check (0 or 1)
|
|
* @param {boolean} [fSuppress] is true to suppress any errors
|
|
* @return {number} corresponding physical address if valid, or ADDR_INVALID if error (TODO: No error conditions exist yet)
|
|
*/
|
|
X86Seg.prototype.checkReadReal = function checkReadReal(off, cb, fSuppress)
|
|
{
|
|
return (this.base + off)|0;
|
|
};
|
|
|
|
/**
|
|
* checkWriteReal(off, cb, fSuppress)
|
|
*
|
|
* TODO: Invoke X86.fnFault.call(this.cpu, X86.EXCEPTION.GP_FAULT) if off is 0xffff and cb is 1;
|
|
* also, whether or not the fnFault() 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 extra bytes to check (0 or 1)
|
|
* @param {boolean} [fSuppress] is true to suppress any errors
|
|
* @return {number} corresponding physical address if valid, or ADDR_INVALID if error (TODO: No error conditions exist yet)
|
|
*/
|
|
X86Seg.prototype.checkWriteReal = function checkWriteReal(off, cb, fSuppress)
|
|
{
|
|
return (this.base + off)|0;
|
|
};
|
|
|
|
/**
|
|
* checkReadProt(off, cb, fSuppress)
|
|
*
|
|
* @this {X86Seg}
|
|
* @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, or ADDR_INVALID if not
|
|
*/
|
|
X86Seg.prototype.checkReadProt = function checkReadProt(off, cb, fSuppress)
|
|
{
|
|
if (off + cb <= this.limit) {
|
|
return (this.base + off)|0;
|
|
}
|
|
return this.checkReadProtDisallowed(off, cb, fSuppress);
|
|
};
|
|
|
|
/**
|
|
* checkReadProtDown(off, cb, fSuppress)
|
|
*
|
|
* @this {X86Seg}
|
|
* @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, ADDR_INVALID if not
|
|
*/
|
|
X86Seg.prototype.checkReadProtDown = function checkReadProtDown(off, cb, fSuppress)
|
|
{
|
|
if (off + cb > this.limit) {
|
|
return (this.base + off)|0;
|
|
}
|
|
return this.checkReadProtDisallowed(off, cb, fSuppress);
|
|
};
|
|
|
|
/**
|
|
* checkReadProtDisallowed(off, cb, fSuppress)
|
|
*
|
|
* @this {X86Seg}
|
|
* @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, ADDR_INVALID if not
|
|
*/
|
|
X86Seg.prototype.checkReadProtDisallowed = function checkReadProtDisallowed(off, cb, fSuppress)
|
|
{
|
|
if (!fSuppress) {
|
|
X86.fnFault.call(this.cpu, X86.EXCEPTION.GP_FAULT, 0);
|
|
}
|
|
return X86.ADDR_INVALID;
|
|
};
|
|
|
|
/**
|
|
* checkWriteProt(off, cb, fSuppress)
|
|
*
|
|
* @this {X86Seg}
|
|
* @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, ADDR_INVALID if not
|
|
*/
|
|
X86Seg.prototype.checkWriteProt = function checkWriteProt(off, cb, fSuppress)
|
|
{
|
|
if (off + cb <= this.limit) {
|
|
return (this.base + off)|0;
|
|
}
|
|
return this.checkWriteProtDisallowed(off, cb, fSuppress);
|
|
};
|
|
|
|
/**
|
|
* checkWriteProtDown(off, cb, fSuppress)
|
|
*
|
|
* @this {X86Seg}
|
|
* @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, ADDR_INVALID if not
|
|
*/
|
|
X86Seg.prototype.checkWriteProtDown = function checkWriteProtDown(off, cb, fSuppress)
|
|
{
|
|
if (off + cb > this.limit) {
|
|
return (this.base + off)|0;
|
|
}
|
|
return this.checkWriteProtDisallowed(off, cb, fSuppress);
|
|
};
|
|
|
|
/**
|
|
* checkWriteProtDisallowed(off, cb, fSuppress)
|
|
*
|
|
* @this {X86Seg}
|
|
* @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, ADDR_INVALID if not
|
|
*/
|
|
X86Seg.prototype.checkWriteProtDisallowed = function checkWriteProtDisallowed(off, cb, fSuppress)
|
|
{
|
|
if (!fSuppress) {
|
|
X86.fnFault.call(this.cpu, X86.EXCEPTION.GP_FAULT, 0);
|
|
}
|
|
return X86.ADDR_INVALID;
|
|
};
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @this {X86Seg}
|
|
* @param {number} selNew
|
|
* @param {boolean} fNest is true if nesting, false if un-nesting
|
|
* @return {boolean} true if successful, false if error
|
|
*/
|
|
X86Seg.prototype.switchTSS = function switchTSS(selNew, fNest)
|
|
{
|
|
var cpu = this.cpu;
|
|
cpu.assert(this === cpu.segCS);
|
|
|
|
var addrOld = cpu.segTSS.base;
|
|
var cplOld = this.cpl;
|
|
var selOld = cpu.segTSS.sel;
|
|
if (!fNest) {
|
|
if (cpu.segTSS.type != X86.DESC.ACC.TYPE.TSS_BUSY) {
|
|
X86.fnFault.call(cpu, X86.EXCEPTION.TS_FAULT, selNew, true);
|
|
return false;
|
|
}
|
|
cpu.setShort(cpu.segTSS.addrDesc + X86.DESC.ACC.OFFSET, (cpu.segTSS.acc & ~X86.DESC.ACC.TYPE.TSS_BUSY) | X86.DESC.ACC.TYPE.TSS);
|
|
}
|
|
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) {
|
|
if (cpu.segTSS.type == X86.DESC.ACC.TYPE.TSS_BUSY) {
|
|
X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, selNew, true);
|
|
return false;
|
|
}
|
|
cpu.setShort(cpu.segTSS.addrDesc + X86.DESC.ACC.OFFSET, cpu.segTSS.acc |= X86.DESC.ACC.TYPE.TSS_BUSY);
|
|
cpu.segTSS.type = X86.DESC.ACC.TYPE.TSS_BUSY;
|
|
}
|
|
cpu.setShort(addrOld + X86.TSS.TASK_IP, cpu.getIP());
|
|
cpu.setShort(addrOld + X86.TSS.TASK_PS, cpu.getPS());
|
|
cpu.setShort(addrOld + X86.TSS.TASK_AX, cpu.regEAX);
|
|
cpu.setShort(addrOld + X86.TSS.TASK_CX, cpu.regECX);
|
|
cpu.setShort(addrOld + X86.TSS.TASK_DX, cpu.regEDX);
|
|
cpu.setShort(addrOld + X86.TSS.TASK_BX, cpu.regEBX);
|
|
cpu.setShort(addrOld + X86.TSS.TASK_SP, cpu.getSP());
|
|
cpu.setShort(addrOld + X86.TSS.TASK_BP, cpu.regEBP);
|
|
cpu.setShort(addrOld + X86.TSS.TASK_SI, cpu.regESI);
|
|
cpu.setShort(addrOld + X86.TSS.TASK_DI, cpu.regEDI);
|
|
cpu.setShort(addrOld + X86.TSS.TASK_ES, cpu.segES.sel);
|
|
cpu.setShort(addrOld + X86.TSS.TASK_CS, cpu.segCS.sel);
|
|
cpu.setShort(addrOld + X86.TSS.TASK_SS, cpu.segSS.sel);
|
|
cpu.setShort(addrOld + X86.TSS.TASK_DS, cpu.segDS.sel);
|
|
var offSS = X86.TSS.TASK_SS;
|
|
var offSP = X86.TSS.TASK_SP;
|
|
cpu.setPS(cpu.getShort(addrNew + X86.TSS.TASK_PS) | (fNest? X86.PS.NT : 0));
|
|
cpu.assert(!fNest || !!(cpu.regPS & X86.PS.NT));
|
|
cpu.regEAX = cpu.getShort(addrNew + X86.TSS.TASK_AX);
|
|
cpu.regECX = cpu.getShort(addrNew + X86.TSS.TASK_CX);
|
|
cpu.regEDX = cpu.getShort(addrNew + X86.TSS.TASK_DX);
|
|
cpu.regEBX = cpu.getShort(addrNew + X86.TSS.TASK_BX);
|
|
cpu.regEBP = cpu.getShort(addrNew + X86.TSS.TASK_BP);
|
|
cpu.regESI = cpu.getShort(addrNew + X86.TSS.TASK_SI);
|
|
cpu.regEDI = cpu.getShort(addrNew + X86.TSS.TASK_DI);
|
|
cpu.segES.load(cpu.getShort(addrNew + X86.TSS.TASK_ES));
|
|
cpu.segDS.load(cpu.getShort(addrNew + X86.TSS.TASK_DS));
|
|
cpu.setCSIP(cpu.getShort(addrNew + X86.TSS.TASK_IP), cpu.getShort(addrNew + X86.TSS.TASK_CS));
|
|
if (this.cpl < cplOld) {
|
|
offSP = (this.cpl << 2) + X86.TSS.CPL0_SP;
|
|
offSS = offSP + 2;
|
|
}
|
|
cpu.setSS(cpu.getShort(addrNew + offSS), true);
|
|
cpu.setSP(cpu.getShort(addrNew + offSP));
|
|
cpu.segLDT.load(cpu.getShort(addrNew + X86.TSS.TASK_LDT));
|
|
if (fNest) cpu.setShort(addrNew + X86.TSS.PREV_TSS, selOld);
|
|
cpu.regCR0 |= X86.CR0.MSW.TS;
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
X86Seg.prototype.loadAcc = function(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;
|
|
}
|
|
if (addrDT !== undefined) {
|
|
var addrDesc = addrDT + (sel & X86.SEL.MASK);
|
|
if (addrDesc + 7 <= addrDTLimit) {
|
|
return cpu.getShort(addrDesc + X86.DESC.ACC.OFFSET);
|
|
}
|
|
}
|
|
X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel);
|
|
return X86.DESC.ACC.INVALID;
|
|
};
|
|
|
|
/**
|
|
* loadDesc6(addrDesc, sel)
|
|
*
|
|
* Used to load a protected-mode selector that refers to a 6-byte "descriptor cache" (aka LOADALL) entry:
|
|
*
|
|
* 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
|
|
*/
|
|
X86Seg.prototype.loadDesc6 = function(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.acc = acc & X86.DESC.ACC.MASK;
|
|
this.type = (acc & X86.DESC.ACC.TYPE.MASK);
|
|
this.ext = 0;
|
|
this.addrDesc = addrDesc;
|
|
this.updateMode();
|
|
|
|
this.messageSeg(sel, base, limit, acc);
|
|
|
|
return base;
|
|
};
|
|
|
|
/**
|
|
* loadDesc8(addrDesc, sel, fSuppress)
|
|
*
|
|
* 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.
|
|
*
|
|
* @this {X86Seg}
|
|
* @param {number} addrDesc is the descriptor address
|
|
* @param {number} sel is the associated selector, or nIDT*8 if IDT descriptor
|
|
* @param {boolean} [fSuppress] is true to suppress any errors, cycle assessment, etc
|
|
* @return {number} base address of selected segment, or ADDR_INVALID if error
|
|
*/
|
|
X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fSuppress)
|
|
{
|
|
var cpu = this.cpu;
|
|
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) {
|
|
base |= (ext & X86.DESC.EXT.BASE2431) << 16;
|
|
limit |= (ext & X86.DESC.EXT.LIMIT1619) << 16;
|
|
if (ext & X86.DESC.EXT.GRANULARITY) limit = (limit << 12) | 0xfff;
|
|
}
|
|
|
|
while (true) {
|
|
|
|
var selCode, cplPrev, addrTSS, offSP, offSS, regSPPrev, regSSPrev;
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
if (this.id == X86Seg.ID.CODE) {
|
|
this.fStackSwitch = false;
|
|
var fCall = this.fCall;
|
|
var fGate, regPSMask, nFaultError, regSP;
|
|
var rpl = sel & X86.SEL.RPL;
|
|
var dpl = (acc & X86.DESC.ACC.DPL.MASK) >> X86.DESC.ACC.DPL.SHIFT;
|
|
/*
|
|
* Since we are X86Seg.ID.CODE, we can use this.cpl instead of the more generic cpu.segCS.cpl
|
|
*/
|
|
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 || (acc & X86.DESC.ACC.TYPE.CONFORMING) && dpl <= this.cpl)) {
|
|
base = X86.ADDR_INVALID;
|
|
break;
|
|
}
|
|
regSP = cpu.popWord();
|
|
cpu.setSS(cpu.popWord(), true);
|
|
cpu.setSP(regSP);
|
|
this.fStackSwitch = true;
|
|
}
|
|
fGate = false;
|
|
}
|
|
else if (type == X86.DESC.ACC.TYPE.GATE_CALL) {
|
|
fGate = true;
|
|
regPSMask = ~0;
|
|
nFaultError = sel;
|
|
if (rpl < this.cpl) rpl = this.cpl; // set RPL to max(RPL,CPL) for call gates
|
|
}
|
|
else if (type == X86.DESC.ACC.TYPE.GATE_INT) {
|
|
fGate = true;
|
|
regPSMask = ~(X86.PS.NT | X86.PS.TF | X86.PS.IF);
|
|
nFaultError = sel | X86.ERRCODE.EXT;
|
|
cpu.assert(!(acc & 0x1f));
|
|
}
|
|
else if (type == X86.DESC.ACC.TYPE.GATE_TRAP) {
|
|
fGate = true;
|
|
regPSMask = ~(X86.PS.NT | X86.PS.TF);
|
|
nFaultError = sel | X86.ERRCODE.EXT;
|
|
cpu.assert(!(acc & 0x1f));
|
|
}
|
|
else if (type == X86.DESC.ACC.TYPE.GATE_TASK) {
|
|
if (!this.switchTSS(base & 0xffff, true)) {
|
|
base = 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).
|
|
*/
|
|
selCode = base & 0xffff;
|
|
if (rpl <= dpl) {
|
|
/*
|
|
* TODO: Verify the PRESENT bit of the gate descriptor, and issue NP_FAULT as appropriate.
|
|
*/
|
|
cplPrev = this.cpl;
|
|
if (this.load(selCode, true) === X86.ADDR_INVALID) {
|
|
cpu.assert(false);
|
|
base = X86.ADDR_INVALID;
|
|
break;
|
|
}
|
|
cpu.regEIP = limit;
|
|
if (this.cpl < cplPrev) {
|
|
if (fCall !== true) {
|
|
cpu.assert(false);
|
|
base = X86.ADDR_INVALID;
|
|
break;
|
|
}
|
|
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;
|
|
offSP = (this.cpl << 2) + X86.TSS.CPL0_SP;
|
|
offSS = offSP + 2;
|
|
regSSPrev = cpu.getSS();
|
|
regSPPrev = cpu.getSP();
|
|
cpu.setSS(cpu.getShort(addrTSS + offSS), true);
|
|
cpu.setSP(cpu.getShort(addrTSS + offSP));
|
|
cpu.pushWord(regSSPrev);
|
|
cpu.pushWord(regSPPrev);
|
|
while (i) cpu.pushWord(this.awParms[--i]);
|
|
this.fStackSwitch = true;
|
|
}
|
|
cpu.regPS &= regPSMask;
|
|
return this.base;
|
|
}
|
|
cpu.assert(false);
|
|
if (!fSuppress) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, nFaultError, true);
|
|
base = X86.ADDR_INVALID;
|
|
break;
|
|
}
|
|
else if (fGate !== false) {
|
|
cpu.assert(false);
|
|
if (!fSuppress) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel, true);
|
|
base = X86.ADDR_INVALID;
|
|
break;
|
|
}
|
|
}
|
|
else if (this.id == X86Seg.ID.DATA) {
|
|
if (selMasked) {
|
|
if (type < X86.DESC.ACC.TYPE.DATA_READONLY || (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; eg:
|
|
*
|
|
* 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.
|
|
*
|
|
* Anyway, because of this, if acc is zero, we won't set fHalt on this GP_FAULT.
|
|
*/
|
|
if (!fSuppress) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel, !!acc);
|
|
base = X86.ADDR_INVALID;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (this.id == X86Seg.ID.STACK) {
|
|
if (!selMasked || type < X86.DESC.ACC.TYPE.DATA_READONLY || (type & (X86.DESC.ACC.TYPE.CODE | X86.DESC.ACC.TYPE.READABLE)) == X86.DESC.ACC.TYPE.CODE) {
|
|
if (!fSuppress) X86.fnFault.call(cpu, X86.EXCEPTION.GP_FAULT, sel, true);
|
|
base = X86.ADDR_INVALID;
|
|
break;
|
|
}
|
|
}
|
|
else if (this.id == X86Seg.ID.TSS) {
|
|
if (!selMasked || type != X86.DESC.ACC.TYPE.TSS && type != X86.DESC.ACC.TYPE.TSS_BUSY) {
|
|
if (!fSuppress) X86.fnFault.call(cpu, X86.EXCEPTION.TS_FAULT, sel, true);
|
|
base = X86.ADDR_INVALID;
|
|
break;
|
|
}
|
|
}
|
|
else if (this.id == X86Seg.ID.OTHER) {
|
|
/*
|
|
* For LSL, we must support any descriptor marked X86.DESC.ACC.TYPE.SEG, as well as TSS and LDT descriptors.
|
|
*/
|
|
if (!(acc & X86.DESC.ACC.TYPE.SEG) && type > X86.DESC.ACC.TYPE.TSS_BUSY) {
|
|
base = X86.ADDR_INVALID;
|
|
break;
|
|
}
|
|
}
|
|
this.sel = sel;
|
|
this.base = base;
|
|
this.limit = limit;
|
|
this.acc = acc;
|
|
this.type = type;
|
|
this.ext = ext;
|
|
this.addrDesc = addrDesc;
|
|
this.updateMode();
|
|
break;
|
|
}
|
|
if (!fSuppress) this.messageSeg(sel, base, limit, acc, ext);
|
|
return base;
|
|
};
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @this {X86Seg}
|
|
* @param {number} addr
|
|
* @return {number} addr, truncated as needed
|
|
*/
|
|
X86Seg.prototype.setBase = function(addr)
|
|
{
|
|
if (this.cpu.model < X86.MODEL_80386) addr &= 0xffffff;
|
|
return this.base = addr;
|
|
};
|
|
|
|
/**
|
|
* save()
|
|
*
|
|
* Early versions of PCjs saved only segment selectors, since that's all that mattered in real-mode;
|
|
* newer versions need to save/restore all the "defining" properties of the X86Seg object.
|
|
*
|
|
* @this {X86Seg}
|
|
* @return {Array}
|
|
*/
|
|
X86Seg.prototype.save = function()
|
|
{
|
|
return [this.sel, this.base, this.limit, this.acc, this.id, this.sName, this.cpl, this.dpl, this.addrDesc];
|
|
};
|
|
|
|
/**
|
|
* restore(a)
|
|
*
|
|
* Early versions of PCjs saved only segment selectors, since that's all that mattered in real-mode;
|
|
* newer versions need to save/restore all the "defining" properties of the X86Seg object.
|
|
*
|
|
* @this {X86Seg}
|
|
* @param {Array|number} a
|
|
*/
|
|
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.id = a[4];
|
|
this.sName = a[5];
|
|
this.cpl = a[6];
|
|
this.dpl = a[7];
|
|
this.addrDesc = a[8];
|
|
}
|
|
};
|
|
|
|
/**
|
|
* updateMode(fProt)
|
|
*
|
|
* 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} [fProt] true for protected-mode access, false for real-mode access, undefined for current mode
|
|
* @return {boolean}
|
|
*/
|
|
X86Seg.prototype.updateMode = function(fProt)
|
|
{
|
|
if (fProt === undefined) {
|
|
fProt = !!(this.cpu.regCR0 & X86.CR0.MSW.PE);
|
|
}
|
|
this.fExpDown = false;
|
|
if (fProt) {
|
|
this.load = this.loadProt;
|
|
this.loadIDT = this.loadIDTProt;
|
|
this.checkRead = this.checkReadProt;
|
|
this.checkWrite = this.checkWriteProt;
|
|
if (this.acc & X86.DESC.ACC.TYPE.SEG) {
|
|
/*
|
|
* If the READABLE bit of CODE_READABLE is not set, then disallow reads
|
|
*/
|
|
if ((this.acc & X86.DESC.ACC.TYPE.CODE_READABLE) == X86.DESC.ACC.TYPE.CODE_EXECONLY) {
|
|
this.checkWrite = this.checkReadProtDisallowed;
|
|
}
|
|
/*
|
|
* If the CODE bit is set, or the the WRITABLE bit is not set, then disallow writes
|
|
*/
|
|
if ((this.acc & X86.DESC.ACC.TYPE.CODE) || !(this.acc & 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.acc & (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;
|
|
}
|
|
}
|
|
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.addrSize = 2;
|
|
this.addrMask = 0xffff;
|
|
} else {
|
|
this.addrSize = 4;
|
|
this.addrMask = (0xffffffff|0);
|
|
}
|
|
} else {
|
|
this.load = this.loadReal;
|
|
this.loadIDT = this.loadIDTReal;
|
|
this.checkRead = this.checkReadReal;
|
|
this.checkWrite = this.checkWriteReal;
|
|
this.limit = 0xffff;
|
|
this.cpl = this.dpl = 0;
|
|
this.addrDesc = X86.ADDR_INVALID;
|
|
this.addrSize = 2;
|
|
this.addrMask = 0xffff;
|
|
}
|
|
this.dataSize = this.addrSize;
|
|
this.dataMask = this.addrMask;
|
|
return fProt;
|
|
};
|
|
|
|
/**
|
|
* messageSeg(sel, base, limit, acc, ext)
|
|
*
|
|
* @this {X86Seg}
|
|
* @param {number} sel
|
|
* @param {number} base
|
|
* @param {number} limit
|
|
* @param {number} acc
|
|
* @param {number} [ext]
|
|
*/
|
|
X86Seg.prototype.messageSeg = function(sel, base, limit, acc, 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) + " acc=" + str.toHexWord(acc) + sDPL);
|
|
}
|
|
this.cpu.assert(/* base !== X86.ADDR_INVALID && */ (this.cpu.model >= X86.MODEL_80386 || !ext || ext == X86.DESC.EXT.AVAIL));
|
|
}
|
|
};
|
|
|
|
if (typeof module !== 'undefined') module.exports = X86Seg;
|