Added WDEB386 service simulation (INT 0x41)

This commit is contained in:
Jeff Parsons 2015-08-27 16:23:12 -07:00
commit f56815c11b
52 changed files with 357 additions and 58 deletions

View file

@ -311,7 +311,7 @@ Bus.prototype.reset = function()
* powerUp(data, fRepower)
*
* We don't need a powerDown() handler, because for largely historical reasons, our state (including the A20 state)
* is saved by saveMemory().
* is saved by the saveMemory(), which called by the CPU.
*
* However, we do need a powerUp() handler, because on resumable machines, the Computer's onReset() function calls
* everyone's powerUp() handler rather than their reset() handler.

View file

@ -1095,7 +1095,7 @@ ChipSet.prototype.powerUp = function(data, fRepower)
* powerDown(fSave, fShutdown)
*
* @this {ChipSet}
* @param {boolean} fSave
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
*/

View file

@ -766,9 +766,9 @@ Computer.prototype.powerReport = function(stateComputer)
* which doesn't seem like a huge problem.
*
* @this {Computer}
* @param {boolean} fSave
* @param {boolean} [fSave] is true to request a saved state
* @param {boolean} [fShutdown] is true if the machine is being shut down
* @return {string|null} string representing the captured state (or null if error)
* @return {string|null} string representing the saved state (or null if error)
*/
Computer.prototype.powerOff = function(fSave, fShutdown)
{
@ -966,7 +966,14 @@ Computer.prototype.setBinding = function(sHTMLType, sBinding, control)
control.onclick = function onClickSave() {
var sUserID = computer.queryUserID(true);
if (sUserID) {
var fSave = !!(computer.resume && !computer.sResumePath);
/*
* I modified the test to include a check for sStatePath so that I could save new states
* for machines with existing states; otherwise, I'd have no (easy) way of capturing and
* updating their state. Making the machine (even temporarily) resumable would have been
* one work-around, but it's not appropriate for some machines, as their state is simply
* too large (for localStorage anyway, which is the default storage solution).
*/
var fSave = !!(computer.resume && !computer.sResumePath || computer.sStatePath);
var sState = computer.powerOff(fSave);
if (fSave) {
computer.saveServerState(sUserID, sState);
@ -974,6 +981,19 @@ Computer.prototype.setBinding = function(sHTMLType, sBinding, control)
computer.notice("Resume disabled, machine state not saved");
}
}
/*
* This seemed like a handy alternative, but it turned out to be a no-go, at least for large states:
*
* var sState = computer.powerOff(true);
* if (sState) {
* sState = "data:text/json;charset=utf-8," + encodeURIComponent(sState);
* window.open(sState);
* }
*
* Perhaps if I embedded the data in a link on the current page instead; eg:
*
* $('<a href="' + sState + '" download="state.json">Download</a>').appendTo('#container');
*/
};
return true;
case "reset":

View file

@ -287,7 +287,7 @@ CPU.prototype.powerUp = function(data, fRepower)
* powerDown(fSave, fShutdown)
*
* @this {CPU}
* @param {boolean} fSave
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
*/

View file

@ -1247,9 +1247,133 @@ if (DEBUGGER) {
this.messageDump(Messages.TSS, function onDumpTSS(s) { dbg.dumpTSS(s); });
this.messageDump(Messages.DOS, function onDumpDOS(s) { dbg.dumpDOS(s); });
this.fWinDbg = this.dbgAddrWinDbg = null;
this.cpu.addIntNotify(Interrupts.WINDBG.VECTOR, this, this.intWindowsDebugger);
if (Interrupts.WINDBGRM.ENABLED) {
this.fWinDbgRM = null;
this.cpu.addIntNotify(Interrupts.WINDBGRM.VECTOR, this, this.intWindowsDebuggerRM);
}
this.setReady();
};
/**
* intWindowsDebugger()
*
* @this {Debugger}
* @param {number} addr
* @return {boolean} true to proceed with the INT 0x41 software interrupt, false to skip
*/
Debugger.prototype.intWindowsDebugger = function(addr)
{
if (this.fWinDbg === false) return true;
var seg, limit;
var cpu = this.cpu;
var AX = cpu.regEAX & 0xffff;
var BX = cpu.regEBX & 0xffff;
var CX = cpu.regECX & 0xffff;
var DX = cpu.regEDX & 0xffff;
var SI = cpu.regESI & 0xffff;
var DI = cpu.regEDI & 0xffff;
var ES = cpu.segES.sel;
if (!this.fWinDbg) {
if (AX == Interrupts.WINDBG.IS_LOADED) {
/*
* We're only going to respond to this function if no one else did, in which case,
* we'll set fWinDbg to true and handle additional notifications.
*/
cpu.addIntReturn(addr, function(dbg) {
return function onInt41Return(nLevel) {
if ((cpu.regEAX & 0xffff) != Interrupts.WINDBG.LOADED) {
cpu.regEAX = (cpu.regEAX & ~0xffff) | Interrupts.WINDBG.LOADED;
dbg.println("INT 0x41 processing enabled");
dbg.dbgAddrWinDbg = dbg.newAddr();
dbg.fWinDbg = true;
} else {
dbg.println("INT 0x41 processing disabled");
dbg.fWinDbg = false;
}
};
}(this));
}
return true;
}
switch(AX) {
case Interrupts.WINDBG.IS_LOADED:
cpu.regEAX = (cpu.regEAX & ~0xffff) | Interrupts.WINDBG.LOADED;
break;
case Interrupts.WINDBG.LOAD_SEG:
/*
* The output should completely mimic WDEB386....
*/
limit = (seg = this.getSegment(CX))? seg.limit : 0;
this.println(this.getSZ(this.setAddr(this.dbgAddrWinDbg, DI, ES)) + "!undefined " + ((SI & 0x1)? "data" : "code") + '(' + str.toHex(BX+1, 4) + ")=#" + str.toHex(CX, 4) + " len " + str.toHex(limit+1));
break;
default:
this.println("INT 0x41: " + str.toHexWord(AX));
break;
}
return true;
};
if (Interrupts.WINDBGRM.ENABLED) {
/**
* intWindowsDebuggerRM()
*
* @this {Debugger}
* @param {number} addr
* @return {boolean} true to proceed with the INT 0x68 software interrupt, false to skip
*/
Debugger.prototype.intWindowsDebuggerRM = function(addr)
{
if (this.fWinDbgRM === false) return true;
var cpu = this.cpu;
var AH = this.cpu.regEAX & 0xff;
if (!this.fWinDbgRM) {
if (AH == Interrupts.WINDBGRM.IS_LOADED) {
/*
* We're only going to respond to this function if no one else did, in which case,
* we'll set fWinDbgRM to true and handle additional notifications.
*/
cpu.addIntReturn(addr, function(dbg) {
return function onInt68Return(nLevel) {
if ((cpu.regEAX & 0xffff) != Interrupts.WINDBGRM.LOADED) {
cpu.regEAX = (cpu.regEAX & ~0xffff) | Interrupts.WINDBGRM.LOADED;
dbg.println("INT 0x68 processing enabled");
dbg.fWinDbgRM = true;
} else {
dbg.println("INT 0x68 processing disabled");
dbg.fWinDbgRM = false;
}
};
}(this));
}
return true;
}
switch(AH) {
case Interrupts.WINDBGRM.IS_LOADED:
cpu.regEAX = (cpu.regEAX & ~0xffff) | Interrupts.WINDBGRM.LOADED;
break;
default:
this.println("INT 0x68: " + str.toHexByte(AH));
break;
}
return true;
};
}
/**
* setBinding(sHTMLType, sBinding, control)
*
@ -1575,10 +1699,32 @@ if (DEBUGGER) {
*/
Debugger.prototype.newAddr = function(off, sel, addr, fProt, fData32, fAddr32)
{
if (fProt === undefined) fProt = this.getProtMode();
if (fData32 === undefined) fData32 = (this.cpu && this.cpu.segCS.sizeData == 4);
if (fAddr32 === undefined) fAddr32 = (this.cpu && this.cpu.segCS.sizeAddr == 4);
return {off: off || 0, sel: sel, addr: addr, fProt: fProt || false, fTempBreak: false, fData32: fData32 || false, fAddr32: fAddr32 || false};
return this.setAddr({}, off, sel, addr, fProt, fData32, fAddr32);
};
/**
* setAddr(dbgAddr, off, sel, addr, fProt, fData32, fAddr32)
*
* @this {Debugger}
* @param {DbgAddr} dbgAddr
* @param {number|null|undefined} [off] (default is zero)
* @param {number|null|undefined} [sel] (default is undefined)
* @param {number|null|undefined} [addr] (default is undefined)
* @param {boolean} [fProt] (default is the current CPU mode)
* @param {boolean} [fData32] (default is the current CPU operand size)
* @param {boolean} [fAddr32] (default is the current CPU address size)
* @return {DbgAddr}
*/
Debugger.prototype.setAddr = function(dbgAddr, off, sel, addr, fProt, fData32, fAddr32)
{
dbgAddr.off = off || 0;
dbgAddr.sel = sel;
dbgAddr.addr = addr;
dbgAddr.fProt = (fProt == null)? this.getProtMode() : fProt;
dbgAddr.fData32 = (fData32 == null)? (this.cpu && this.cpu.segCS.sizeData == 4) : fData32;
dbgAddr.fAddr32 = (fAddr32 == null)? (this.cpu && this.cpu.segCS.sizeAddr == 4) : fAddr32;
dbgAddr.fTempBreak = false;
return dbgAddr;
};
/**
@ -1694,8 +1840,8 @@ if (DEBUGGER) {
cchMax = cchMax || 256;
while (s.length < cchMax) {
var b = this.getByte(dbgAddr, 1);
if (!b || b == 0x24) break;
s += (b >= 32 && b < 128? String.fromCharCode(b) : '.');
if (!b || b == 0x24 || b >= 127) break;
s += (b >= 32? String.fromCharCode(b) : '.');
}
return s;
};
@ -2853,7 +2999,7 @@ if (DEBUGGER) {
* powerDown(fSave, fShutdown)
*
* @this {Debugger}
* @param {boolean} fSave
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean}
*/
@ -3704,8 +3850,8 @@ if (DEBUGGER) {
sOperands += (sOperand || "???");
}
var sLine = this.hexAddr(dbgAddrIns) + ' ';
var sBytes = "";
var sLine = this.hexAddr(dbgAddrIns) + ' ';
if (dbgAddrIns.addr != X86.ADDR_INVALID && dbgAddr.addr != X86.ADDR_INVALID) {
do {
sBytes += str.toHex(this.getByte(dbgAddrIns, 1), 2);
@ -5078,7 +5224,23 @@ if (DEBUGGER) {
}
if (sAddr == "state") {
this.println(this.cmp.powerOff(true));
var s = this.cmp.powerOff(true);
if (sLen == "console") {
/*
* Console buffers are notoriously small, and even the following code, which breaks the
* data into parts (eg, "d state console 1", "d state console 2", etc) just isn't that helpful.
*
* var nPart = +sBytes;
* if (nPart) s = s.substr(1000000 * (nPart-1), 1000000);
*
* So, the best way to capture a large machine state is to run your own local server and use
* server-side storage. Take a look at the "Save" binding in computer.js, which binds an HTML
* control to the computer.powerOff() and computer.saveServerState() functions.
*/
console.log(s);
} else {
this.println(s);
}
return;
}
@ -5803,12 +5965,13 @@ if (DEBUGGER) {
};
/**
* doRegisters(asArgs)
* doRegisters(asArgs, fInstruction)
*
* @this {Debugger}
* @param {Array.<string>} [asArgs]
* @param {boolean} [fInstruction] (default is true)
*/
Debugger.prototype.doRegisters = function(asArgs)
Debugger.prototype.doRegisters = function(asArgs, fInstruction)
{
if (asArgs && asArgs[1] == '?') {
this.println("register commands:");
@ -5817,13 +5980,16 @@ if (DEBUGGER) {
this.println("\trx [#]\tset flag or register x to [#]");
return;
}
var fIns = true, fProt;
var fProt;
if (fInstruction == null) fInstruction = true;
if (asArgs != null && asArgs.length > 1) {
var sReg = asArgs[1];
if (sReg == 'p') {
fProt = (this.cpu.model >= X86.MODEL_80286);
} else {
// fIns = false;
// fInstruction = false;
var sValue = null;
var i = sReg.indexOf('=');
if (i > 0) {
@ -5909,13 +6075,13 @@ if (DEBUGGER) {
this.cpu.setSS(w);
break;
case "CS":
// fIns = true;
// fInstruction = true;
this.cpu.setCS(w);
this.dbgAddrNextCode = this.newAddr(this.cpu.getIP(), this.cpu.getCS());
break;
case "IP":
case "EIP":
// fIns = true;
// fInstruction = true;
this.cpu.setIP(w);
this.dbgAddrNextCode = this.newAddr(this.cpu.getIP(), this.cpu.getCS());
break;
@ -6052,7 +6218,7 @@ if (DEBUGGER) {
this.println(this.getRegDump(fProt));
if (fIns) {
if (fInstruction) {
this.dbgAddrNextCode = this.newAddr(this.cpu.getIP(), this.cpu.getCS());
this.doUnassemble(this.hexAddr(this.dbgAddrNextCode));
}

View file

@ -868,7 +868,7 @@ Disk.prototype.donePowerUp = function(drive, disk, sDiskName, sDiskPath)
* exist, and even after it was created, it didn't originally receive powerDown() notifications.
*
* @this {Disk}
* @param {boolean} fSave
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean}
*/

View file

@ -608,7 +608,7 @@ FDC.prototype.powerUp = function(data, fRepower)
* powerDown(fSave, fShutdown)
*
* @this {FDC}
* @param {boolean} fSave
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
*/

View file

@ -598,7 +598,7 @@ HDC.prototype.powerUp = function(data, fRepower)
* powerDown(fSave, fShutdown)
*
* @this {HDC}
* @param {boolean} fSave
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean}
*/

View file

@ -59,7 +59,19 @@ var Interrupts = {
DOS_IDLE: 0x28,
DOS_NETBIOS:0x2A,
MOUSE: 0x33,
FUNCS: {}
WINDBG: { // Windows Debugger (eg, WDEB386) protected-mode interface
VECTOR: 0x41,
IS_LOADED: 0x004F, // AX command
LOADED: 0xF386, // returned in AX if Windows Debugger loaded
LOAD_SEG: 0x0050 // SI==0 if code, 1 if data; BX==segnum-1; CX==selector; DX==data instance; ES:[E]DI->module name
},
WINDBGRM: { // Windows Debugger (eg, WDEB386) real-mode interface
VECTOR: 0x68,
IS_LOADED: 0x43, // AH command
LOADED: 0xF386, // returned in AX if Windows Debugger loaded
ENABLED: false // support for WINDBGRM interrupts is NOT enabled by default
},
FUNCS: {} // filled in only if DEBUGGER is true
};
if (DEBUGGER) {
@ -87,7 +99,7 @@ if (DEBUGGER) {
0x16: "get drive %DL change line status",
0x17: "set drive %DL DASD type",
0x18: "set drive %DL media type"
},
};
Interrupts.FUNCS[Interrupts.CASSETTE] = {
0x80: "open device",
0x81: "close device",
@ -197,6 +209,9 @@ if (DEBUGGER) {
0x63: "get lead byte table (%AL)", // DOS 2.25 and 3.20+
0x6C: "extended open file $%DS:%SI" // DOS 4.00+
};
Interrupts.FUNCS[Interrupts.WINDBG.VECTOR] = {
0x004F: "check debugger loaded" // WINDBG.IS_LOADED returns WINDBG.LOADED (0xF386) if debugger loaded
};
}
/*

View file

@ -1367,7 +1367,7 @@ Keyboard.prototype.powerUp = function(data, fRepower)
* powerDown(fSave, fShutdown)
*
* @this {Keyboard}
* @param {boolean} fSave
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
*/

View file

@ -288,7 +288,7 @@ Mouse.prototype.powerUp = function(data, fRepower)
* powerDown(fSave, fShutdown)
*
* @this {Mouse}
* @param {boolean} fSave
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
*/

View file

@ -369,7 +369,7 @@ Panel.prototype.powerUp = function(data, fRepower)
* powerDown(fSave, fShutdown)
*
* @this {Panel}
* @param {boolean} fSave
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
*/

View file

@ -119,7 +119,7 @@ RAM.prototype.powerUp = function(data, fRepower)
* powerDown(fSave, fShutdown)
*
* @this {RAM}
* @param {boolean} fSave
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
*/

View file

@ -202,7 +202,7 @@ ROM.prototype.powerUp = function(data, fRepower)
* created, above and beyond those symbols we automatically loaded, if any, along with the ROM).
*
* @this {ROM}
* @param {boolean} fSave
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
*/

View file

@ -421,7 +421,7 @@ SerialPort.prototype.powerUp = function(data, fRepower)
* powerDown(fSave, fShutdown)
*
* @this {SerialPort}
* @param {boolean} fSave
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
*/

View file

@ -3339,7 +3339,7 @@ Video.prototype.powerUp = function(data, fRepower)
* buffer contents, and blocking all further updates to the display.
*
* @this {Video}
* @param {boolean} fSave
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
*/

View file

@ -1605,6 +1605,9 @@ X86CPU.prototype.getChecksum = function()
*
* Add an software interrupt notification handler to the CPU's list of such handlers.
*
* TODO: Consider adding removeIntNotify(). Example use case: if the Debugger's intWindowsDebugger() function
* detects that an INT 0x41 client is loaded, it would be quite happy to uninstall itself.
*
* @this {X86CPU}
* @param {number} nInt
* @param {Component} component
@ -1645,10 +1648,12 @@ X86CPU.prototype.checkIntNotify = function(nInt)
* The enabling of INT messages is one of the criteria that's also included in the Debugger's checksEnabled()
* function, and therefore included in fDebugCheck, so for maximum speed, we check fDebugCheck first.
*
* NOTE: By wrapping this in MAXDEBUG, we're effectively eliminating the need for any checkIntReturn() calls;
* onIntReturn() generates a lot of noise, via dbg.messageIntReturn(), and because there's no way to be be sure
* we'll catch the return (or for some interrupts, *whether* they will return), it's safer to disable this
* feature unless you really want it.
* NOTE: We've added MAXDEBUG to the test below, because onIntReturn() generates a lot of noise, via
* dbg.messageIntReturn(), and because there's no way to be sure we'll catch the return (or for some interrupts,
* *whether* they will return), so it's safer to disable this feature unless you really want it.
*
* For most purposes, just having dbg.messageInt(), and the Debugger's ability to selectively turn categories
* of messages on and off, is good enough.
*/
if (DEBUGGER && this.aFlags.fDebugCheck) {
if (this.messageEnabled(Messages.INT) && this.dbg.messageInt(nInt, this.regLIP) && MAXDEBUG) {

View file

@ -1325,7 +1325,7 @@ X86.fnIRET = function IRET()
if (this.setCSIP(newIP, newCS, false) != null) {
this.setPS(newPS, cpl);
if (MAXDEBUG && this.cIntReturn) this.checkIntReturn(this.regLIP);
if (this.cIntReturn) this.checkIntReturn(this.regLIP);
}
}
this.opLSP = X86.ADDR_INVALID;
@ -2280,7 +2280,7 @@ X86.fnRETF = function RETF(n)
this.zeroSeg(this.segGS);
}
}
if (MAXDEBUG && n == 2 && this.cIntReturn) this.checkIntReturn(this.regLIP);
if (n == 2 && this.cIntReturn) this.checkIntReturn(this.regLIP);
this.opLSP = X86.ADDR_INVALID;
};
@ -3376,6 +3376,7 @@ X86.fnVERR = function VERR(dst, src)
}
}
this.clearZF();
if (this.sizeData > 2 || this.sizeAddr > 2) this.stopCPU();
return dst;
};
@ -3413,6 +3414,7 @@ X86.fnVERW = function VERW(dst, src)
}
}
this.clearZF();
if (this.sizeData > 2 || this.sizeAddr > 2) this.stopCPU();
return dst;
};

View file

@ -3486,6 +3486,10 @@ X86.opINTn = function INTn()
return;
}
var nInt = this.getIPByte();
/*
* checkIntNotify() checks for any notification handlers registered via addIntNotify(), calls them,
* and returns false ONLY if a notification handler returned false (ie, requesting the interrupt be skipped).
*/
if (this.checkIntNotify(nInt)) {
X86.fnINT.call(this, nInt, null, 0);
return;