v1.19.6: Improved restartability of stack-related instructions

This commit is contained in:
Jeff Parsons 2015-09-14 16:53:57 -07:00
commit deeac4902d
174 changed files with 9030 additions and 1736 deletions

View file

@ -294,6 +294,7 @@ var BusInfo;
Bus.prototype.initMemory = function()
{
var block = new Memory();
block.copyBreakpoints(this.dbg);
this.aMemBlocks = new Array(this.nBlockTotal);
for (var iBlock = 0; iBlock < this.nBlockTotal; iBlock++) {
this.aMemBlocks[iBlock] = block;
@ -402,7 +403,7 @@ Bus.prototype.addMemory = function(addr, size, type, controller)
}
var blockOld = this.aMemBlocks[iBlock];
var blockNew = new Memory(addr, sizeBlock, this.nBlockSize, type, controller);
blockNew.copyBreakpoints(blockOld, this.dbg);
blockNew.copyBreakpoints(this.dbg, blockOld);
this.aMemBlocks[iBlock++] = blockNew;
addr = addrBlock + this.nBlockSize;
size -= sizeBlock;
@ -585,7 +586,7 @@ Bus.prototype.removeMemory = function(addr, size)
while (size > 0) {
var blockOld = this.aMemBlocks[iBlock];
var blockNew = new Memory(addr);
blockNew.copyBreakpoints(blockOld, this.dbg);
blockNew.copyBreakpoints(this.dbg, blockOld);
this.aMemBlocks[iBlock++] = blockNew;
addr = iBlock * this.nBlockSize;
size -= this.nBlockSize;
@ -976,6 +977,24 @@ Bus.prototype.addBackTrackObject = function(obj, bto, off)
* this.assert(slot < cbtObjects);
*/
}
/*
* I hit the following error after running in a machine with lots of disk activity:
*
* Error: assertion failure in deskpro386.bus
* at Bus.Component.assert (http://pcjs:8088/modules/shared/lib/component.js:732:31)
* at Bus.addBackTrackObject (http://pcjs:8088/modules/pcjs/lib/bus.js:980:18)
* at onATCReadData (http://pcjs:8088/modules/pcjs/lib/hdc.js:1410:35)
* at HDC.readData (http://pcjs:8088/modules/pcjs/lib/hdc.js:2573:23)
* at HDC.inATCByte (http://pcjs:8088/modules/pcjs/lib/hdc.js:1398:20)
* at HDC.inATCData (http://pcjs:8088/modules/pcjs/lib/hdc.js:1487:17)
* at Bus.checkPortInputNotify (http://pcjs:8088/modules/pcjs/lib/bus.js:1457:38)
* at X86CPU.INSw (http://pcjs:8088/modules/pcjs/lib/x86ops.js:1640:26)
* at X86CPU.stepCPU (http://pcjs:8088/modules/pcjs/lib/x86cpu.js:4637:37)
* at X86CPU.CPU.runCPU (http://pcjs:8088/modules/pcjs/lib/cpu.js:1014:22)
*
* TODO: Investigate. For now, BACKTRACK is completely disabled (in part because it also needs
* to be revamped for machines with paging enabled).
*/
this.assert(slot < Bus.BACKTRACK.SLOT_MAX);
this.ibtLastAlloc = slot;
bto.slot = slot + 1;
@ -1338,36 +1357,6 @@ Bus.prototype.restoreMemory = function(a)
return true;
};
/**
* addMemBreak(addr, fWrite)
*
* @this {Bus}
* @param {number} addr
* @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint
*/
Bus.prototype.addMemBreak = function(addr, fWrite)
{
if (DEBUGGER) {
var iBlock = addr >>> this.nBlockShift;
this.aMemBlocks[iBlock].addBreakpoint(addr & this.nBlockLimit, fWrite);
}
};
/**
* removeMemBreak(addr, fWrite)
*
* @this {Bus}
* @param {number} addr
* @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint
*/
Bus.prototype.removeMemBreak = function(addr, fWrite)
{
if (DEBUGGER) {
var iBlock = addr >>> this.nBlockShift;
this.aMemBlocks[iBlock].removeBreakpoint(addr & this.nBlockLimit, fWrite);
}
};
/**
* addPortInputBreak(port)
*

View file

@ -155,14 +155,18 @@ function Debugger(parmsDbg)
this.dbgAddrAssemble = this.newAddr();
/*
* aSymbolTable is an array of 5-element arrays, one per ROM or other chunk of address space.
* Each 5-element arrays contains:
* aSymbolTable is an array of SymbolTable objects, one per ROM or other chunk of address space.
*
* [0]: sel
* [1]: addr
* [2]: len
* [3]: aSymbols
* [4]: aOffsetPairs
* Each SymbolTable object contains the following properties:
*
* sModule
* nSegment
* sel
* off
* addr (physical address, if any; eg, if symbols are for a ROM)
* len
* aSymbols
* aOffsetPairs
*
* See addSymbols() for more details, since that's how callers add sets of symbols to the table.
*/
@ -1288,28 +1292,47 @@ if (DEBUGGER) {
if (Interrupts.WINDBG.ENABLED || Interrupts.WINDBGRM.ENABLED) {
/**
* addSegmentInfo(dbgAddr, nSeg, sel, fCode, fPrint)
* addSegmentInfo(dbgAddr, nSegment, sel, fCode, fPrint)
*
* @this {Debugger}
* @param {DbgAddr} dbgAddr (address of module name)
* @param {number} nSeg (logical segment number)
* @param {number} nSegment (logical segment number)
* @param {number} sel (current selector)
* @param {boolean} fCode (true if code segment, false if data segment)
* @param {boolean} [fPrint]
*/
Debugger.prototype.addSegmentInfo = function(dbgAddr, nSeg, sel, fCode, fPrint)
Debugger.prototype.addSegmentInfo = function(dbgAddr, nSegment, sel, fCode, fPrint)
{
var sModule = this.getSZ(dbgAddr);
var seg = this.getSegment(sel);
var len = seg? seg.limit + 1 : 0;
var sSection = (fCode? "_CODE" : "_DATA") + str.toHex(nSeg, 2);
var sSection = (fCode? "_CODE" : "_DATA") + str.toHex(nSegment, 2);
if (fPrint) {
this.println(sModule + "!undefined " + (fCode? "code" : "data") + '(' + str.toHex(nSeg, 4) + ")=#" + str.toHex(sel, 4) + " len " + str.toHex(len));
this.println(sModule + ' ' + (fCode? "code" : "data") + '(' + str.toHex(nSegment, 4) + ")=#" + str.toHex(sel, 4) + " len " + str.toHex(len));
}
var aSymbols = {};
var off = 0;
var aSymbols = this.findModuleInfo(sModule, nSegment);
aSymbols[sModule + sSection] = off;
this.addSymbols(sel, off, len, aSymbols);
this.addSymbols(sModule, nSegment, sel, off, null, len, aSymbols);
};
/**
* removeSegmentInfo(sel, fPrint)
*
* @this {Debugger}
* @param {number} sel
* @param {boolean} [fPrint]
*/
Debugger.prototype.removeSegmentInfo = function(sel, fPrint)
{
var sModuleRemoved = this.removeSymbols(null, sel);
if (fPrint) {
if (sModuleRemoved) {
this.println(sModuleRemoved + " #" + str.toHex(sel, 4) + " removed");
} else {
this.println("unable to remove module for segment #" + str.toHex(sel, 4));
}
}
};
/**
@ -1320,8 +1343,8 @@ if (DEBUGGER) {
* DD_actual_sel dw ? ; actual selector value
* DD_base dd ? ; linear address offset for start of segment
* DD_length dd ? ; actual length of segment
* DD_name df ? ; 16:32 ptr to null terminated device name
* DD_sym_name df ? ; 16:32 ptr to null terminated symbolic module name (i.e. Win386)
* DD_name df ? ; 16:32 ptr to null terminated module name
* DD_sym_name df ? ; 16:32 ptr to null terminated parent name (eg, "DOS386")
* DD_alias_sel dw ? ; alias selector value (0 = none)
*
* @this {Debugger}
@ -1331,34 +1354,63 @@ if (DEBUGGER) {
*/
Debugger.prototype.addSectionInfo = function(dbgAddr, fCode, fPrint)
{
var nSeg = this.getShort(dbgAddr, 2);
var nSegment = this.getShort(dbgAddr, 2);
var sel = this.getShort(dbgAddr, 2);
var off = this.getLong(dbgAddr, 4);
var len = this.getLong(dbgAddr, 4);
var dbgAddrDevice = this.newAddr(this.getLong(dbgAddr, 4), this.getShort(dbgAddr, 2));
var dbgAddrModule = this.newAddr(this.getLong(dbgAddr, 4), this.getShort(dbgAddr, 2));
var dbgAddrParent = this.newAddr(this.getLong(dbgAddr, 4), this.getShort(dbgAddr, 2));
// sel = this.getShort(dbgAddr, 2) || sel;
var sParent = this.getSZ(dbgAddrParent).toUpperCase();
var sModule = this.getSZ(dbgAddrModule).toUpperCase();
var sDevice = this.getSZ(dbgAddrDevice).toUpperCase();
var sSection = (fCode? "_CODE" : "_DATA") + str.toHex(nSeg, 2);
if (sParent == sModule) {
sParent = "";
} else {
sParent += '!';
}
var sSection = (fCode? "_CODE" : "_DATA") + str.toHex(nSegment, 2);
if (fPrint) {
/*
* Mimics WDEB386 output, except that WDEB386 only displays a linear address, omitting the selector.
*/
this.println(sModule + '!' + sDevice + "!undefined " + (fCode? "code" : "data") + '(' + str.toHex(nSeg, 4) + ")=" + str.toHex(sel, 4) + ':' + str.toHex(off) + " len " + str.toHex(len));
this.println(sParent + sModule + ' ' + (fCode? "code" : "data") + '(' + str.toHex(nSegment, 4) + ")=" + str.toHex(sel, 4) + ':' + str.toHex(off) + " len " + str.toHex(len));
}
/*
* TODO: Add support for 32-bit symbols; findModuleInfo() relies on Disk.getModuleInfo(), and the Disk
* component doesn't yet know how to parse 32-bit executables.
*/
var aSymbols = this.findModuleInfo(sModule, nSegment);
aSymbols[sModule + sSection] = off;
this.addSymbols(sModule, nSegment, sel, off, null, len, aSymbols);
};
/**
* removeSectionInfo(nSegment, dbgAddr, fPrint)
*
* @this {Debugger}
* @param {DbgAddr} dbgAddr (address of module)
* @param {boolean} [fPrint]
*/
Debugger.prototype.removeSectionInfo = function(nSegment, dbgAddr, fPrint)
{
var sModule = this.getSZ(dbgAddr).toUpperCase();
var sModuleRemoved = this.removeSymbols(sModule, nSegment);
if (fPrint) {
if (sModuleRemoved) {
this.println(sModule + ' ' + str.toHex(nSegment, 4) + " removed");
} else {
this.println("unable to remove " + sModule + " for section " + str.toHex(nSegment, 4));
}
}
var aSymbols = {};
aSymbols[sDevice + sSection] = off;
this.addSymbols(sel, off, len, aSymbols);
};
/**
* intWindowsCallBack()
*
* This intercepts calls to Windows callback addresses, which use INT 0x30 (aka Transfer Space Fault).
* This intercepts calls to Windows callback addresses, which use INT 0x30 (aka Transfer Space Faults).
*
* We're only interested in one particular callback: the one specifying VW32_Int41Dispatch (0x002A002A)
* in EAX.
* We're only interested in one particular callback: the VW32_Int41Dispatch (0x002A002A) that KERNEL32
* issues as 32-bit executable sections are loaded.
*
* At the time that INT 0x30 occurs, a far 32-bit call has been made, preceded by a near 32-bit call,
* preceded by a 32-bit push of the Windows Debugger function # that would normally be in EAX if this had
@ -1369,7 +1421,12 @@ if (DEBUGGER) {
* instruction following an INT 0x30; in fact, execution doesn't even continue after the far 32-bit call
* (even though the kernel places a "RET 4" after that call). So, rather than recreate all that automatic
* address popping, we let the system do it for us, since it's designed to work whether a debugger (eg,
* WDEB386's Debug VXD) is installed or not.
* WDEB386's DEBUG VxD) is installed or not.
*
* TODO: Consider "consuming" all VW32_Int41Dispatch callbacks, because the Windows 95 kernel goes to
* great effort to pass those requests on to the DEBUG VxD, which end up going nowhere when the VxD isn't
* loaded (to load it, you must either run WDEB386.EXE or install it via your SYSTEM.INI). Regrettably,
* Windows 95 assumes that if WDEB386 support is present, then a DEBUG VxD must be present as well.
*
* @this {Debugger}
* @param {number} addr
@ -1387,7 +1444,7 @@ if (DEBUGGER) {
var EAX = this.getLong(dbgAddr);
switch(EAX) {
case Interrupts.WINDBG.LOAD_SEG32:
case Interrupts.WINDBG.LOADSEG32:
/*
* SI == segment type:
* 0x0 code selector
@ -1462,14 +1519,60 @@ if (DEBUGGER) {
case Interrupts.WINDBG.IS_LOADED:
if (this.fWinDbg) {
cpu.regEAX = (cpu.regEAX & ~0xffff) | Interrupts.WINDBG.LOADED;
this.println("INT 0x41 handling enabled");
}
break;
case Interrupts.WINDBG.LOAD_SEG:
case Interrupts.WINDBG.LOADSEG:
this.addSegmentInfo(this.newAddr(DI, ES), BX+1, CX, !(SI & 0x1), this.fWinDbg);
break;
case Interrupts.WINDBG.LOAD_SEG32:
case Interrupts.WINDBG.FREESEG:
this.removeSegmentInfo(BX);
break;
case Interrupts.WINDBG.KRNLVARS:
/*
* BX = version number of this data (0x3A0)
* DX:CX points to:
* WORD hGlobalHeap ****
* WORD pGlobalHeap ****
* WORD hExeHead ****
* WORD hExeSweep
* WORD topPDB
* WORD headPDB
* WORD topsizePDB
* WORD headTDB ****
* WORD curTDB ****
* WORD loadTDB
* WORD LockTDB
* WORD SelTableLen ****
* DWORD SelTableStart ****
*/
break;
case Interrupts.WINDBG.RELSEG:
case Interrupts.WINDBG.LOADDLL:
case Interrupts.WINDBG.DELMODULE:
/*
* TODO: Figure out what to do with these notifications, if anything
*/
break;
case Interrupts.WINDBG.REGDOTCMD:
case Interrupts.WINDBG.CONDBP:
case Interrupts.WINDBG.LOADHIGH:
break;
case Interrupts.WINDBG.GETSYMBOL:
if (this.fWinDbg) cpu.regEAX = (cpu.regEAX & ~0xffff)|1; // AX == 1 means not found
break;
case Interrupts.WINDBG.CHECKFAULT:
if (this.fWinDbg) cpu.regEAX = (cpu.regEAX & ~0xffff)|0; // AX == 0 means handle fault normally
break;
case Interrupts.WINDBG.LOADSEG32:
/*
* SI == segment type:
* 0x0 code selector
@ -1479,8 +1582,16 @@ if (DEBUGGER) {
this.addSectionInfo(this.newAddr(cpu.regEBX, DX), !SI, this.fWinDbg);
break;
case Interrupts.WINDBG.FREESEG32:
/*
* BX == segment number
* DX:EDI -> module name
*/
this.removeSectionInfo(BX, this.newAddr(cpu.regEDI, DX));
break;
default:
if (MAXDEBUG && this.fWinDbg) {
if (DEBUG && this.fWinDbg) {
this.println("INT 0x41: " + str.toHexWord(AX));
}
break;
@ -1506,6 +1617,9 @@ if (DEBUGGER) {
var cpu = this.cpu;
var AL = cpu.regEAX & 0xff;
var AH = (cpu.regEAX >> 8) & 0xff;
var BX = cpu.regEBX & 0xffff;
var CX = cpu.regECX & 0xffff;
var DX = cpu.regEDX & 0xffff;
var DI = cpu.regEDI & 0xffff;
var ES = cpu.segES.sel;
@ -1573,13 +1687,25 @@ if (DEBUGGER) {
}
break;
case Interrupts.WINDBGRM.LOAD_SEG:
case Interrupts.WINDBGRM.FREESEG:
this.removeSegmentInfo(BX);
break;
case Interrupts.WINDBGRM.REMOVESEGS:
/*
* TODO: This probably just signals the end of module loading; nothing is required, but we should
* clean up whatever we can....
*/
break;
case Interrupts.WINDBGRM.LOADSEG:
if (AL == 0x20) {
/*
* Real-mode EXE
* CX == paragraph
* ES:DI -> module name
*/
this.addSegmentInfo(this.newAddr(DI, ES), 0, CX, true, this.fWinDbgRM);
}
else if (AL < 0x80) {
/*
@ -1595,6 +1721,7 @@ if (DEBUGGER) {
* DX == actual selector (if 0x40 or 0x41)
* ES:DI -> module name
*/
this.addSegmentInfo(this.newAddr(DI, ES), BX+1, (AL & 0x40)? DX : CX, !(AL & 0x1), this.fWinDbgRM);
}
else {
/*
@ -1611,7 +1738,7 @@ if (DEBUGGER) {
break;
default:
if (MAXDEBUG && this.fWinDbgRM) {
if (DEBUG && this.fWinDbgRM) {
this.println("INT 0x68: " + str.toHexByte(AH));
}
break;
@ -2776,6 +2903,30 @@ if (DEBUGGER) {
this.println(sDump);
};
/**
* findModuleInfo(sModule, nSegment)
*
* Since we're not sure what Disk the module was loaded from, we have to check all of them.
*
* @this {Debugger}
* @param {string} sModule
* @param {number} nSegment
* @return {Array}
*/
Debugger.prototype.findModuleInfo = function(sModule, nSegment)
{
var aSymbols = [];
if (SYMBOLS) {
var component, componentPrev = null;
while (component = this.cmp.getComponentByType("Disk", componentPrev)) {
aSymbols = component.getModuleInfo(sModule, nSegment);
if (aSymbols.length) break;
componentPrev = component;
}
}
return aSymbols;
};
/**
* messageInit(sEnable)
*
@ -3841,15 +3992,21 @@ if (DEBUGGER) {
{
var i;
this.aBreakExec = ["bp"];
/*
* TODO: Each read breakpoint needs to keep track of whether it's linear or physical.
*/
if (this.aBreakRead !== undefined) {
for (i = 1; i < this.aBreakRead.length; i++) {
this.bus.removeMemBreak(this.getAddr(this.aBreakRead[i]), false);
this.cpu.removeMemBreak(this.getAddr(this.aBreakRead[i]), false, true);
}
}
this.aBreakRead = ["br"];
/*
* TODO: Each write breakpoint needs to keep track of whether it's linear or physical.
*/
if (this.aBreakWrite !== undefined) {
for (i = 1; i < this.aBreakWrite.length; i++) {
this.bus.removeMemBreak(this.getAddr(this.aBreakWrite[i]), true);
this.cpu.removeMemBreak(this.getAddr(this.aBreakWrite[i]), true, true);
}
}
this.aBreakWrite = ["bw"];
@ -3939,7 +4096,10 @@ if (DEBUGGER) {
this.println("invalid address: " + this.hexAddr(dbgAddr));
fSuccess = false;
} else {
this.bus.addMemBreak(addr, aBreak == this.aBreakWrite);
/*
* TODO: Add some UI that allows a physical address (fLinear is currently hard-coded to true)
*/
this.cpu.addMemBreak(addr, aBreak == this.aBreakWrite, true);
/*
* Force memory breakpoints to use their linear address, by zapping the selector.
*/
@ -3998,7 +4158,10 @@ if (DEBUGGER) {
}
aBreak.splice(i, 1);
if (aBreak != this.aBreakExec) {
this.bus.removeMemBreak(addr, aBreak == this.aBreakWrite);
/*
* TODO: Add some UI that allows a physical address (fLinear is currently hard-coded to true)
*/
this.cpu.removeMemBreak(addr, aBreak == this.aBreakWrite, true);
}
this.historyInit();
break;
@ -5175,7 +5338,7 @@ if (DEBUGGER) {
};
/**
* addSymbols(sel, addr, len, aSymbols)
* addSymbols(sModule, nSegment, sel, off, addr, len, aSymbols)
*
* As filedump.js (formerly convrom.php) explains, aSymbols is a JSON-encoded object whose properties consist
* of all the symbols (in upper-case), and the values of those properties are objects containing any or all of
@ -5231,7 +5394,7 @@ if (DEBUGGER) {
* We add all these entries to our internal symbol table, which is an array of 4-element arrays, each of which
* look like:
*
* [sel, addr, len, aSymbols, aOffsetPairs]
* [sel, off, addr, len, aSymbols, aOffsetPairs]
*
* There are two basic symbol operations: findSymbol(), which takes an address and finds the symbol, if any,
* at that address, and findSymbolAddr(), which takes a string and attempts to match it to a non-anonymous
@ -5246,12 +5409,15 @@ if (DEBUGGER) {
* properly.
*
* @this {Debugger}
* @param {string|null} sModule
* @param {number} nSegment (zero if undefined)
* @param {number} sel (the default segment/selector for all symbols in this group)
* @param {number} addr (the physical address of the region where the given symbols are located)
* @param {number} off (from the base of the given selector)
* @param {number|null} addr (physical address where the symbols are located, if the memory is physical; eg, ROM)
* @param {number} len (the size of the region, in bytes)
* @param {Object} aSymbols (collection of symbols in this group; the format of this collection is described below)
*/
Debugger.prototype.addSymbols = function(sel, addr, len, aSymbols)
Debugger.prototype.addSymbols = function(sModule, nSegment, sel, off, addr, len, aSymbols)
{
var dbgAddr = {};
var aOffsetPairs = [];
@ -5260,12 +5426,12 @@ if (DEBUGGER) {
if (typeof symbol == "number") {
aSymbols[sSymbol] = symbol = {'o': symbol};
}
var off = symbol['o'];
var offSymbol = symbol['o'];
var selSymbol = symbol['s'];
var sAnnotation = symbol['a'];
if (off !== undefined) {
if (offSymbol !== undefined) {
if (selSymbol !== undefined) {
dbgAddr.off = off;
dbgAddr.off = offSymbol;
dbgAddr.sel = selSymbol;
dbgAddr.addr = null;
/*
@ -5282,11 +5448,44 @@ if (DEBUGGER) {
}
symbol['p'] = dbgAddr.addr;
}
usr.binaryInsert(aOffsetPairs, [off >>> 0, sSymbol], this.comparePairs);
usr.binaryInsert(aOffsetPairs, [offSymbol >>> 0, sSymbol], this.comparePairs);
}
if (sAnnotation) symbol['a'] = sAnnotation.replace(/''/g, "\"");
}
this.aSymbolTable.push([sel, addr, len, aSymbols, aOffsetPairs]);
var symbolTable = {
sModule: sModule,
nSegment: nSegment,
sel: sel,
off: off,
addr: addr,
len: len,
aSymbols: aSymbols,
aOffsetPairs: aOffsetPairs
};
this.aSymbolTable.push(symbolTable);
};
/**
* removeSymbols(sModule, nSegment)
*
* @this {Debugger}
* @param {string|null} sModule
* @param {number} [nSegment] (segment # if sModule set, selector if sModule clear)
* @return {string|null} name of the module removed, or null if no module was found
*/
Debugger.prototype.removeSymbols = function(sModule, nSegment)
{
var sModuleRemoved = null;
for (var iTable = 0; iTable < this.aSymbolTable.length; iTable++) {
var symbolTable = this.aSymbolTable[iTable];
if (sModule && symbolTable.sModule != sModule) continue;
if (sModule && nSegment == symbolTable.nSegment || !sModule && nSegment == symbolTable.sel) {
sModuleRemoved = symbolTable.sModule;
this.aSymbolTable.splice(iTable, 1);
break;
}
}
return sModuleRemoved;
};
/**
@ -5299,21 +5498,18 @@ if (DEBUGGER) {
*/
Debugger.prototype.dumpSymbols = function()
{
for (var i = 0; i < this.aSymbolTable.length; i++) {
var sel = this.aSymbolTable[i][0];
var addr = this.aSymbolTable[i][1];
//var len = this.aSymbolTable[i][2];
var aSymbols = this.aSymbolTable[i][3];
for (var sSymbol in aSymbols) {
for (var iTable = 0; iTable < this.aSymbolTable.length; iTable++) {
var symbolTable = this.aSymbolTable[iTable];
for (var sSymbol in symbolTable.aSymbols) {
if (sSymbol.charAt(0) == '.') continue;
var symbol = aSymbols[sSymbol];
var off = symbol['o'];
if (off === undefined) continue;
var selSym = symbol['s'];
if (selSym === undefined) selSym = sel;
var sSymbolOrig = aSymbols[sSymbol]['l'];
var symbol = symbolTable.aSymbols[sSymbol];
var offSymbol = symbol['o'];
if (offSymbol === undefined) continue;
var selSymbol = symbol['s'];
if (selSymbol === undefined) selSymbol = symbolTable.sel;
var sSymbolOrig = symbolTable.aSymbols[sSymbol]['l'];
if (sSymbolOrig) sSymbol = sSymbolOrig;
this.println(this.hexOffset(off, selSym) + ' ' + sSymbol);
this.println(this.hexOffset(offSymbol, selSymbol) + ' ' + sSymbol);
}
}
};
@ -5334,26 +5530,27 @@ if (DEBUGGER) {
Debugger.prototype.findSymbol = function(dbgAddr, fNearest)
{
var aSymbol = [];
var offSymbol = dbgAddr.off >>> 0;
var addrSymbol = this.getAddr(dbgAddr) >>> 0;
for (var iTable = 0; iTable < this.aSymbolTable.length; iTable++) {
var sel = this.aSymbolTable[iTable][0];
var addr = this.aSymbolTable[iTable][1] >>> 0;
var len = this.aSymbolTable[iTable][2];
if (sel == dbgAddr.sel || (sel == 0x28 || sel == 0x30) && (dbgAddr.sel == 0x28 || dbgAddr.sel == 0x30)) {
if (addrSymbol >= addr && addrSymbol < addr + len) {
var off = dbgAddr.off >>> 0;
var aOffsetPairs = this.aSymbolTable[iTable][4];
var result = usr.binarySearch(aOffsetPairs, [off], this.comparePairs);
if (result >= 0) {
this.returnSymbol(iTable, result, aSymbol);
}
else if (fNearest) {
result = ~result;
this.returnSymbol(iTable, result-1, aSymbol);
this.returnSymbol(iTable, result, aSymbol);
}
break;
var symbolTable = this.aSymbolTable[iTable];
var sel = symbolTable.sel;
var off = symbolTable.off >>> 0;
var addr = symbolTable.addr;
if (addr != null) addr >>>= 0;
var len = symbolTable.len;
if (sel == 0x30) sel = 0x28; // TODO: Remove this hack once we're able to differentiate Windows 95 ring 0 code and data
if (sel == dbgAddr.sel && offSymbol >= off && offSymbol < off + len || addr != null && addrSymbol >= addr && addrSymbol < addr + len) {
var result = usr.binarySearch(symbolTable.aOffsetPairs, [offSymbol], this.comparePairs);
if (result >= 0) {
this.returnSymbol(iTable, result, aSymbol);
}
else if (fNearest) {
result = ~result;
this.returnSymbol(iTable, result-1, aSymbol);
this.returnSymbol(iTable, result, aSymbol);
}
break;
}
}
if (!aSymbol.length) {
@ -5380,24 +5577,21 @@ if (DEBUGGER) {
var dbgAddr;
if (sSymbol.match(/^[a-z_][a-z0-9_]*$/i)) {
var sUpperCase = sSymbol.toUpperCase();
for (var i = 0; i < this.aSymbolTable.length; i++) {
var sel = this.aSymbolTable[i][0];
var addr = this.aSymbolTable[i][1];
//var len = this.aSymbolTable[i][2];
var aSymbols = this.aSymbolTable[i][3];
var symbol = aSymbols[sUpperCase];
for (var iTable = 0; iTable < this.aSymbolTable.length; iTable++) {
var symbolTable = this.aSymbolTable[iTable];
var symbol = symbolTable.aSymbols[sUpperCase];
if (symbol !== undefined) {
var off = symbol['o'];
if (off !== undefined) {
var offSymbol = symbol['o'];
if (offSymbol !== undefined) {
/*
* We assume that every ROM is ORG'ed at 0x0000, and therefore unless the symbol has an
* explicitly-defined segment, we return the segment associated with the entire group; for
* a ROM, that segment is normally "addrROM >>> 4". Down the road, we may want/need to
* support a special symbol entry (eg, ".ORG") that defines an alternate origin.
*/
var selSym = symbol['s'];
if (selSym === undefined) selSym = sel;
dbgAddr = this.newAddr(off, selSym, symbol['p']);
var selSymbol = symbol['s'];
if (selSymbol === undefined) selSymbol = symbolTable.sel;
dbgAddr = this.newAddr(offSymbol, selSymbol, symbol['p']);
}
/*
* The symbol matched, but it wasn't for an address (no 'o' offset), and there's no point
@ -5422,14 +5616,14 @@ if (DEBUGGER) {
Debugger.prototype.returnSymbol = function(iTable, iOffset, aSymbol)
{
var symbol = {};
var aOffsetPairs = this.aSymbolTable[iTable][4];
var aOffsetPairs = this.aSymbolTable[iTable].aOffsetPairs;
var offset = 0, sSymbol = null;
if (iOffset >= 0 && iOffset < aOffsetPairs.length) {
offset = aOffsetPairs[iOffset][0];
sSymbol = aOffsetPairs[iOffset][1];
}
if (sSymbol) {
symbol = this.aSymbolTable[iTable][3][sSymbol];
symbol = this.aSymbolTable[iTable].aSymbols[sSymbol];
sSymbol = (sSymbol.charAt(0) == '.'? null : (symbol['l'] || sSymbol));
}
aSymbol.push(sSymbol);

View file

@ -89,8 +89,19 @@ var TYPEDARRAYS = (typeof ArrayBuffer !== 'undefined');
* match the DEBUGGER setting -- unless it slows down machines using the built-in Debugger too much, in which case
* we'll have to rethink that choice OR provide a Debugger command that dynamically enables/disables as much of
* the backtracking support as possible.
*
* TODO: BACKTRACK support is currently completely disabled until we have a chance to investigate the problem
* discussed in Bus.addBackTrackObject().
*/
var BACKTRACK = !COMPILED;
var BACKTRACK = false;
/**
* @define {boolean}
*
* SYMBOLS enables automatic symbol generation from known DLL, EXE and VXD file formats. It's currently
* enabled whenever DEBUGGER support is enabled.
*/
var SYMBOLS = DEBUGGER;
/**
* @define {boolean}
@ -150,6 +161,7 @@ if (typeof module !== 'undefined') {
global.FATARRAYS = FATARRAYS;
global.TYPEDARRAYS = TYPEDARRAYS;
global.BACKTRACK = BACKTRACK;
global.SYMBOLS = SYMBOLS;
global.SAMPLER = SAMPLER;
global.BUGS_8086 = BUGS_8086;
global.I386 = I386;

View file

@ -265,6 +265,13 @@ Component.subclass(Disk);
* where offset is relative to the segment's offStart value, and symbol is a string describing the
* entry.
*
* NOTE: Although aEntries uses a format similar to the Debugger's aOffsetPairs, they are not
* interchangeable data structures, because ours is ordered by ordinal, whereas aOffsetPairs is
* ordered by offset. We provide an interface, getModuleInfo(), to the Debugger that converts
* our data into an intermediate array, aSymbols, which the Debugger then uses to build aOffsetPairs.
* It would be nice to avoid building that intermediate representation, but it's a side-effect of
* the Debugger's earlier support for JSON-encoded MAP files.
*
* There will always be an offset at index 0 of an aEntries[] element, but some error or incomplete
* symbolic information could result in a missing symbol at index 1, because symbol name processing is
* separate from entry table processing.
@ -707,8 +714,8 @@ FileInfo.prototype.getSymbol = function(off, fNearest)
* To support fNearest, save the entry where (off - entry[0]) yields the smallest positive result.
*/
var cbNearest = off, entryNearest;
for (var iEntry in segment.aEntries) {
var entry = segment.aEntries[iEntry];
for (var iOrdinal in segment.aEntries) {
var entry = segment.aEntries[iOrdinal];
var cb = off - entry[0];
if (!cb) {
sSymbol = this.sModule + '!' + entry[1];
@ -1153,7 +1160,7 @@ Disk.prototype.doneLoad = function(sDiskFile, sDiskData, nErrorCode, sDiskPath)
this.printMessage('doneLoad("' + sDiskFile + '","' + sDiskPath + '")');
}
this.fRemote = true;
if (BACKTRACK) this.buildFileTable();
if (BACKTRACK || SYMBOLS) this.buildFileTable();
disk = this;
} else {
this.controller.notice('Unable to connect to disk "' + sDiskPath + '" (error ' + nErrorCode + ': ' + sDiskData + ')', fPrintOnly);
@ -1343,7 +1350,7 @@ Disk.prototype.doneLoad = function(sDiskFile, sDiskData, nErrorCode, sDiskPath)
}
this.aDiskData = aDiskData;
this.dwChecksum = dwChecksum;
if (BACKTRACK) this.buildFileTable();
if (BACKTRACK || SYMBOLS) this.buildFileTable();
disk = this;
}
} catch (e) {
@ -1362,9 +1369,8 @@ Disk.prototype.doneLoad = function(sDiskFile, sDiskData, nErrorCode, sDiskPath)
*
* This function builds (or rebuilds) a complete file table from the (first) FAT volume found on the current
* disk, and then updates all the sector objects to point back to the corresponding file. Used for BACKTRACK
* support, and like BACKTRACK support, this is an expensive operation, in terms of both time and memory, so
* it should only be called when a disk is mounted or has been modified (eg, by applying deltas from a saved
* machine state).
* and SYMBOLS support. Because this is an expensive operation, in terms of both time and memory, it should
* only be called when a disk is mounted or has been modified (eg, by applying deltas from a saved machine state).
*
* More recently, the FileInfo objects in the table have been enhanced to include debugging information if
* the file is an EXE or DLL, which we determine merely by checking the file extension.
@ -1382,7 +1388,7 @@ Disk.prototype.doneLoad = function(sDiskFile, sDiskData, nErrorCode, sDiskPath)
*/
Disk.prototype.buildFileTable = function()
{
if (BACKTRACK) {
if (BACKTRACK || SYMBOLS) {
var i, off, dir = {}, iSector;
@ -1563,6 +1569,38 @@ Disk.prototype.buildFileTable = function()
return this.aFileTable;
};
/**
* getModuleInfo(sModule, nSegment)
*
* If the given module and segment number is found, we return an Array of symbol offsets, indexed by symbol name.
*
* @this {Disk}
* @param {string} sModule
* @param {number} nSegment
* @return {Array}
*/
Disk.prototype.getModuleInfo = function(sModule, nSegment)
{
var aSymbols = [];
if (SYMBOLS && this.aFileTable) {
for (var iFile = 0; iFile < this.aFileTable.length; iFile++) {
var file = this.aFileTable[iFile];
if (file.sModule != sModule) continue;
var segment = file.aSegments[nSegment];
if (!segment) continue;
for (var iOrdinal in segment.aEntries) {
var entry = segment.aEntries[iOrdinal];
/*
* entry[1] is the symbol name, which becomes the index, and entry[0] is the offset.
*/
aSymbols[entry[1]] = entry[0];
}
break;
}
}
return aSymbols;
};
/**
* getSymbolInfo(sSymbol)
*
@ -1579,14 +1617,14 @@ Disk.prototype.buildFileTable = function()
Disk.prototype.getSymbolInfo = function(sSymbol)
{
var aInfo = [];
if (this.aFileTable) {
if (SYMBOLS && this.aFileTable) {
var sSymbolUpper = sSymbol.toUpperCase();
for (var iFile = 0; iFile < this.aFileTable.length; iFile++) {
var file = this.aFileTable[iFile];
for (var iSegment in file.aSegments) {
var segment = file.aSegments[iSegment];
for (var iEntry in segment.aEntries) {
var entry = segment.aEntries[iEntry];
for (var iOrdinal in segment.aEntries) {
var entry = segment.aEntries[iOrdinal];
if (entry[1] && entry[1].indexOf(sSymbolUpper) >= 0) {
aInfo.push([entry[1], file.sName, iSegment, entry[0], segment.offEnd - segment.offStart]);
}
@ -2641,9 +2679,9 @@ Disk.prototype.restore = function(deltas)
this.printMessage('restore("' + this.sDiskName + '"): restored ' + nChanges + ' change(s)');
}
/*
* Last but not least, rebuild the disk's file table if BACKTRACK support is enabled.
* Last but not least, rebuild the disk's file table if BACKTRACK or SYMBOLS support is enabled.
*/
if (BACKTRACK) this.buildFileTable();
if (BACKTRACK || SYMBOLS) this.buildFileTable();
}
return nChanges;
};

View file

@ -68,8 +68,27 @@ var Interrupts = {
VECTOR: 0x41, // (AX==command)
IS_LOADED: 0x004F, // DS_DebLoaded
LOADED: 0xF386, // DS_DebPresent (returned in AX if Windows Debugger loaded)
LOAD_SEG: 0x0050, // DS_LoadSeg (SI==0 if code, 1 if data; BX==segnum-1; CX==selector; ES:[E]DI->module name)
LOAD_SEG32: 0x0150, // DS_LoadSeg_32 (SI==0 if code, 1 if data; DX:EBX->D386_Device_Params)
LOADSEG: 0x0050, // DS_LoadSeg (SI==0 if code, 1 if data; BX==segnum-1; CX==selector; ES:[E]DI->module name)
FREESEG: 0x0052, // DS_FreeSeg (BX==segment)
KRNLVARS: 0x005A, // DS_Kernel_Vars
RELSEG: 0x005C, // DS_ReleaseSeg (same as DS_FreeSeg but "restores any breakpoints first")
LOADHIGH: 0x005D, // D386_LoadCodeDataHigh
LOADDLL: 0x0064, // DS_LOADDLL
DELMODULE: 0x0065, // DS_DELMODULE
REGDOTCMD: 0x0070, // DS_RegisterDotCommand
CHECKFAULT: 0x007F, // DS_CheckFault (BX==fault #, CX==fault type; return AX=0 to handle fault normally)
TRAPFAULT: 0x0083, // DS_TrapFault (BX==fault #, CX==faulting CS, EDX==faulting EIP, ESI==fault error, EDI==fault flags)
FAULTTYPE: {
V86: 0x0001,
PM: 0x0002,
RING0: 0x0004,
FIRST: 0x0008,
LAST: 0x0010
},
GETSYMBOL: 0x008D, // DS_GetSymbol (DS:ESI->symbol; return AX=0 if success, 1 if not found, 2 if memory not loaded yet)
LOADSEG32: 0x0150, // DS_LoadSeg_32 (SI==0 if code, 1 if data; DX:EBX->D386_Device_Params)
FREESEG32: 0x0152, // DS_FreeSeg_32 (BX==segment, DX:EDI->module name)
CONDBP: 0xF001, // DS_CondBP (break here if WDEB386 was run with /B; ESI -> string to display)
ENABLED: true // support for WINDBG interrupts can be disabled (but NOT if WINDBGRM is enabled)
},
WINDBGRM: { // Windows Debugger real-mode interface
@ -77,7 +96,9 @@ var Interrupts = {
IS_LOADED: 0x43, // D386_Identify
LOADED: 0xF386, // D386_Id (returned in AX if Windows Debugger loaded)
PREP_PMODE: 0x44, // D386_Prepare_PMode (must return a 16:32 address in ES:EDI to a "PMinit" handler)
LOAD_SEG: 0x50, // D386_Load_Segment (AL=segment type, ES:DI->D386_Device_Params)
FREESEG: 0x48, // D386_Free_Segment (BX==real-mode segment)
REMOVESEGS: 0x4F, // D386_Remove_Segs (remove any undefined segments from the named module at ES:DI)
LOADSEG: 0x50, // D386_Load_Segment (AL=segment type, ES:DI->D386_Device_Params)
ENABLED: true // support for WINDBGRM interrupts can be disabled
},
FUNCS: {} // filled in only if DEBUGGER is true

View file

@ -108,10 +108,10 @@ function Memory(addr, used, size, type, controller, cpu)
this.type = type || Memory.TYPE.NONE;
this.fReadOnly = (type == Memory.TYPE.ROM);
this.controller = null;
this.cpu = cpu; // If a CPU reference is provided, then this must be an UNPAGED Memory block allocation
this.cpu = cpu; // if a CPU reference is provided, then this must be an UNPAGED Memory block allocation
this.fDirty = this.fDirtyEver = false;
this.cReadBreakpoints = this.cWriteBreakpoints = 0;
this.setPhysBlock();
this.copyBreakpoints(); // initialize the block's Debugger info (eg, breakpoint totals); the caller will reinitialize
if (BACKTRACK) {
if (!size || controller) {
@ -281,7 +281,6 @@ Memory.prototype = {
this.type = type;
this.fReadOnly = (type == Memory.TYPE.ROM);
}
this.dbg = dbg;
if (TYPEDARRAYS) {
this.buffer = mem.buffer;
this.dv = mem.dv;
@ -297,6 +296,7 @@ Memory.prototype = {
}
this.setAccess(Memory.afnMemory);
}
this.copyBreakpoints(dbg, mem);
},
/**
* save()
@ -579,15 +579,16 @@ Memory.prototype = {
}
},
/**
* copyBreakpoints(mem)
* copyBreakpoints(dbg, mem)
*
* @this {Memory}
* @param {Memory|undefined} mem (outgoing Memory block to copy breakpoints from, if any)
* @param {Debugger} [dbg]
* @param {Memory} [mem] (outgoing Memory block to copy breakpoints from, if any)
*/
copyBreakpoints: function(mem, dbg) {
copyBreakpoints: function(dbg, mem) {
this.dbg = dbg;
this.cReadBreakpoints = this.cWriteBreakpoints = 0;
if (mem) {
if (dbg) this.dbg = dbg;
if (mem.cpu) this.cpu = mem.cpu;
if ((this.cReadBreakpoints = mem.cReadBreakpoints)) {
this.setReadAccess(Memory.afnChecked, false);

View file

@ -182,7 +182,7 @@ ROM.prototype.powerUp = function(data, fRepower)
{
if (this.aSymbols) {
if (this.dbg) {
this.dbg.addSymbols(this.addrROM >>> 4, this.addrROM, this.sizeROM, this.aSymbols);
this.dbg.addSymbols(this.id, 0, this.addrROM >>> 4, 0, this.addrROM, this.sizeROM, this.aSymbols);
}
/*
* Our only role in the handling of symbols is to hand them off to the Debugger at our

View file

@ -5966,7 +5966,7 @@ Video.prototype.outATC = function(port, bOut, addrFrom)
}
else {
/*
* TODO: We need a screen blanking function, suitable for any mode, when INDX_PAL_ENABLE transitions off.
* TODO: We might want a screen blanking function, suitable for any mode, when INDX_PAL_ENABLE is cleared.
* powerDown() might like to use such a function, too. updateScreen() already disables any further screen
* updates while INDX_PAL_ENABLE is clear (except when fForce is true), but that's all we currently do.
*
@ -5984,6 +5984,9 @@ Video.prototype.outATC = function(port, bOut, addrFrom)
* C000:2B43 EE OUT DX,AL
* C000:2B44 B020 MOV AL,20
* C000:2B46 EE OUT DX,AL <-- this ATC index value obviously DOES contain 0x20
*
* I'm not sure there are any situations where deliberately flickering the screen is a good thing -- unless
* someone REALLY wants to recreate the ugly flickering scroll of a CGA...?
*/
}
/*

View file

@ -680,6 +680,81 @@ X86CPU.prototype.setAddressMask = function(nBusMask)
this.nBusMask = this.nMemMask = nBusMask;
};
/**
* addMemBreak(addr, fWrite, fLinear)
*
* NOTE: addMemBreak() could be merged with addMemCheck(), but the new merged interface would
* have to provide one additional parameter indicating whether the Debugger or the CPU is the client.
*
* @this {X86CPU}
* @param {number} addr
* @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint
* @param {boolean} [fLinear] (true for linear breakpoint, false for physical)
*/
X86CPU.prototype.addMemBreak = function(addr, fWrite, fLinear)
{
if (DEBUGGER) {
var iBlock = addr >>> this.nBlockShift;
var aBlocks = (fLinear? this.aMemBlocks : this.aBusBlocks);
aBlocks[iBlock].addBreakpoint(addr & this.nBlockLimit, fWrite);
}
};
/**
* removeMemBreak(addr, fWrite, fLinear)
*
* NOTE: removeMemBreak() could be merged with removeMemCheck(), but the new merged interface would
* have to provide one additional parameter indicating whether the Debugger or the CPU is the client.
*
* @this {X86CPU}
* @param {number} addr
* @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint
* @param {boolean} [fLinear] (true for linear breakpoint, false for physical)
*/
X86CPU.prototype.removeMemBreak = function(addr, fWrite, fLinear)
{
if (DEBUGGER) {
var iBlock = addr >>> this.nBlockShift;
var aBlocks = (fLinear? this.aMemBlocks : this.aBusBlocks);
aBlocks[iBlock].removeBreakpoint(addr & this.nBlockLimit, fWrite);
}
};
/**
* addMemCheck(addr, fWrite)
*
* These functions provide Debug register functionality to the CPU by leveraging the same Memory block-based
* breakpoint support originally created for our built-in Debugger. Only minimal changes were required to the
* Memory component, by adding additional checkMemoryException() call-outs from the "checked" Memory access
* functions.
*
* Note that those call-outs occur only AFTER our own Debugger (if present) has checked the address and has
* passed on it, because we want our own Debugger's breakpoints to take precedence over any breakpoints that
* the emulated machine may have enabled.
*
* @this {X86CPU}
* @param {number} addr
* @param {boolean} fWrite is true for a memory write check, false for a memory read check
*/
X86CPU.prototype.addMemCheck = function(addr, fWrite)
{
var iBlock = addr >>> this.nBlockShift;
this.aMemBlocks[iBlock].addBreakpoint(addr & this.nBlockLimit, fWrite, this);
};
/**
* removeMemCheck(addr, fWrite)
*
* @this {X86CPU}
* @param {number} addr
* @param {boolean} fWrite is true for a memory write check, false for a memory read check
*/
X86CPU.prototype.removeMemCheck = function(addr, fWrite)
{
var iBlock = addr >>> this.nBlockShift;
this.aMemBlocks[iBlock].removeBreakpoint(addr & this.nBlockLimit, fWrite);
};
/**
* enablePageBlocks()
*
@ -705,7 +780,28 @@ X86CPU.prototype.enablePageBlocks = function()
}
if (this.aMemBlocks === this.aBusBlocks) {
this.aMemBlocks = new Array(this.nBlockTotal);
/*
* TODO: Currently we allocate only one UNPAGED block for the entire linear address space;
* only when a block is touched and becomes PAGED do we allocate a dedicated Memory block
* for that slot. One potential downside to using a single UNPAGED block, however, is that
* it will accumulate all breakpoints for all UNPAGED blocks, requiring copyBreakpoints() to
* do extra work to figure out which breakpoints should be copied (ie, removed) from the
* outgoing block -- which it can't currently do, because blocks only keep track of the total
* number of breakpoints, not the actual breakpoint addresses.
*
* So, Memory blocks either need to start maintaining their own breakpoint address lists,
* or we need to allocate separate (empty) UNPAGED blocks for every slot. I've not tackled
* this yet, because it's largely just a debugging issue.
*
* Notice that when we call copyBreakpoints() here, it's merely to initialize the new block;
* we make no attempt to copy any breakpoints from physical blocks to linear blocks, although
* perhaps we should. The plan for our Debugger is to maintain separate physical and linear
* breakpoint address lists, but what about CPU Debug registers? If the CPU sets the Debug
* registers, then enables paging, do all the previous Debug register addresses automatically
* become linear addresses? I'm guessing they do.
*/
this.blockUnpaged = new Memory(null, 0, 0, Memory.TYPE.UNPAGED, null, this);
this.blockUnpaged.copyBreakpoints(this.dbg);
for (var iBlock = 0; iBlock < this.nBlockTotal; iBlock++) {
this.aMemBlocks[iBlock] = this.blockUnpaged;
}
@ -803,6 +899,9 @@ X86CPU.prototype.mapPageBlock = function(addr, fWrite, fSuppress)
var blockPhys = this.aBusBlocks[(addrPhys & this.nBusMask) >>> this.nBlockShift];
if (fSuppress) return blockPhys;
var iBlock = addr >>> this.nBlockShift;
var block = this.aMemBlocks[iBlock];
/*
* So we have the block containing the physical memory corresponding to the given linear address.
*
@ -811,9 +910,10 @@ X86CPU.prototype.mapPageBlock = function(addr, fWrite, fSuppress)
var addrPage = addr & ~X86.LADDR.OFFSET;
var blockPage = new Memory(addrPage, 0, 0, Memory.TYPE.PAGED);
blockPage.setPhysBlock(blockPhys, blockPDE, offPDE, blockPTE, offPTE);
blockPage.copyBreakpoints(this.dbg, block);
var iBlock = addr >>> this.nBlockShift;
this.aMemBlocks[iBlock] = blockPage;
this.aBlocksPaged.push(iBlock);
return blockPage;
};
@ -1716,40 +1816,6 @@ X86CPU.prototype.checkIntReturn = function(addr)
}
};
/**
* addMemCheck(addr, fWrite)
*
* These functions provide Debug register functionality by leveraging the same Memory block-based breakpoint
* support originally created for our built-in Debugger. Only minimal changes were required to the Memory
* component, by adding additional checkMemoryException() call-outs from the "checked" Memory access functions.
*
* Note that those call-outs occur only AFTER our own Debugger (if present) has checked the address and has
* passed on it, because we want our own Debugger's breakpoints to take precedence over any breakpoints that
* the emulated machine may have enabled.
*
* @this {X86CPU}
* @param {number} addr
* @param {boolean} fWrite is true for a memory write check, false for a memory read check
*/
X86CPU.prototype.addMemCheck = function(addr, fWrite)
{
var iBlock = addr >>> this.nBlockShift;
this.aMemBlocks[iBlock].addBreakpoint(addr & this.nBlockLimit, fWrite, this);
};
/**
* removeMemCheck(addr, fWrite)
*
* @this {X86CPU}
* @param {number} addr
* @param {boolean} fWrite is true for a memory write check, false for a memory read check
*/
X86CPU.prototype.removeMemCheck = function(addr, fWrite)
{
var iBlock = addr >>> this.nBlockShift;
this.aMemBlocks[iBlock].removeBreakpoint(addr & this.nBlockLimit, fWrite);
};
/**
* checkDebugRegisters(fEnable)
*
@ -3938,7 +4004,9 @@ X86CPU.prototype.getSIBAddr = function(mod)
X86CPU.prototype.popWord = function()
{
var w = this.getWord(this.regLSP);
this.regLSP = (this.regLSP + (I386? this.sizeData : 2))|0;
/*
* Properly comparing regLSP to regLSPLimit would normally require coercing both to unsigned
* (ie, floating-point) values. But instead, we do a subtraction, (regLSPLimit - regLSP), and
@ -3974,29 +4042,39 @@ X86CPU.prototype.popWord = function()
X86CPU.prototype.pushData = function(d, size)
{
this.assert(size == 2 || size == 4);
this.regLSP = (this.regLSP - size)|0;
var regLSP = (this.regLSP - size)|0;
/*
* Properly comparing regLSP to regLSPLimitLow would normally require coercing both to unsigned
* (ie, floating-point) values. But instead, we do a subtraction, (regLSP - regLSPLimitLow), and
* if the result is negative, we need only be concerned if the signs of both numbers are the same
* (ie, the sign of their XOR'ed union is positive).
*/
if (((this.regLSP - this.regLSPLimitLow)|0) < 0 && (this.regLSPLimitLow ^ this.regLSP) >= 0) {
if (((regLSP - this.regLSPLimitLow)|0) < 0 && (this.regLSPLimitLow ^ regLSP) >= 0) {
/*
* There's no such thing as an SS fault on the 8086/8088, and I'm assuming that, on newer
* processors, when the stack segment limit is set to the maximum, it's OK for the stack to wrap.
*/
if (this.model <= X86.MODEL_8088 || !this.segSS.fExpDown && this.segSS.limit == this.segSS.maskAddr || this.segSS.fExpDown && !this.segSS.limit) {
this.setSP((this.regLSP - this.segSS.base) & this.segSS.maskAddr);
this.setSP((regLSP - this.segSS.base) & this.segSS.maskAddr);
regLSP = this.regLSP;
} else {
X86.fnFault.call(this, X86.EXCEPTION.SS_FAULT, 0);
}
}
if (size == 2) {
this.setShort(this.regLSP, d);
this.setShort(regLSP, d);
} else {
this.setLong(this.regLSP, d);
this.setLong(regLSP, d);
}
/*
* We update this.regLSP at the end to make life simpler for opcode handlers that perform only one
* pushWord() operation, relieving them from having to snapshot this.regLSP into this.opLSP needlessly.
*/
this.regLSP = regLSP;
};
/**
@ -4017,25 +4095,34 @@ X86CPU.prototype.pushWord = function(w)
* setWord() calls setShort() or setLong() as appropriate, and setShort() truncates incoming values, so the fact
* that any incoming signed values will not be truncated to 16 bits should not be a concern.
*/
this.regLSP = (this.regLSP - (I386? this.sizeData : 2))|0;
var regLSP = (this.regLSP - (I386? this.sizeData : 2))|0;
/*
* Properly comparing regLSP to regLSPLimitLow would normally require coercing both to unsigned
* (ie, floating-point) values. But instead, we do a subtraction, (regLSP - regLSPLimitLow), and
* if the result is negative, we need only be concerned if the signs of both numbers are the same
* (ie, the sign of their XOR'ed union is positive).
*/
if (((this.regLSP - this.regLSPLimitLow)|0) < 0 && (this.regLSPLimitLow ^ this.regLSP) >= 0) {
if (((regLSP - this.regLSPLimitLow)|0) < 0 && (this.regLSPLimitLow ^ regLSP) >= 0) {
/*
* There's no such thing as an SS fault on the 8086/8088, and I'm assuming that, on newer
* processors, when the stack segment limit is set to the maximum, it's OK for the stack to wrap.
*/
if (this.model <= X86.MODEL_8088 || !this.segSS.fExpDown && this.segSS.limit == this.segSS.maskAddr || this.segSS.fExpDown && !this.segSS.limit) {
this.setSP((this.regLSP - this.segSS.base) & this.segSS.maskAddr);
this.setSP((regLSP - this.segSS.base) & this.segSS.maskAddr);
regLSP = this.regLSP;
} else {
X86.fnFault.call(this, X86.EXCEPTION.SS_FAULT, 0);
}
}
this.setWord(this.regLSP, w);
this.setWord(regLSP, w);
/*
* We update this.regLSP at the end to make life simpler for opcode handlers that perform only one
* pushWord() operation, relieving them from having to snapshot this.regLSP into this.opLSP needlessly.
*/
this.regLSP = regLSP;
};
/**

View file

@ -560,6 +560,13 @@ X86.fnCALLw = function CALLw(dst, src)
*/
X86.fnCALLF = function CALLF(off, sel)
{
/*
* Originally, we would snapshot regLSP into opLSP because setCSIP() could trigger a segment fault,
* but additionally, the stack segment could trigger either a segment fault or a page fault; indeed,
* any operation that performs multiple stack modifications must take this precaution and snapshot regLSP.
*/
this.opLSP = this.regLSP;
var oldCS = this.getCS();
var oldIP = this.getIP();
var oldSize = (I386? this.sizeData : 2);
@ -567,6 +574,8 @@ X86.fnCALLF = function CALLF(off, sel)
this.pushData(oldCS, oldSize);
this.pushData(oldIP, oldSize);
}
this.opLSP = X86.ADDR_INVALID;
};
/**
@ -582,9 +591,18 @@ X86.fnCALLFdw = function CALLFdw(dst, src)
if (this.regEA === X86.ADDR_INVALID) {
return X86.fnGRPUndefined.call(this, dst, src);
}
/*
* Originally, we would snapshot regLSP into opLSP because fnCALLF() could trigger a segment fault,
* but additionally, the stack segment could trigger either a segment fault or a page fault; indeed,
* any operation that performs multiple stack modifications must take this precaution and snapshot regLSP.
*/
this.opLSP = this.regLSP;
X86.fnCALLF.call(this, dst, this.getShort(this.regEA + this.sizeData));
this.nStepCycles -= this.cycleCounts.nOpCyclesCallDM;
this.opFlags |= X86.OPFLAG.NOWRITE;
this.opLSP = X86.ADDR_INVALID;
return dst;
};
@ -1444,7 +1462,9 @@ X86.fnINT = function INT(nIDT, nError, nCycles)
X86.fnIRET = function IRET()
{
/*
* As discussed in fnRETF(), we temporarily set opLSP around operations that fnFault() may need to restart.
* Originally, we would snapshot regLSP into opLSP because newCS could trigger a segment fault,
* but additionally, the stack segment could trigger either a segment fault or a page fault; indeed,
* any operation that performs multiple stack modifications must take this precaution and snapshot regLSP.
*/
this.opLSP = this.regLSP;
@ -1515,6 +1535,7 @@ X86.fnIRET = function IRET()
if (this.cIntReturn) this.checkIntReturn(this.regLIP);
}
}
this.opLSP = X86.ADDR_INVALID;
};
@ -2418,31 +2439,16 @@ X86.fnRCRd = function RCRd(dst, src)
* we may have switched to; setCSIP() returns true if a stack switch occurred, false if not, and null
* if an error occurred.
*
* Take a look at our counterpart, fnCALLF():
*
* if (this.setCSIP(off, sel, true) != null) {
* this.pushWord(oldCS);
* this.pushWord(oldIP);
* }
*
* That code is inherently restartable, because it doesn't modify the stack unless setCSIP() succeeds.
*
* Here, our task is a little more complicated, because 1) it's not convenient to defer our stack
* operations until AFTER setCSIP(); 2) we have to deal with an additional stack adjustment value (n);
* and 3) if setCSIP() triggers a fault (eg, NP_FAULT), fnFault() must be able to do the rewinding,
* which happens BEFORE setCSIP() returns.
*
* The current hack to make the stack "rewindable" involves copying regLSP to opLSP, similar to what we do
* for EIP (ie, by copying regLIP into opLIP prior to executing every opcode), so that fnFault() can rewind
* ESP as needed. And since I don't really want to snapshot more data inside the opcode loop, my compromise
* is to set opLSP only within instructions (like this one) that read/write the stack, and then reset opLSP
* back to X86.ADDR_INVALID when we're done.
*
* @this {X86CPU}
* @param {number} n
*/
X86.fnRETF = function RETF(n)
{
/*
* Originally, we would snapshot regLSP into opLSP because newCS could trigger a segment fault,
* but additionally, the stack segment could trigger either a segment fault or a page fault; indeed,
* any operation that performs multiple stack modifications must take this precaution and snapshot regLSP.
*/
this.opLSP = this.regLSP;
var newIP = this.popWord();
@ -2476,6 +2482,7 @@ X86.fnRETF = function RETF(n)
}
}
if (n == 2 && this.cIntReturn) this.checkIntReturn(this.regLIP);
this.opLSP = X86.ADDR_INVALID;
};

View file

@ -1118,6 +1118,9 @@ X86.opPUSHFS = function PUSHFS()
*/
X86.opPOPFS = function POPFS()
{
/*
* Any operation that modifies the stack before loading a new segment must snapshot regLSP first.
*/
this.opLSP = this.regLSP;
this.setFS(this.popWord());
this.nStepCycles -= this.cycleCounts.nOpCyclesPopReg;
@ -1190,6 +1193,9 @@ X86.opPUSHGS = function PUSHGS()
*/
X86.opPOPGS = function POPGS()
{
/*
* Any operation that modifies the stack before loading a new segment must snapshot regLSP first.
*/
this.opLSP = this.regLSP;
this.setGS(this.popWord());
this.nStepCycles -= this.cycleCounts.nOpCyclesPopReg;

View file

@ -143,7 +143,7 @@ X86.opPUSHES = function PUSHES()
X86.opPOPES = function POPES()
{
/*
* As discussed in fnRETF(), we temporarily set opLSP around operations that fnFault() may need to restart.
* Any operation that modifies the stack before loading a new segment must snapshot regLSP first.
*/
this.opLSP = this.regLSP;
this.setES(this.popWord());
@ -240,6 +240,9 @@ X86.opPUSHCS = function PUSHCS()
*/
X86.opPOPCS = function POPCS()
{
/*
* Because this is an 8088-only operation, we don't have to worry about taking a snapshot of regLSP first.
*/
this.setCS(this.popWord());
this.nStepCycles -= this.cycleCounts.nOpCyclesPopReg;
};
@ -344,7 +347,7 @@ X86.opPUSHSS = function PUSHSS()
X86.opPOPSS = function POPSS()
{
/*
* As discussed in fnRETF(), we temporarily set opLSP around operations that fnFault() may need to restart.
* Any operation that modifies the stack before loading a new segment must snapshot regLSP first.
*/
this.opLSP = this.regLSP;
this.setSS(this.popWord());
@ -442,7 +445,7 @@ X86.opPUSHDS = function PUSHDS()
X86.opPOPDS = function POPDS()
{
/*
* As discussed in fnRETF(), we temporarily set opLSP around operations that fnFault() may need to restart.
* Any operation that modifies the stack before loading a new segment must snapshot regLSP first.
*/
this.opLSP = this.regLSP;
this.setDS(this.popWord());
@ -1269,6 +1272,11 @@ X86.opPOPDI = function POPDI()
*/
X86.opPUSHA = function PUSHA()
{
/*
* Any operation that performs multiple stack modifications must snapshot regLSP first.
*/
this.opLSP = this.regLSP;
/*
* TODO: regLSP needs to be pre-bounds-checked against regLSPLimitLow
*/
@ -1303,6 +1311,8 @@ X86.opPUSHA = function PUSHA()
}
this.pushWord(this.regEDI & this.maskData);
this.nStepCycles -= this.cycleCounts.nOpCyclesPushAll;
this.opLSP = X86.ADDR_INVALID;
};
/**
@ -1312,6 +1322,11 @@ X86.opPUSHA = function PUSHA()
*/
X86.opPOPA = function POPA()
{
/*
* Any operation that performs multiple stack modifications must snapshot regLSP first.
*/
this.opLSP = this.regLSP;
this.regEDI = (this.regEDI & ~this.maskData) | this.popWord();
if (BACKTRACK) {
this.backTrack.btiDILo = this.backTrack.btiMem0; this.backTrack.btiDIHi = this.backTrack.btiMem1;
@ -1346,6 +1361,8 @@ X86.opPOPA = function POPA()
this.backTrack.btiAL = this.backTrack.btiMem0; this.backTrack.btiAH = this.backTrack.btiMem1;
}
this.nStepCycles -= this.cycleCounts.nOpCyclesPopAll;
this.opLSP = X86.ADDR_INVALID;
};
/**
@ -3427,9 +3444,6 @@ X86.opRETn = function RETn()
{
var n = this.getIPShort();
var newIP = this.popWord();
// if (DEBUG) this.printMessage(" returning to " + str.toHex(this.segCS.sel, 4) + ':' + str.toHex(newIP, this.sizeData << 1), this.bitsMessage, true);
this.setIP(newIP);
if (n) this.setSP(this.getSP() + n); // TODO: optimize
this.nStepCycles -= this.cycleCounts.nOpCyclesRetn;
@ -3443,9 +3457,6 @@ X86.opRETn = function RETn()
X86.opRET = function RET()
{
var newIP = this.popWord();
// if (DEBUG) this.printMessage(" returning to " + str.toHex(this.segCS.sel, 4) + ':' + str.toHex(newIP, this.sizeData << 1), this.bitsMessage, true);
this.setIP(newIP);
this.nStepCycles -= this.cycleCounts.nOpCyclesRet;
};
@ -3509,6 +3520,11 @@ X86.opMOVw = function MOVw()
*/
X86.opENTER = function ENTER()
{
/*
* Any operation that performs multiple stack modifications must snapshot regLSP first.
*/
this.opLSP = this.regLSP;
var wLocal = this.getIPShort();
var bLevel = this.getIPByte() & 0x1f;
/*
@ -3528,6 +3544,8 @@ X86.opENTER = function ENTER()
}
this.regEBP = (this.regEBP & ~this.maskData) | wFrame;
this.setSP((this.getSP() & ~this.segSS.maskAddr) | ((this.getSP() - wLocal) & this.segSS.maskAddr));
this.opLSP = X86.ADDR_INVALID;
};
/**
@ -3537,12 +3555,20 @@ X86.opENTER = function ENTER()
*/
X86.opLEAVE = function LEAVE()
{
/*
* Any operation that performs multiple stack modifications must snapshot regLSP first.
*/
this.opLSP = this.regLSP;
this.setSP((this.getSP() & ~this.segSS.maskAddr) | (this.regEBP & this.segSS.maskAddr));
this.regEBP = (this.regEBP & ~this.maskData) | (this.popWord() & this.maskData);
/*
* NOTE: 5 is the cycle time for the 80286; the 80186/80188 has a cycle time of 8. TODO: Fix this someday.
*/
this.nStepCycles -= 5;
this.opLSP = X86.ADDR_INVALID;
};
/**
@ -3965,9 +3991,6 @@ X86.opCALL = function CALL()
var disp = this.getIPWord();
var oldIP = this.getIP();
var newIP = oldIP + disp;
// if (DEBUG) this.printMessage("calling " + str.toHex(newIP, this.sizeData << 1), this.bitsMessage, true);
this.pushWord(oldIP);
this.setIP(newIP);
this.nStepCycles -= this.cycleCounts.nOpCyclesCall;