Added support for "call breaks", and fixed BTC, BTR and BTS (hopefully correctly this time),

This commit is contained in:
Jeff Parsons 2015-09-04 14:13:09 -07:00
commit 8d83504d9a
5 changed files with 215 additions and 62 deletions

View file

@ -1271,7 +1271,7 @@ if (DEBUGGER) {
this.messageDump(Messages.TSS, function onDumpTSS(asArgs) { dbg.dumpTSS(asArgs); });
this.messageDump(Messages.DOS, function onDumpDOS(asArgs) { dbg.dumpDOS(asArgs); });
if (Interrupts.WINDBG.ENABLED) {
if (Interrupts.WINDBG.ENABLED || Interrupts.WINDBGRM.ENABLED) {
this.fWinDbg = null;
this.cpu.addIntNotify(Interrupts.WINDBG.VECTOR, this.intWindowsDebugger.bind(this));
}
@ -1283,12 +1283,21 @@ if (DEBUGGER) {
this.setReady();
};
if (Interrupts.WINDBG.ENABLED) {
if (Interrupts.WINDBG.ENABLED || Interrupts.WINDBGRM.ENABLED) {
/**
* intWindowsDebugger()
*
* This intercepts calls to the Windows Debugger protected-mode interface (INT 0x41).
*
* It's enabled if Interrupts.WINDBG.ENABLED is true, but it must ALSO be enabled if
* Interrupts.WINDBGRM.ENABLED is true, because if the latter decides to respond to
* requests, then we must start responding, too, because Windows assumes that the former
* is installed whenever it detects the latter.
*
* Which is also why intWindowsDebuggerRM() will also set this.fWinDbg to true: we MUST
* return false for all INT 0x41 requests, so that all requests are consumed, since there's
* no guarantee that a valid interrupt handler exists inside the machine.
*
* @this {Debugger}
* @param {number} addr
* @return {boolean} true to proceed with the INT 0x41 software interrupt, false to skip
@ -1328,10 +1337,15 @@ if (DEBUGGER) {
return true;
}
/*
* NOTE: If this.fWinDbg is true, then all cases should return false, because we're taking full
* responsibility for all requests (don't assume there's valid interrupt handler inside the machine).
*/
switch(AX) {
case Interrupts.WINDBG.IS_LOADED:
if (this.fWinDbg) {
cpu.regEAX = (cpu.regEAX & ~0xffff) | Interrupts.WINDBG.LOADED;
return false;
}
break;
@ -1341,14 +1355,16 @@ if (DEBUGGER) {
*/
sModule = this.getSZ(this.newAddr(DI, ES));
limit = (seg = this.getSegment(CX))? seg.limit : 0;
if (!this.fWinDbg) {
if (this.fWinDbg) {
this.println(sModule + "!undefined " + ((SI & 0x1)? "data" : "code") + '(' + str.toHex(BX+1, 4) + ")=#" + str.toHex(CX, 4) + " len " + str.toHex(limit+1));
return false;
}
break;
default:
if (this.fWinDbg) {
this.println("INT 0x41: " + str.toHexWord(AX));
return false;
}
break;
}
@ -1387,7 +1403,11 @@ if (DEBUGGER) {
if ((cpu.regEAX & 0xffff) != Interrupts.WINDBGRM.LOADED) {
cpu.regEAX = (cpu.regEAX & ~0xffff) | Interrupts.WINDBGRM.LOADED;
dbg.println("INT 0x68 handling enabled");
dbg.fWinDbgRM = true;
/*
* If we turn on INT 0x68 handling, we must also turn on INT 0x41 handling,
* because Windows assumes that the latter handler exists whenever the former does.
*/
dbg.fWinDbg = dbg.fWinDbgRM = true;
} else {
dbg.println("INT 0x68 monitoring enabled");
dbg.fWinDbgRM = false;
@ -1402,9 +1422,20 @@ if (DEBUGGER) {
case Interrupts.WINDBGRM.IS_LOADED:
if (this.fWinDbgRM) {
cpu.regEAX = (cpu.regEAX & ~0xffff) | Interrupts.WINDBGRM.LOADED;
return false;
}
break;
case Interrupts.WINDBGRM.PREP_PMODE:
if (this.fWinDbgRM) {
var a = cpu.segCS.addCallBreak(this.callWindowsDebuggerPMInit.bind(this));
if (a) {
cpu.regEDI = a[0];
cpu.setES(a[1]);
}
}
return false;
case Interrupts.WINDBGRM.LOAD_SEG:
if (AL == 0x20) {
/*
@ -1450,22 +1481,80 @@ if (DEBUGGER) {
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 selAlias = this.getShort(dbgAddr, 2) || sel;
if (!this.fWinDbgRM) {
this.log(this.getSZ(dbgAddrModule) + '!' + this.getSZ(dbgAddrDevice) + "!undefined " + ((AL & 0x1)? "data" : "code") + '(' + str.toHex(nSeg, 4) + ")=" + str.toHex(selAlias, 4) + ':' + str.toHex(off) + " len " + str.toHex(len));
if (this.fWinDbgRM) {
this.println(this.getSZ(dbgAddrModule) + '!' + this.getSZ(dbgAddrDevice) + "!undefined " + ((AL & 0x1)? "data" : "code") + '(' + str.toHex(nSeg, 4) + ")=" + str.toHex(selAlias, 4) + ':' + str.toHex(off) + " len " + str.toHex(len));
}
}
cpu.regEAX = (cpu.regEAX & ~0xff) | 0x01;
if (this.fWinDbgRM) {
cpu.regEAX = (cpu.regEAX & ~0xff) | 0x01;
return false;
}
break;
default:
if (this.fWinDbgRM) {
this.log("INT 0x68: " + str.toHexByte(AH));
this.println("INT 0x68: " + str.toHexByte(AH));
return false;
}
break;
}
return true;
};
/**
* callWindowsDebuggerPMInit()
*
* This intercepts calls to the Windows Debugger "PMInit" interface; eg:
*
* AL = function code
*
* 0 - initialize IDT
* ES:EDI points to protected mode IDT
*
* 1 - initialize page checking
* BX = physical selector
* ECX = linear bias
*
* 2 - specify that debug queries are supported
*
* 3 - initialize spare PTE
* EBX = linear address of spare PTE
* EDX = linear address the PTE represents
*
* 4 - set Enter/Exit VMM routine address
* EBX = Enter VMM routine address
* ECX = Exit VMM routine address
* EDX = $_Debug_Out_Service address
* ESI = $_Trace_Out_Service address
* The VMM enter/exit routines must return with a retfd
*
* 5 - get debugger size/physical address
* returns: AL = 0 (don't call AL = 1)
* ECX = size in bytes
* ESI = starting physical code/data address
*
* 6 - set debugger base/initialize spare PTE
* EBX = linear address of spare PTE
* EDX = linear address the PTE represents
* ESI = starting linear address of debug code/data
*
* 7 - enable memory context functions
*
* @this {Debugger}
* @return {boolean} (must always return false to skip the call, because the call is using a CALLBREAK address)
*/
Debugger.prototype.callWindowsDebuggerPMInit = function()
{
var cpu = this.cpu;
var AL = cpu.regEAX & 0xff;
this.println("INT 0x68 callback: " + str.toHexByte(AL));
if (AL == 5) {
cpu.regECX = cpu.regESI = 0; // our in-machine debugger footprint is zero
cpu.regEAX = (cpu.regEAX & ~0xff) | 0x01;
}
return false;
}
}
/**

View file

@ -62,21 +62,19 @@ var Interrupts = {
ALT_DISK: 0x40, // HDC BIOS saves original FDC BIOS vector here
ALT_VIDEO: 0x6D, // IBM VGA BIOS saves original video BIOS vector here
WINDBG: { // Windows Debugger 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; ES:[E]DI->module name
ENABLED: true // support for WINDBGRM interrupts can be disabled
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)
ENABLED: true // support for WINDBG interrupts can be disabled (but NOT if WINDBGRM is enabled)
},
WINDBGRM: { // Windows Debugger real-mode interface
VECTOR: 0x68,
IS_LOADED: 0x43, // AH command
LOADED: 0xF386, // returned in AX if Windows Debugger loaded
LOAD_SEG: 0x50, // AL=segment type, ES:DI->D386_Device_Params
/*
* This must be disabled until we're able to respond intelligently to requests like D386_Prepare_PMode (0x44)
*/
ENABLED: false // support for WINDBGRM interrupts can be disabled
VECTOR: 0x68, // (AH==command)
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)
ENABLED: true // support for WINDBGRM interrupts can be disabled
},
FUNCS: {} // filled in only if DEBUGGER is true
};

View file

@ -2310,18 +2310,14 @@ X86CPU.prototype.setCSIP = function(off, sel, fCall)
/*
* We break this operation into the following discrete steps (eg, set IP, load CS, and then update IP) so
* that segCS.load(sel) has the ability to modify IP when sel refers to a gate (call, interrupt, trap, etc).
*
* NOTE: regEIP acts merely as a conduit for the IP, if any, that segCS.load() may load; regLIP is still our
* internal instruction pointer. Callers that need the real IP must call getIP().
*/
this.regEIP = off;
var base = this.segCS.loadCode(sel, fCall);
var base = this.segCS.loadCode(off, sel, fCall);
if (base !== X86.ADDR_INVALID) {
/*
* TODO: Should this code be factored into a setLIP() function? The other primary client would be fnINT().
*/
if (I386) this.resetSizes();
this.regLIP = (base + (this.regEIP & (I386? this.maskData : 0xffff)))|0;
this.regLIP = (base + (this.segCS.offIP & (I386? this.maskData : 0xffff)))|0;
this.regLIPLimit = (base + this.segCS.limit)|0;
this.nCPL = this.segCS.cpl; // cache the current CPL where it's more convenient
if (PREFETCH) this.flushPrefetch(this.regLIP);

View file

@ -369,21 +369,29 @@ X86.fnBTMem = function BTMem(dst, src)
if (this.regEA === X86.ADDR_INVALID) {
return X86.fnBT.call(this, dst, src);
}
var offByte = src >>> 3;
if (offByte >= this.sizeData) {
/*
* TODO: Consider a worker function that performs the following block of code for: BT, BTC, BTR, and BTS.
* It's somewhat inconvenient, because it needs to provide two results: an updated src AND an updated dst.
*/
if ((src >>> 3) >= this.sizeData) {
/*
* offByte is src divided by 8, but now we need src divided by 16 or 32, according to the OPERAND size,
* We just divided src by 8, but now we need to divide src by 16 or 32, according to the OPERAND size,
* which means shifting it right by either 4 or 5 bits. That gives us a short or long INDEX, which we then
* multiply by the OPERAND size to obtain to the corresponding short or long OFFSET that we add to regEA.
* multiply by the OPERAND size to obtain to the corresponding short or long OFFSET that we must add to
* the original EA offset.
*/
var i = src >>> (this.sizeData == 2? 4 : 5);
dst = this.getWord(this.regEA += i * this.sizeData);
dst = this.getEAWord(this.segEA, this.offEA + i * this.sizeData);
}
/*
* Now we convert src from a bit index into a bit mask.
* Now we convert src from a bit index to a bit mask.
*/
src = 1 << (src & (this.sizeData == 2? 0xf : 0x1f));
if (dst & src) this.setCF(); else this.clearCF();
/*
* End of common code block
*/
this.nStepCycles -= 6;
this.opFlags |= X86.OPFLAG.NOWRITE;
@ -406,18 +414,19 @@ X86.fnBTCMem = function BTCMem(dst, src)
if (this.regEA === X86.ADDR_INVALID) {
return X86.fnBTC.call(this, dst, src);
}
var offByte = src >>> 3;
if (offByte >= this.sizeData) {
if ((src >>> 3) >= this.sizeData) {
/*
* offByte is src divided by 8, but now we need src divided by 16 or 32, according to the OPERAND size,
* We just divided src by 8, but now we need to divide src by 16 or 32, according to the OPERAND size,
* which means shifting it right by either 4 or 5 bits. That gives us a short or long INDEX, which we then
* multiply by the OPERAND size to obtain to the corresponding short or long OFFSET that we add to regEA.
* multiply by the OPERAND size to obtain to the corresponding short or long OFFSET that we must add to
* the original EA offset.
*/
var i = src >>> (this.sizeData == 2? 4 : 5);
dst = this.getWord(this.regEA += i * this.sizeData);
dst = this.getEAWord(this.segEA, this.offEA + i * this.sizeData);
}
/*
* Now we convert src from a bit index into a bit mask.
* Now we convert src from a bit index to a bit mask.
*/
src = 1 << (src & (this.sizeData == 2? 0xf : 0x1f));
if (dst & src) this.setCF(); else this.clearCF();
@ -442,18 +451,19 @@ X86.fnBTRMem = function BTRMem(dst, src)
if (this.regEA === X86.ADDR_INVALID) {
return X86.fnBTR.call(this, dst, src);
}
var offByte = src >>> 3;
if (offByte >= this.sizeData) {
if ((src >>> 3) >= this.sizeData) {
/*
* offByte is src divided by 8, but now we need src divided by 16 or 32, according to the OPERAND size,
* We just divided src by 8, but now we need to divide src by 16 or 32, according to the OPERAND size,
* which means shifting it right by either 4 or 5 bits. That gives us a short or long INDEX, which we then
* multiply by the OPERAND size to obtain to the corresponding short or long OFFSET that we add to regEA.
* multiply by the OPERAND size to obtain to the corresponding short or long OFFSET that we must add to
* the original EA offset.
*/
var i = src >>> (this.sizeData == 2? 4 : 5);
dst = this.getWord(this.regEA += i * this.sizeData);
dst = this.getEAWord(this.segEA, this.offEA + i * this.sizeData);
}
/*
* Now we convert src from a bit index into a bit mask.
* Now we convert src from a bit index to a bit mask.
*/
src = 1 << (src & (this.sizeData == 2? 0xf : 0x1f));
if (dst & src) this.setCF(); else this.clearCF();
@ -478,18 +488,19 @@ X86.fnBTSMem = function BTSMem(dst, src)
if (this.regEA === X86.ADDR_INVALID) {
return X86.fnBTS.call(this, dst, src);
}
var offByte = src >>> 3;
if (offByte >= this.sizeData) {
if ((src >>> 3) >= this.sizeData) {
/*
* offByte is src divided by 8, but now we need src divided by 16 or 32, according to the OPERAND size,
* We just divided src by 8, but now we need to divide src by 16 or 32, according to the OPERAND size,
* which means shifting it right by either 4 or 5 bits. That gives us a short or long INDEX, which we then
* multiply by the OPERAND size to obtain to the corresponding short or long OFFSET that we add to regEA.
* multiply by the OPERAND size to obtain to the corresponding short or long OFFSET that we must add to
* the original EA offset.
*/
var i = src >>> (this.sizeData == 2? 4 : 5);
dst = this.getWord(this.regEA += i * this.sizeData);
dst = this.getEAWord(this.segEA, this.offEA + i * this.sizeData);
}
/*
* Now we convert src from a bit index into a bit mask.
* Now we convert src from a bit index to a bit mask.
*/
src = 1 << (src & (this.sizeData == 2? 0xf : 0x1f));
if (dst & src) this.setCF(); else this.clearCF();

View file

@ -124,10 +124,14 @@ function X86Seg(cpu, id, sName, fProt)
*
* loadIDT() sets fCall to true unconditionally in protected-mode (fCall has no meaning in real-mode).
*/
this.fCall = null;
this.fStackSwitch = false;
this.sizeFrame = 2; // must be set by all loadIDT() calls so that callers know the proper frame size
this.awParms = (this.id == X86Seg.ID.CODE? new Array(32) : []);
if (this.id == X86Seg.ID.CODE) {
this.offIP = 0;
this.fCall = null;
this.fStackSwitch = false;
this.sizeFrame = 2; // must be set by all loadIDT() calls so that callers know the proper frame size
this.awParms = new Array(32);
this.aCallBreaks = [];
}
this.updateMode(true, fProt);
}
@ -142,18 +146,41 @@ X86Seg.ID = {
DBG: 7 // "DBG"
};
X86Seg.CALLBREAK_SEL = 0x0001;
/**
* loadCode(sel, fCall)
* addCallBreak(fn)
*
* A simple wrapper function that encapsulates setting the fCall property for segCS loads.
* Returns a "call break" address in an [off, sel] array. The given function, fn(), is called
* whenever that address is called, and if fn() returns false, then the call is skipped. Otherwise,
* the call is performed (ie, the old CS:[E]IP is pushed on the stack, and CS:[E]IP is set to the
* "call break" address. Which is probably a bad idea, so your function should probably always
* return false. Just sayin'.
*
* @this {X86Seg}
* @param {function()} fn
* @return {Array.<number>} containing offset and selector of call-break address
*/
X86Seg.prototype.addCallBreak = function(fn)
{
this.aCallBreaks.push(fn);
return [this.aCallBreaks.length, X86Seg.CALLBREAK_SEL];
};
/**
* loadCode(off, sel, fCall)
*
* A simple wrapper function that encapsulates setting offIP and fCall for segCS loads.
*
* @this {X86Seg}
* @param {number} off
* @param {number} sel
* @param {boolean|undefined} fCall is true if CALLF in progress, false if RETF/IRET in progress, undefined otherwise
* @return {number} base address of selected segment, or X86.ADDR_INVALID if error
*/
X86Seg.prototype.loadCode = function loadCode(sel, fCall)
X86Seg.prototype.loadCode = function loadCode(off, sel, fCall)
{
this.offIP = off;
this.fCall = fCall;
return this.load(sel);
};
@ -295,7 +322,7 @@ X86Seg.prototype.loadIDTProt = function loadIDTProt(nIDT)
var addrDesc = (cpu.addrIDT + nIDT)|0;
if (((cpu.addrIDTLimit - addrDesc)|0) >= 7) {
this.fCall = true;
return this.loadDesc8(addrDesc, nIDT) + cpu.regEIP;
return this.loadDesc8(addrDesc, nIDT) + this.offIP;
}
/*
* TODO: Remove fHalt=true from this fnFault() call once this code path has been tested.
@ -637,6 +664,35 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe)
this.sizeFrame = this.sizeData;
var fCall = this.fCall;
/*
* This special bit of code is currently used only by the Debugger, when it needs to inject
* a 16:32 callback address into the machine that it can intercept calls to. We call these
* "call break" addresses, because they're like private breakpoints that only operate when
* a particular address is called; specifically, an address with selector 0x0001 and an offset
* that forms an index (1-based) into the aCallBreaks function table.
*
* In protected-mode, 0x0001 is an invalid code selector (a null selector with an RPL of 1),
* and while it's not inconceivable that an operating system might use such a selector for
* some strange purpose, I've not seen such an operating system. And in any case, those
* operating systems are not likely to trigger the Debugger's call to addCallBreak(), so no
* call breaks will be generated, and this code will never execute.
*
* TODO: If we ever need this to be mode-independent, it can be moved somewhere where it will
* trigger for both real and protected-mode code segment loads, because CALLBREAK_SEL (0x0001)
* is also a very unlikely real-mode CS value (but again, not inconceivable). I think this is
* a reasonable solution, and it's likely the best we can do without injecting code into the
* machine that we could address -- and even then, it would not be a mode-independent address.
*/
if (fCall && sel == X86Seg.CALLBREAK_SEL && this.aCallBreaks.length) {
var iBreak = this.offIP - 1;
var fnCallBreak = this.aCallBreaks[iBreak];
cpu.assert(fnCallBreak);
if (fnCallBreak && !fnCallBreak()) {
return X86.ADDR_INVALID;
}
}
var rpl = sel & X86.SEL.RPL;
var dpl = (acc & X86.DESC.ACC.DPL.MASK) >> X86.DESC.ACC.DPL.SHIFT;
@ -789,7 +845,7 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe)
this.sizeFrame = sizeGate;
cpu.regEIP = limit;
this.offIP = limit;
cpu.assert(this.cpl == cplNew);
if (this.cpl < cplOld) {
@ -943,9 +999,12 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe)
default:
/*
* The only other case used to be X86Seg.ID.DBG, but the Debugger uses probeDesc() now, so we should never get here.
* The only other cases are:
*
* X86Seg.ID.NULL, X86Seg.ID.LDT, and X86Seg.ID.DBG
*
* which correspond to segNULL, segLDT and segDebugger; however, segLDT is the only one that might require further validation (TODO: Investigate).
*/
cpu.assert(false);
break;
}