Limited Debug register support
This commit is contained in:
parent
884f49d6b5
commit
9fa2941394
7 changed files with 168 additions and 85 deletions
|
|
@ -1662,6 +1662,10 @@ if (DEBUGGER) {
|
||||||
/**
|
/**
|
||||||
* newAddr(off, sel, addr, fProt, fData32, fAddr32)
|
* newAddr(off, sel, addr, fProt, fData32, fAddr32)
|
||||||
*
|
*
|
||||||
|
* NOTES: If 'off' is undefined, it may be because parseExpression() was unable to parse the given value,
|
||||||
|
* in which case we need to make sure that 'off' passes through as undefined, so that callers can test for
|
||||||
|
* "dbgAddr.off == null" to detect invalid addresses.
|
||||||
|
*
|
||||||
* @this {Debugger}
|
* @this {Debugger}
|
||||||
* @param {number|null|undefined} [off] (default is zero)
|
* @param {number|null|undefined} [off] (default is zero)
|
||||||
* @param {number|null|undefined} [sel] (default is undefined)
|
* @param {number|null|undefined} [sel] (default is undefined)
|
||||||
|
|
@ -2406,9 +2410,11 @@ if (DEBUGGER) {
|
||||||
while ((i = s.indexOf('$', i)) >= 0) {
|
while ((i = s.indexOf('$', i)) >= 0) {
|
||||||
sAddr = s.substr(i+1, 9);
|
sAddr = s.substr(i+1, 9);
|
||||||
dbgAddr = this.parseAddr(sAddr);
|
dbgAddr = this.parseAddr(sAddr);
|
||||||
sReplace = sAddr + ' "' + this.getSZ(dbgAddr) + '"';
|
if (dbgAddr) {
|
||||||
s = s.replace('$' + sAddr, sReplace);
|
sReplace = sAddr + ' "' + this.getSZ(dbgAddr) + '"';
|
||||||
i += sReplace.length;
|
s = s.replace('$' + sAddr, sReplace);
|
||||||
|
i += sReplace.length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* Replace every ^XXXX:XXXX, where XXXX:XXXX is a segmented address, with the FCB filename stored at that address.
|
* Replace every ^XXXX:XXXX, where XXXX:XXXX is a segmented address, with the FCB filename stored at that address.
|
||||||
|
|
@ -2417,10 +2423,12 @@ if (DEBUGGER) {
|
||||||
while ((i = s.indexOf('^', i)) >= 0) {
|
while ((i = s.indexOf('^', i)) >= 0) {
|
||||||
sAddr = s.substr(i+1, 9);
|
sAddr = s.substr(i+1, 9);
|
||||||
dbgAddr = this.parseAddr(sAddr);
|
dbgAddr = this.parseAddr(sAddr);
|
||||||
this.incAddr(dbgAddr);
|
if (dbgAddr) {
|
||||||
sReplace = sAddr + ' "' + this.getSZ(dbgAddr, 11) + '"';
|
this.incAddr(dbgAddr);
|
||||||
s = s.replace('^' + sAddr, sReplace);
|
sReplace = sAddr + ' "' + this.getSZ(dbgAddr, 11) + '"';
|
||||||
i += sReplace.length;
|
s = s.replace('^' + sAddr, sReplace);
|
||||||
|
i += sReplace.length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
};
|
};
|
||||||
|
|
@ -4049,7 +4057,7 @@ if (DEBUGGER) {
|
||||||
* @param {string|undefined} sAddr
|
* @param {string|undefined} sAddr
|
||||||
* @param {number|undefined} [type] is the address segment type, in case sAddr doesn't specify a segment
|
* @param {number|undefined} [type] is the address segment type, in case sAddr doesn't specify a segment
|
||||||
* @param {boolean} [fNoChecks] (eg, true when setting breakpoints that may not be valid now, but will be later)
|
* @param {boolean} [fNoChecks] (eg, true when setting breakpoints that may not be valid now, but will be later)
|
||||||
* @return {{DbgAddr}}
|
* @return {{DbgAddr}|null|undefined}
|
||||||
*/
|
*/
|
||||||
Debugger.prototype.parseAddr = function(sAddr, type, fNoChecks)
|
Debugger.prototype.parseAddr = function(sAddr, type, fNoChecks)
|
||||||
{
|
{
|
||||||
|
|
@ -4076,6 +4084,7 @@ if (DEBUGGER) {
|
||||||
addr = null;
|
addr = null;
|
||||||
} else {
|
} else {
|
||||||
addr = this.parseExpression(sAddr);
|
addr = this.parseExpression(sAddr);
|
||||||
|
if (addr == null) off = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -4085,8 +4094,10 @@ if (DEBUGGER) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dbgAddr = this.newAddr(off, sel, addr);
|
if (off != null) {
|
||||||
if (!fNoChecks) this.checkLimit(dbgAddr);
|
dbgAddr = this.newAddr(off, sel, addr);
|
||||||
|
if (!fNoChecks) this.checkLimit(dbgAddr);
|
||||||
|
}
|
||||||
return dbgAddr;
|
return dbgAddr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -4548,7 +4559,7 @@ if (DEBUGGER) {
|
||||||
Debugger.prototype.doAssemble = function(asArgs)
|
Debugger.prototype.doAssemble = function(asArgs)
|
||||||
{
|
{
|
||||||
var dbgAddr = this.parseAddr(asArgs[1], Debugger.ADDR_CODE);
|
var dbgAddr = this.parseAddr(asArgs[1], Debugger.ADDR_CODE);
|
||||||
if (dbgAddr.off == null) return;
|
if (!dbgAddr) return;
|
||||||
|
|
||||||
this.dbgAddrAssemble = dbgAddr;
|
this.dbgAddrAssemble = dbgAddr;
|
||||||
if (asArgs[2] === undefined) {
|
if (asArgs[2] === undefined) {
|
||||||
|
|
@ -4626,7 +4637,7 @@ if (DEBUGGER) {
|
||||||
var dbgAddr = {};
|
var dbgAddr = {};
|
||||||
if (sAddr != "*") {
|
if (sAddr != "*") {
|
||||||
dbgAddr = this.parseAddr(sAddr, Debugger.ADDR_CODE, true);
|
dbgAddr = this.parseAddr(sAddr, Debugger.ADDR_CODE, true);
|
||||||
if (dbgAddr.off == null) return;
|
if (!dbgAddr) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -4767,7 +4778,7 @@ if (DEBUGGER) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR_DATA);
|
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR_DATA);
|
||||||
if (dbgAddr.off == null || dbgAddr.sel == null && dbgAddr.addr == null) return;
|
if (!dbgAddr) return;
|
||||||
|
|
||||||
var sDump = "";
|
var sDump = "";
|
||||||
if (BACKTRACK && sCmd == "di") {
|
if (BACKTRACK && sCmd == "di") {
|
||||||
|
|
@ -4821,7 +4832,8 @@ if (DEBUGGER) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR_DATA);
|
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR_DATA);
|
||||||
if (dbgAddr.off == null) return;
|
if (!dbgAddr) return;
|
||||||
|
|
||||||
for (var i = 2; i < asArgs.length; i++) {
|
for (var i = 2; i < asArgs.length; i++) {
|
||||||
var b = str.parseInt(asArgs[i], 16);
|
var b = str.parseInt(asArgs[i], 16);
|
||||||
if (b === undefined) {
|
if (b === undefined) {
|
||||||
|
|
@ -5004,8 +5016,7 @@ if (DEBUGGER) {
|
||||||
Debugger.prototype.doList = function(sSymbol)
|
Debugger.prototype.doList = function(sSymbol)
|
||||||
{
|
{
|
||||||
var dbgAddr = this.parseAddr(sSymbol, Debugger.ADDR_CODE);
|
var dbgAddr = this.parseAddr(sSymbol, Debugger.ADDR_CODE);
|
||||||
|
if (!dbgAddr) return;
|
||||||
if (dbgAddr.off == null && dbgAddr.addr == null) return;
|
|
||||||
|
|
||||||
var addr = this.getAddr(dbgAddr);
|
var addr = this.getAddr(dbgAddr);
|
||||||
sSymbol = sSymbol? (sSymbol + ": ") : "";
|
sSymbol = sSymbol? (sSymbol + ": ") : "";
|
||||||
|
|
@ -5064,7 +5075,9 @@ if (DEBUGGER) {
|
||||||
|
|
||||||
var fJSON = (asArgs[1] == "json");
|
var fJSON = (asArgs[1] == "json");
|
||||||
var iDrive, iSector = 0, nSectors = 0;
|
var iDrive, iSector = 0, nSectors = 0;
|
||||||
|
|
||||||
var dbgAddr = (fJSON? {} : this.parseAddr(asArgs[1], Debugger.ADDR_DATA));
|
var dbgAddr = (fJSON? {} : this.parseAddr(asArgs[1], Debugger.ADDR_DATA));
|
||||||
|
if (!dbgAddr) return;
|
||||||
|
|
||||||
iDrive = this.parseValue(asArgs[2], "drive #");
|
iDrive = this.parseValue(asArgs[2], "drive #");
|
||||||
if (iDrive === undefined) return;
|
if (iDrive === undefined) return;
|
||||||
|
|
@ -5086,7 +5099,7 @@ if (DEBUGGER) {
|
||||||
* drive and its disk are inseparable; it's certainly possible that its disk object may be empty at
|
* drive and its disk are inseparable; it's certainly possible that its disk object may be empty at
|
||||||
* this point, but that will only affect whether the read succeeds or not.
|
* this point, but that will only affect whether the read succeeds or not.
|
||||||
*/
|
*/
|
||||||
var dc = this.fdc;
|
var dc = this .fdc;
|
||||||
if (iDrive >= 2 && this.hdc) {
|
if (iDrive >= 2 && this.hdc) {
|
||||||
iDrive -= 2;
|
iDrive -= 2;
|
||||||
dc = this.hdc;
|
dc = this.hdc;
|
||||||
|
|
@ -5547,7 +5560,7 @@ if (DEBUGGER) {
|
||||||
{
|
{
|
||||||
if (sAddr !== undefined) {
|
if (sAddr !== undefined) {
|
||||||
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR_CODE);
|
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR_CODE);
|
||||||
if (dbgAddr.off == null) return;
|
if (!dbgAddr) return;
|
||||||
this.setTempBreakpoint(dbgAddr);
|
this.setTempBreakpoint(dbgAddr);
|
||||||
}
|
}
|
||||||
if (!this.runCPU(true)) {
|
if (!this.runCPU(true)) {
|
||||||
|
|
@ -5841,7 +5854,7 @@ if (DEBUGGER) {
|
||||||
Debugger.prototype.doUnassemble = function(sAddr, sAddrEnd, n)
|
Debugger.prototype.doUnassemble = function(sAddr, sAddrEnd, n)
|
||||||
{
|
{
|
||||||
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR_CODE);
|
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR_CODE);
|
||||||
if (dbgAddr.off == null) return;
|
if (!dbgAddr) return;
|
||||||
|
|
||||||
if (n === undefined) n = 1;
|
if (n === undefined) n = 1;
|
||||||
var dbgAddrEnd = this.newAddr(this.maskReg, dbgAddr.sel, this.bus.nBusLimit);
|
var dbgAddrEnd = this.newAddr(this.maskReg, dbgAddr.sel, this.bus.nBusLimit);
|
||||||
|
|
@ -5850,7 +5863,7 @@ if (DEBUGGER) {
|
||||||
if (sAddrEnd !== undefined) {
|
if (sAddrEnd !== undefined) {
|
||||||
|
|
||||||
dbgAddrEnd = this.parseAddr(sAddrEnd, Debugger.ADDR_CODE);
|
dbgAddrEnd = this.parseAddr(sAddrEnd, Debugger.ADDR_CODE);
|
||||||
if (dbgAddrEnd.off == null || dbgAddrEnd.off < dbgAddr.off) return;
|
if (!dbgAddrEnd || dbgAddrEnd.off < dbgAddr.off) return;
|
||||||
|
|
||||||
cb = dbgAddrEnd.off - dbgAddr.off;
|
cb = dbgAddrEnd.off - dbgAddr.off;
|
||||||
if (!DEBUG && cb > 0x100) {
|
if (!DEBUG && cb > 0x100) {
|
||||||
|
|
|
||||||
|
|
@ -2142,7 +2142,7 @@ Disk.prototype.toJSON = function()
|
||||||
/*
|
/*
|
||||||
* Last but not least, insert line breaks after every object definition, to ease the pain on text editors.
|
* Last but not least, insert line breaks after every object definition, to ease the pain on text editors.
|
||||||
*/
|
*/
|
||||||
s = s.replace(/},/gm, "},\n");
|
s = s.replace(/([\]}]),/gm, "$1,\n");
|
||||||
return s;
|
return s;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ function Memory(addr, used, size, type, controller, cpu)
|
||||||
this.type = type || Memory.TYPE.NONE;
|
this.type = type || Memory.TYPE.NONE;
|
||||||
this.fReadOnly = (type == Memory.TYPE.ROM);
|
this.fReadOnly = (type == Memory.TYPE.ROM);
|
||||||
this.controller = null;
|
this.controller = null;
|
||||||
this.cpu = cpu;
|
this.cpu = cpu; // If a CPU reference is provided, then this must be an UNPAGED Memory block allocation
|
||||||
this.fDirty = this.fDirtyEver = false;
|
this.fDirty = this.fDirtyEver = false;
|
||||||
this.setPhysBlock();
|
this.setPhysBlock();
|
||||||
|
|
||||||
|
|
@ -490,6 +490,8 @@ Memory.prototype = {
|
||||||
/**
|
/**
|
||||||
* getPageBlock(addr, fWrite)
|
* getPageBlock(addr, fWrite)
|
||||||
*
|
*
|
||||||
|
* Called for UNPAGED Memory blocks only.
|
||||||
|
*
|
||||||
* @this {Memory}
|
* @this {Memory}
|
||||||
* @param {number} addr
|
* @param {number} addr
|
||||||
* @param {boolean} fWrite (true if called for a write, false if for a read)
|
* @param {boolean} fWrite (true if called for a write, false if for a read)
|
||||||
|
|
@ -526,7 +528,7 @@ Memory.prototype = {
|
||||||
*
|
*
|
||||||
* NOTE: Some Memory blocks already require access to the CPU (eg, UNPAGED blocks that need to call cpu.mapPageBlock()),
|
* NOTE: Some Memory blocks already require access to the CPU (eg, UNPAGED blocks that need to call cpu.mapPageBlock()),
|
||||||
* while others require access only if the CPU has set a read or write breakpoint in one of its Debug registers; the latter
|
* while others require access only if the CPU has set a read or write breakpoint in one of its Debug registers; the latter
|
||||||
* case is handled here by virtue of the cpu parameter.
|
* case is handled here by virtue of the CPU parameter.
|
||||||
*
|
*
|
||||||
* @this {Memory}
|
* @this {Memory}
|
||||||
* @param {number} off
|
* @param {number} off
|
||||||
|
|
@ -552,9 +554,15 @@ Memory.prototype = {
|
||||||
/**
|
/**
|
||||||
* removeBreakpoint(off, fWrite)
|
* removeBreakpoint(off, fWrite)
|
||||||
*
|
*
|
||||||
* NOTE: If this Memory block is not an UNPAGED block that might need to call cpu.mapPageBlock()), and it no longer
|
* NOTE: If this Memory block is not an UNPAGED block that might need to call cpu.mapPageBlock()), and it no
|
||||||
* has any read or write breakpoints associated with it, then it no longer needs a CPU reference. However, the latter
|
* longer has any read or write breakpoints associated with it, then it no longer needs a CPU reference. The
|
||||||
* is a moot point, because the "checked" memory access functions should be swapped out when this function is done.
|
* existence of a CPU reference only impacts the performance of the "checked" memory access functions, so it's
|
||||||
|
* not critical to eliminate it, but we do it anyway, in case the "checked" functions become re-enabled later
|
||||||
|
* (eg, by the Debugger -- yeah, the Debugger, which is why eliminating it isn't critical).
|
||||||
|
*
|
||||||
|
* TODO: Another option would be to count CPU references separately from Debugger references, so that when the
|
||||||
|
* former goes to zero, we can unconditionally remove the CPU reference; UNPAGED blocks would automatically
|
||||||
|
* increment that reference count, so their CPU reference would never go away.
|
||||||
*
|
*
|
||||||
* @this {Memory}
|
* @this {Memory}
|
||||||
* @param {number} off
|
* @param {number} off
|
||||||
|
|
@ -575,6 +583,7 @@ Memory.prototype = {
|
||||||
}
|
}
|
||||||
Component.assert(this.cWriteBreakpoints >= 0);
|
Component.assert(this.cWriteBreakpoints >= 0);
|
||||||
}
|
}
|
||||||
|
if (!this.cReadBreakpoints && !this.cWriteBreakpoints && this.type != Memory.TYPE.UNPAGED) this.cpu = null;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* readNone(off)
|
* readNone(off)
|
||||||
|
|
|
||||||
|
|
@ -409,18 +409,19 @@ var X86 = {
|
||||||
* Bit values for opFlags, which are all reset to zero prior to each instruction
|
* Bit values for opFlags, which are all reset to zero prior to each instruction
|
||||||
*/
|
*/
|
||||||
OPFLAG: {
|
OPFLAG: {
|
||||||
NOREAD: 0x0001,
|
NOREAD: 0x0001, // disable memory reads for the remainder of the current instruction
|
||||||
NOWRITE: 0x0002,
|
NOWRITE: 0x0002, // disable memory writes for the remainder of the current instruction
|
||||||
NOINTR: 0x0004, // indicates a segreg has been set, or a prefix, or an STI (delay INTR acknowledgement)
|
NOINTR: 0x0004, // a segreg has been set, or a prefix, or an STI (delay INTR acknowledgement)
|
||||||
FAULT: 0x0008, // indicates a fault occurred during the current instruction
|
|
||||||
SEG: 0x0010, // segment override
|
SEG: 0x0010, // segment override
|
||||||
LOCK: 0x0020, // lock prefix
|
LOCK: 0x0020, // lock prefix
|
||||||
REPZ: 0x0040, // repeat while Z (NOTE: this value MUST match PS.ZF; see opCMPSb/opCMPSw/opSCASb/opSCASw)
|
REPZ: 0x0040, // repeat while Z (NOTE: this value MUST match PS.ZF; see opCMPSb/opCMPSw/opSCASb/opSCASw)
|
||||||
REPNZ: 0x0080, // repeat while NZ
|
REPNZ: 0x0080, // repeat while NZ
|
||||||
REPEAT: 0x0100, // indicates that an instruction is being repeated (ie, some iteration AFTER the first)
|
REPEAT: 0x0100, // an instruction is being repeated (ie, some iteration AFTER the first)
|
||||||
PUSHSP: 0x0200, // the SP register is potentially being referenced by a PUSH SP opcode, adjustment may be required
|
PUSHSP: 0x0200, // the SP register is potentially being referenced by a PUSH SP opcode, adjustment may be required
|
||||||
DATASIZE: 0x1000, // data size override
|
DATASIZE: 0x0400, // data size override
|
||||||
ADDRSIZE: 0x2000 // address size override
|
ADDRSIZE: 0x0800, // address size override
|
||||||
|
FAULT: 0x1000, // a fault occurred during the current instruction
|
||||||
|
DEBUG: 0x2000 // a DEBUG exception occurred during the current instruction
|
||||||
},
|
},
|
||||||
/*
|
/*
|
||||||
* Bit values for intFlags
|
* Bit values for intFlags
|
||||||
|
|
|
||||||
|
|
@ -1683,7 +1683,7 @@ X86CPU.prototype.checkIntReturn = function(addr)
|
||||||
*
|
*
|
||||||
* These functions provide Debug register functionality by leveraging the same Memory block-based breakpoint
|
* 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
|
* support originally created for our built-in Debugger. Only minimal changes were required to the Memory
|
||||||
* component, by adding additional checkMemoryException() call-outs to the "checked" Memory access functions.
|
* 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
|
* 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
|
* passed on it, because we want our own Debugger's breakpoints to take precedence over any breakpoints that
|
||||||
|
|
@ -1712,6 +1712,58 @@ X86CPU.prototype.removeMemCheck = function(addr, fWrite)
|
||||||
this.aMemBlocks[iBlock].removeBreakpoint(addr & this.nBlockLimit, fWrite);
|
this.aMemBlocks[iBlock].removeBreakpoint(addr & this.nBlockLimit, fWrite);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* checkDebugRegisters(fEnable)
|
||||||
|
*
|
||||||
|
* opMOVdr() simplifies its life by doing work ONLY if the contents of a Debug register is actually changing.
|
||||||
|
*
|
||||||
|
* Whenever a single register is about to change, it calls this function with fEnable set to false to REMOVE any
|
||||||
|
* active checks, then updates the Debug register, then calls us again with fEnable set to true to (re)ADD active
|
||||||
|
* checks.
|
||||||
|
*
|
||||||
|
* @this {X86CPU}
|
||||||
|
* @param {boolean} fEnable
|
||||||
|
*/
|
||||||
|
X86CPU.prototype.checkDebugRegisters = function(fEnable)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* We use a constant mask for the enable bits (X86.DR7.L0 | X86.DR7.G0) and shift our copy of regDR7
|
||||||
|
* right 2 bits after each Debug register check.
|
||||||
|
*
|
||||||
|
* Similarly, we make a copy of regDR7 in bitsDR7 and shift the latter right 4 bits at a time, so that
|
||||||
|
* the RW and LEN bits for the next Debug register are always in positions 1-0 and 3-2, respectively.
|
||||||
|
*/
|
||||||
|
var regDR7 = this.regDR[7];
|
||||||
|
var bitsDR7 = regDR7 >> 16;
|
||||||
|
|
||||||
|
for (var i = 0; i < 4; i++) {
|
||||||
|
if (regDR7 & (X86.DR7.L0 | X86.DR7.G0)) {
|
||||||
|
/*
|
||||||
|
* We look only to the low bit of the RW field to determine if we should be watching for a write.
|
||||||
|
* FYI, if the low bit is clear but the high bit is set, that's "undefined"; we treat it as a read.
|
||||||
|
*/
|
||||||
|
var fWrite = !!(bitsDR7 & 0x1);
|
||||||
|
/*
|
||||||
|
* The address in regDR[i] should already be masked with ~0x1 for 2-byte accesses (LEN == 0x1) or
|
||||||
|
* with ~0x3 for 4-byte accesses (LEN == 0x3), but if the client forgets, the hardware supposedly
|
||||||
|
* enforces it, so that's what we do here, too.
|
||||||
|
*
|
||||||
|
* FYI, if LEN is set to the "undefined" value of (0x2), we still apply a mask to the address, albeit
|
||||||
|
* a nonsensical mask of ~0x2 or 0xfffffffd. That's how we define that particular "undefined" LEN.
|
||||||
|
*/
|
||||||
|
var addr = this.regDR[i];
|
||||||
|
var len = ((bitsDR7 >> 2) & 0x3);
|
||||||
|
addr &= ~len; // NOTE: if LEN == 0x0, we don't need to mask, but ~0x0 is equivalent to no mask
|
||||||
|
if (fEnable) {
|
||||||
|
this.addMemCheck(addr, fWrite);
|
||||||
|
} else {
|
||||||
|
this.removeMemCheck(addr, fWrite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
regDR7 >>= 2; bitsDR7 >>= 4;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* checkMemoryException(addr, nb, fWrite)
|
* checkMemoryException(addr, nb, fWrite)
|
||||||
*
|
*
|
||||||
|
|
@ -1719,8 +1771,8 @@ X86CPU.prototype.removeMemCheck = function(addr, fWrite)
|
||||||
* giving us the opportunity look for a matching "read" or "write" breakpoint enabled in one of the DRn registers.
|
* giving us the opportunity look for a matching "read" or "write" breakpoint enabled in one of the DRn registers.
|
||||||
*
|
*
|
||||||
* TODO: This currently does not discriminate between data reads and execution reads. When we switch to a true
|
* TODO: This currently does not discriminate between data reads and execution reads. When we switch to a true
|
||||||
* "prefetch" model, that would also be a good time to include a signal to this function that any "read" accesses
|
* "prefetch" model, that would also be a good time to include a signal to this function which "read" accesses are
|
||||||
* are actually "exec" accesses.
|
* are actually "exec" accesses.
|
||||||
*
|
*
|
||||||
* @this {X86CPU}
|
* @this {X86CPU}
|
||||||
* @param {number} addr
|
* @param {number} addr
|
||||||
|
|
@ -1729,24 +1781,45 @@ X86CPU.prototype.removeMemCheck = function(addr, fWrite)
|
||||||
*/
|
*/
|
||||||
X86CPU.prototype.checkMemoryException = function(addr, nb, fWrite)
|
X86CPU.prototype.checkMemoryException = function(addr, nb, fWrite)
|
||||||
{
|
{
|
||||||
if (this.regDR[7] & X86.DR7.ENABLE) {
|
/*
|
||||||
|
* NOTE: We're preventing redundant X86.EXCEPTION.DEBUG exceptions for a single instruction by checking
|
||||||
|
* X86.OPFLAG.DEBUG. I decided not to rely on the generic X86.OPFLAG.FAULT, because if an instruction
|
||||||
|
* first triggers a DIFFERENT exception which then triggers a DEBUG exception (eg, because a Debug register
|
||||||
|
* was set on the IDT entry of the first exception), that we'd actually like to see the DEBUG exception,
|
||||||
|
* as opposed to, say, a double fault. TODO: Determine if that SHOULD generate a double-fault.
|
||||||
|
*/
|
||||||
|
if (!(this.opFlags & X86.OPFLAG.FAULT) && (this.regDR[7] & X86.DR7.ENABLE)) {
|
||||||
|
nb--;
|
||||||
|
/*
|
||||||
|
* We use a constant mask for the enable bits (X86.DR7.L0 | X86.DR7.G0) and shift our copy of regDR7
|
||||||
|
* right 2 bits after each Debug register check.
|
||||||
|
*
|
||||||
|
* Similarly, we make a copy of regDR7 in bitsDR7 and shift the latter right 4 bits at a time, so that
|
||||||
|
* the RW and LEN bits for the next Debug register are always in positions 1-0 and 3-2, respectively.
|
||||||
|
*/
|
||||||
var regDR7 = this.regDR[7];
|
var regDR7 = this.regDR[7];
|
||||||
var bitsEnabled = X86.DR7.L0 | X86.DR7.G0;
|
var bitsDR7 = regDR7 >> 16;
|
||||||
var bitsRWMask = 0x00030000;
|
|
||||||
var bitsRWRequired = (fWrite? 0x00010000 : (fWrite == false? 0x00030000 : 0));
|
var bitsRWMask = X86.DR7.RW0 >> 16;
|
||||||
var bitsLENMask = 0x000C0000;
|
var bitsRWRequired = (fWrite? 0x1 : (fWrite == false? 0x3 : 0x0));
|
||||||
var bitsLENRequired = (nb == 1? 0x00000000 : (nb == 2? 0x00040000 : 0x000C000));
|
|
||||||
for (var i = 0; i < 4; i++) {
|
for (var i = 0; i < 4; i++) {
|
||||||
if ((regDR7 & bitsEnabled) && (regDR7 & bitsRWMask) == bitsRWRequired) {
|
if ((regDR7 & (X86.DR7.L0 | X86.DR7.G0)) && (bitsDR7 & bitsRWMask) == bitsRWRequired) {
|
||||||
if ((regDR7 & bitsLENMask) == bitsLENRequired && addr == this.regDR[i]) {
|
/*
|
||||||
|
* NOTE: We reduced nb from 1-4 to 0-3 above, so we don't need to add 1 to len either.
|
||||||
|
*/
|
||||||
|
var len = (bitsDR7 >> 2);
|
||||||
|
/*
|
||||||
|
* Time to determine if addr through addr + nb overlaps regDR[i] through regDR[i] + len.
|
||||||
|
*/
|
||||||
|
if (addr + nb >= this.regDR[i] && addr <= this.regDR[i] + len) {
|
||||||
this.regDR[6] |= (1 << i);
|
this.regDR[6] |= (1 << i);
|
||||||
|
this.opFlags |= X86.OPFLAG.DEBUG;
|
||||||
X86.fnFault.call(this, X86.EXCEPTION.DEBUG);
|
X86.fnFault.call(this, X86.EXCEPTION.DEBUG);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bitsEnabled <<= 2;
|
regDR7 >>= 2; bitsDR7 >>= 4;
|
||||||
bitsRWMask <<= 2; bitsRWRequired <<= 2;
|
|
||||||
bitsLENMask <<= 2; bitsLENRequired <<= 2;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3693,6 +3693,13 @@ X86.fnSrcNone = function SrcNone()
|
||||||
*/
|
*/
|
||||||
X86.fnFault = function(nFault, nError, fHalt, nCycles)
|
X86.fnFault = function(nFault, nError, fHalt, nCycles)
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
* This flag is used by selected opcodes to provide an early exit if X86.OPFLAG.FAULT is set, in some cases
|
||||||
|
* making it possible for an instruction to be restarted (eg, opINSw()), and in other cases preventing a redundant
|
||||||
|
* fault from being generated. However, to prevent multiple X86.EXCEPTION.DEBUG exceptions on a single instruction,
|
||||||
|
* checkMemoryException() currently relies on its own OPFLAG.DEBUG, on the theory that we should be allowed to see
|
||||||
|
* DEBUG exceptions triggered by other exceptions.
|
||||||
|
*/
|
||||||
this.opFlags |= X86.OPFLAG.FAULT;
|
this.opFlags |= X86.OPFLAG.FAULT;
|
||||||
|
|
||||||
if (!this.aFlags.fComplete) {
|
if (!this.aFlags.fComplete) {
|
||||||
|
|
|
||||||
|
|
@ -338,7 +338,7 @@ X86.opMOVrc = function MOVrc()
|
||||||
*/
|
*/
|
||||||
if (this.nCPL) {
|
if (this.nCPL) {
|
||||||
/*
|
/*
|
||||||
* You're not allowed to read control registers if the current privilege level is not zero
|
* You're not allowed to read control registers if the current privilege level is not zero.
|
||||||
*/
|
*/
|
||||||
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
|
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
|
||||||
return;
|
return;
|
||||||
|
|
@ -366,7 +366,7 @@ X86.opMOVrc = function MOVrc()
|
||||||
this.nStepCycles -= 6;
|
this.nStepCycles -= 6;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO: Implement BACKTRACK for this instruction (although Control registers are not likely to be a conduit for much interesting data).
|
* TODO: Implement BACKTRACK for this instruction (although Control registers are not likely to be a conduit for interesting data).
|
||||||
*/
|
*/
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -387,7 +387,7 @@ X86.opMOVrd = function MOVrd()
|
||||||
*/
|
*/
|
||||||
if (this.nCPL) {
|
if (this.nCPL) {
|
||||||
/*
|
/*
|
||||||
* You're not allowed to read control registers if the current privilege level is not zero
|
* You're not allowed to read control registers if the current privilege level is not zero.
|
||||||
*/
|
*/
|
||||||
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
|
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
|
||||||
return;
|
return;
|
||||||
|
|
@ -406,7 +406,7 @@ X86.opMOVrd = function MOVrd()
|
||||||
this.nStepCycles -= 22;
|
this.nStepCycles -= 22;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO: Implement BACKTRACK for this instruction (although Debug registers are not likely to be a conduit for much interesting data).
|
* TODO: Implement BACKTRACK for this instruction (although Debug registers are not likely to be a conduit for interesting data).
|
||||||
*/
|
*/
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -436,7 +436,7 @@ X86.opMOVcr = function MOVcr()
|
||||||
*/
|
*/
|
||||||
if (this.nCPL) {
|
if (this.nCPL) {
|
||||||
/*
|
/*
|
||||||
* You're not allowed to write control registers if the current privilege level is not zero
|
* You're not allowed to write control registers if the current privilege level is not zero.
|
||||||
*/
|
*/
|
||||||
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
|
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
|
||||||
return;
|
return;
|
||||||
|
|
@ -464,7 +464,7 @@ X86.opMOVcr = function MOVcr()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO: Implement BACKTRACK for this instruction (although Control registers are not likely to be a conduit for much interesting data).
|
* TODO: Implement BACKTRACK for this instruction (although Control registers are not likely to be a conduit for interesting data).
|
||||||
*/
|
*/
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -485,7 +485,7 @@ X86.opMOVdr = function MOVdr()
|
||||||
*/
|
*/
|
||||||
if (this.nCPL) {
|
if (this.nCPL) {
|
||||||
/*
|
/*
|
||||||
* You're not allowed to write control registers if the current privilege level is not zero
|
* You're not allowed to write control registers if the current privilege level is not zero.
|
||||||
*/
|
*/
|
||||||
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
|
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
|
||||||
return;
|
return;
|
||||||
|
|
@ -498,38 +498,18 @@ X86.opMOVdr = function MOVdr()
|
||||||
X86.opUndefined.call(this);
|
X86.opUndefined.call(this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var regDR = this.getReg(bModRM & 0x7);
|
var regDR = this.getReg(bModRM & 0x7);
|
||||||
|
|
||||||
if (iDst == 7) {
|
if (regDR != this.regDR[iDst]) {
|
||||||
var regDR7 = this.regDR[7];
|
this.checkDebugRegisters(false);
|
||||||
if ((regDR ^ regDR7) & X86.DR7.ENABLE) {
|
this.regDR[iDst] = regDR;
|
||||||
/*
|
this.checkDebugRegisters(true);
|
||||||
* We need to check changes to the Debug Control Register (DR7); specifically, if any of
|
|
||||||
* DR0-DR3 are transitioning from disabled to enabled, or vice versa, then we need to call
|
|
||||||
* addMemCheck(), or removeMemCheck(), as appropriate.
|
|
||||||
*/
|
|
||||||
var bitsEnabled = X86.DR7.L0 | X86.DR7.G0;
|
|
||||||
var bitsRWMask = 0x00030000;
|
|
||||||
for (var i = 0; i < 4; i++) {
|
|
||||||
var fEnabled = (regDR & bitsEnabled);
|
|
||||||
if (!fEnabled != !(regDR7 & bitsEnabled)) {
|
|
||||||
var bitsRW = (regDR & bitsRWMask) >> (i << 1);
|
|
||||||
var fWrite = !!(bitsRW & 0x00010000);
|
|
||||||
if (fEnabled) {
|
|
||||||
this.addMemCheck(this.regDR[i], fWrite);
|
|
||||||
} else {
|
|
||||||
this.removeMemCheck(this.regDR[i], fWrite);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bitsEnabled <<= 2;
|
|
||||||
bitsRWMask <<= 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.regDR[iDst] = regDR;
|
|
||||||
this.nStepCycles -= (iDst < 4? 22 : 14);
|
this.nStepCycles -= (iDst < 4? 22 : 14);
|
||||||
/*
|
/*
|
||||||
* TODO: Implement BACKTRACK for this instruction (although Debug registers are not likely to be a conduit for much interesting data).
|
* TODO: Implement BACKTRACK for this instruction (although Debug registers are not likely to be a conduit for interesting data).
|
||||||
*/
|
*/
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -550,7 +530,7 @@ X86.opMOVrt = function MOVrt()
|
||||||
*/
|
*/
|
||||||
if (this.nCPL) {
|
if (this.nCPL) {
|
||||||
/*
|
/*
|
||||||
* You're not allowed to read control registers if the current privilege level is not zero
|
* You're not allowed to read control registers if the current privilege level is not zero.
|
||||||
*/
|
*/
|
||||||
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
|
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
|
||||||
return;
|
return;
|
||||||
|
|
@ -567,7 +547,7 @@ X86.opMOVrt = function MOVrt()
|
||||||
this.nStepCycles -= 12;
|
this.nStepCycles -= 12;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO: Implement BACKTRACK for this instruction (although Test registers are not likely to be a conduit for much interesting data).
|
* TODO: Implement BACKTRACK for this instruction (although Test registers are not likely to be a conduit for interesting data).
|
||||||
*/
|
*/
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -588,7 +568,7 @@ X86.opMOVtr = function MOVtr()
|
||||||
*/
|
*/
|
||||||
if (this.nCPL) {
|
if (this.nCPL) {
|
||||||
/*
|
/*
|
||||||
* You're not allowed to write control registers if the current privilege level is not zero
|
* You're not allowed to write control registers if the current privilege level is not zero.
|
||||||
*/
|
*/
|
||||||
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
|
X86.fnFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
|
||||||
return;
|
return;
|
||||||
|
|
@ -610,7 +590,7 @@ X86.opMOVtr = function MOVtr()
|
||||||
this.nStepCycles -= 12;
|
this.nStepCycles -= 12;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO: Implement BACKTRACK for this instruction (although Test registers are not likely to be a conduit for much interesting data).
|
* TODO: Implement BACKTRACK for this instruction (although Test registers are not likely to be a conduit for interesting data).
|
||||||
*/
|
*/
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue