v1.19.6: Improved restartability of stack-related instructions
This commit is contained in:
parent
978b668a42
commit
deeac4902d
174 changed files with 9030 additions and 1736 deletions
|
|
@ -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)
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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...?
|
||||
*/
|
||||
}
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue