First v1.18.8 change: preliminary support for 80386 debug registers

This commit is contained in:
Jeff Parsons 2015-07-31 18:25:37 -07:00
commit 6003e948b6
145 changed files with 1099 additions and 947 deletions

View file

@ -1512,15 +1512,15 @@ if (DEBUGGER) {
};
/**
* getAddr(dbgAddr, fWrite, cb)
* getAddr(dbgAddr, fWrite, nb)
*
* @this {Debugger}
* @param {{DbgAddr}} dbgAddr
* @param {boolean} [fWrite]
* @param {number} [cb] is number of bytes to check (1, 2 or 4); default is 1
* @param {number} [nb] is number of bytes to check (1, 2 or 4); default is 1
* @return {number} is the corresponding linear address, or X86.ADDR_INVALID
*/
Debugger.prototype.getAddr = function(dbgAddr, fWrite, cb)
Debugger.prototype.getAddr = function(dbgAddr, fWrite, nb)
{
/*
* Some addresses (eg, breakpoint addresses) save their original linear address in dbgAddr.addr,
@ -1534,9 +1534,9 @@ if (DEBUGGER) {
var seg = this.getSegment(dbgAddr.sel, dbgAddr.fProt);
if (seg) {
if (!fWrite) {
addr = seg.checkRead(dbgAddr.off, cb || 1, true);
addr = seg.checkRead(dbgAddr.off, nb || 1, true);
} else {
addr = seg.checkWrite(dbgAddr.off, cb || 1, true);
addr = seg.checkWrite(dbgAddr.off, nb || 1, true);
}
dbgAddr.addr = addr;
}
@ -2990,7 +2990,7 @@ if (DEBUGGER) {
Debugger.prototype.checkInstruction = function(addr, nState)
{
if (nState > 0) {
if (this.checkBreakpoint(addr, this.aBreakExec)) {
if (this.checkBreakpoint(addr, 1, this.aBreakExec)) {
return true;
}
/*
@ -3027,18 +3027,19 @@ if (DEBUGGER) {
};
/**
* checkMemoryRead(addr)
* checkMemoryRead(addr, nb)
*
* This "check" function is called by a Memory block to inform us that a memory read occurred, giving us an
* opportunity to track the read if we want, and look for a matching "read" breakpoint, if any.
*
* @this {Debugger}
* @param {number} addr
* @param {number} [nb] (# of bytes; default is 1)
* @return {boolean} true if breakpoint hit, false if not
*/
Debugger.prototype.checkMemoryRead = function(addr)
Debugger.prototype.checkMemoryRead = function(addr, nb)
{
if (this.checkBreakpoint(addr, this.aBreakRead)) {
if (this.checkBreakpoint(addr, nb || 1, this.aBreakRead)) {
this.stopCPU(true);
return true;
}
@ -3046,18 +3047,19 @@ if (DEBUGGER) {
};
/**
* checkMemoryWrite(addr)
* checkMemoryWrite(addr, nb)
*
* This "check" function is called by a Memory block to inform us that a memory write occurred, giving us an
* opportunity to track the write if we want, and look for a matching "write" breakpoint, if any.
*
* @this {Debugger}
* @param {number} addr
* @param {number} [nb] (# of bytes; default is 1)
* @return {boolean} true if breakpoint hit, false if not
*/
Debugger.prototype.checkMemoryWrite = function(addr)
Debugger.prototype.checkMemoryWrite = function(addr, nb)
{
if (this.checkBreakpoint(addr, this.aBreakWrite)) {
if (this.checkBreakpoint(addr, nb || 1, this.aBreakWrite)) {
this.stopCPU(true);
return true;
}
@ -3271,7 +3273,7 @@ if (DEBUGGER) {
Debugger.prototype.clearTempBreakpoint = function(addr)
{
if (addr !== undefined) {
this.checkBreakpoint(addr, this.aBreakExec, true);
this.checkBreakpoint(addr, 1, this.aBreakExec, true);
this.fProcStep = 0;
} else {
for (var i = 1; i < this.aBreakExec.length; i++) {
@ -3309,15 +3311,16 @@ if (DEBUGGER) {
};
/**
* checkBreakpoint(addr, aBreak, fTemp)
* checkBreakpoint(addr, nb, aBreak, fTemp)
*
* @this {Debugger}
* @param {number} addr
* @param {number} nb (# of bytes)
* @param {Array} aBreak
* @param {boolean} [fTemp]
* @return {boolean} true if breakpoint has been hit, false if not
*/
Debugger.prototype.checkBreakpoint = function(addr, aBreak, fTemp)
Debugger.prototype.checkBreakpoint = function(addr, nb, aBreak, fTemp)
{
/*
* Time to check for execution breakpoints; note that this should be done BEFORE updating frequency
@ -3361,13 +3364,20 @@ if (DEBUGGER) {
* If you want to create a real-mode breakpoint that will break regardless of mode,
* use the physical address of the real-mode memory location instead.
*/
if (addr == this.mapBreakpoint(this.getAddr(dbgAddrBreak))) {
if (dbgAddrBreak.fTempBreak) {
this.findBreakpoint(aBreak, dbgAddrBreak, true);
} else if (!fTemp) {
this.println("breakpoint hit: " + this.hexAddr(dbgAddrBreak) + " (" + aBreak[0] + ")");
var addrBreak = this.mapBreakpoint(this.getAddr(dbgAddrBreak));
for (var n = 0; n < nb; n++) {
if (addr == addrBreak) {
if (dbgAddrBreak.fTempBreak) {
this.findBreakpoint(aBreak, dbgAddrBreak, true);
} else if (!fTemp) {
this.println("breakpoint hit: " + this.hexAddr(dbgAddrBreak) + " (" + aBreak[0] + ")");
}
fBreak = true;
break;
}
fBreak = true;
addrBreak++;
addr++;
n++;
}
}
}

View file

@ -795,88 +795,102 @@ Memory.prototype = {
/**
* readByteChecked(off, addr)
*
* TODO: Considering adding an assert that "this.addr + off === addr", and consider using that fact to eliminate
* the extra 'addr' parameter.
*
* @this {Memory}
* @param {number} off
* @param {number} addr
* @return {number}
*/
readByteChecked: function readByteChecked(off, addr) {
if (DEBUGGER && this.dbg) this.dbg.checkMemoryRead(addr);
if (!DEBUGGER || !this.dbg || !this.dbg.checkMemoryRead(addr)) {
if (I386) this.cpu.checkMemoryException(addr, 1, false);
}
return this.readByteDirect(off, addr);
},
/**
* readShortChecked(off, addr)
*
* TODO: Considering adding an assert that "this.addr + off === addr", and consider using that fact to eliminate
* the extra 'addr' parameter.
*
* @this {Memory}
* @param {number} off
* @param {number} addr
* @return {number}
*/
readShortChecked: function readShortChecked(off, addr) {
if (DEBUGGER && this.dbg) {
this.dbg.checkMemoryRead(addr) ||
this.dbg.checkMemoryRead(addr + 1);
if (!DEBUGGER || !this.dbg && !this.dbg.checkMemoryRead(addr, 2)) {
if (I386) this.cpu.checkMemoryException(addr, 2, false);
}
return this.readShortDirect(off, addr);
},
/**
* readLongChecked(off, addr)
*
* TODO: Considering adding an assert that "this.addr + off === addr", and consider using that fact to eliminate
* the extra 'addr' parameter.
*
* @this {Memory}
* @param {number} off
* @param {number} addr
* @return {number}
*/
readLongChecked: function readLongChecked(off, addr) {
if (DEBUGGER && this.dbg) {
this.dbg.checkMemoryRead(addr) ||
this.dbg.checkMemoryRead(addr + 1) ||
this.dbg.checkMemoryRead(addr + 2) ||
this.dbg.checkMemoryRead(addr + 3);
if (!DEBUGGER || !this.dbg || !this.dbg.checkMemoryRead(addr, 4)) {
if (I386) this.cpu.checkMemoryException(addr, 4, false);
}
return this.readLongDirect(off, addr);
},
/**
* writeByteChecked(off, b, addr)
*
* TODO: Considering adding an assert that "this.addr + off === addr", and consider using that fact to eliminate
* the extra 'addr' parameter.
*
* @this {Memory}
* @param {number} off
* @param {number} addr
* @param {number} b
*/
writeByteChecked: function writeByteChecked(off, b, addr) {
if (DEBUGGER && this.dbg) this.dbg.checkMemoryWrite(addr);
if (!DEBUGGER || !this.dbg || !this.dbg.checkMemoryWrite(addr)) {
if (I386) this.cpu.checkMemoryException(addr, 1, true);
}
if (this.fReadOnly) this.writeNone(off, b, addr); else this.writeByteDirect(off, b, addr);
},
/**
* writeShortChecked(off, w, addr)
*
* TODO: Considering adding an assert that "this.addr + off === addr", and consider using that fact to eliminate
* the extra 'addr' parameter.
*
* @this {Memory}
* @param {number} off
* @param {number} addr
* @param {number} w
*/
writeShortChecked: function writeShortChecked(off, w, addr) {
if (DEBUGGER && this.dbg) {
this.dbg.checkMemoryWrite(addr) ||
this.dbg.checkMemoryWrite(addr + 1);
if (!DEBUGGER || !this.dbg && !this.dbg.checkMemoryWrite(addr, 2)) {
if (I386) this.cpu.checkMemoryException(addr, 2, true);
}
if (this.fReadOnly) this.writeNone(off, w, addr); else this.writeShortDirect(off, w, addr);
},
/**
* writeLongChecked(off, l, addr)
*
* TODO: Considering adding an assert that "this.addr + off === addr", and consider using that fact to eliminate
* the extra 'addr' parameter.
*
* @this {Memory}
* @param {number} off
* @param {number} l
* @param {number} addr
*/
writeLongChecked: function writeLongChecked(off, l, addr) {
if (DEBUGGER && this.dbg) {
this.dbg.checkMemoryWrite(this.addr + off) ||
this.dbg.checkMemoryWrite(this.addr + off + 1) ||
this.dbg.checkMemoryWrite(this.addr + off + 2) ||
this.dbg.checkMemoryWrite(this.addr + off + 3);
if (!DEBUGGER || !this.dbg || !this.dbg.checkMemoryWrite(addr, 4)) {
if (I386) this.cpu.checkMemoryException(addr, 4, true);
}
if (this.fReadOnly) this.writeNone(off, l, addr); else this.writeLongDirect(off, l, addr);
},

View file

@ -99,6 +99,36 @@ var X86 = {
ET: 0x00000010, // coprocessor type (80287 or 80387); always 1 on post-80386 CPUs
PG: 0x80000000|0 // 0: paging disabled
},
DR7: { // Debug Control Register
L0: 0x00000001,
G0: 0x00000002,
L1: 0x00000004,
G1: 0x00000008,
L2: 0x00000010,
G2: 0x00000020,
L3: 0x00000040,
G3: 0x00000080,
ENABLE: 0x000000FF,
LE: 0x00000100,
GE: 0x00000200,
RW0: 0x00030000, // 00: exec-only 01: write-only 10: undefined 11: read/write-only
LEN0: 0x000C0000, // 00: one-byte, 01: two-byte, 10: undefined 11: four-byte
RW1: 0x00300000, // 00: exec-only 01: write-only 10: undefined 11: read/write-only
LEN1: 0x00C00000, // 00: one-byte, 01: two-byte, 10: undefined 11: four-byte
RW2: 0x03000000, // 00: exec-only 01: write-only 10: undefined 11: read/write-only
LEN2: 0x0C000000, // 00: one-byte, 01: two-byte, 10: undefined 11: four-byte
RW3: 0x30000000, // 00: exec-only 01: write-only 10: undefined 11: read/write-only
LEN3: 0xC0000000|0// 00: one-byte, 01: two-byte, 10: undefined 11: four-byte
},
DR6: { // Debug Status Register
B0: 0x00000001,
B1: 0x00000002,
B2: 0x00000004,
B3: 0x00000008,
BD: 0x00002000, // set if the next instruction will read or write one of the eight debug registers and ICE-386 is also using them
BS: 0x00004000, // set if the debug handler is entered due to the TF (trap flag) bit set in the EFLAGS register
BT: 0x00008000 // set before entering the DEBUG handler if a task switch has occurred and the T-bit of the new TSS is set
},
SEL: {
RPL: 0x0003, // requested privilege level (0-3)
LDT: 0x0004, // table indicator (0: GDT, 1: LDT)
@ -280,7 +310,7 @@ var X86 = {
*/
EXCEPTION: {
DIV_ERR: 0x00, // Divide Error Interrupt
TRAP: 0x01, // Single Step (aka Trap) Interrupt
DEBUG: 0x01, // Debug (aka Single Step Trap) Interrupt
NMI: 0x02, // Non-Maskable Interrupt
BREAKPOINT: 0x03, // Breakpoint Interrupt
OVERFLOW: 0x04, // INTO Overflow Interrupt (FYI, return address does NOT point to offending instruction)

View file

@ -1312,8 +1312,8 @@ X86CPU.prototype.resetRegs = function()
this.regCR1 = 0; // reserved
this.regCR2 = 0; // page fault linear address (PFLA)
this.regCR3 = 0; // page directory base register (PDBR)
this.regDRn = [0,0,0,0,null,null,0,0]; // Debug Registers DR0-DR7 (DR4-DR5 are undefined)
this.regTRn = [null,null,null,null,null,null,0,0]; // Test Registers TR0-TR7 (TR0-TR5 are undefined)
this.regDR = [0,0,0,0,null,null,0,0]; // Debug Registers DR0-DR7 (DR4-DR5 are undefined)
this.regTR = [null,null,null,null,null,null,0,0]; // Test Registers TR0-TR7 (TR0-TR5 are undefined)
this.segFS = new X86Seg(this, X86Seg.ID.DATA, "FS");
this.segGS = new X86Seg(this, X86Seg.ID.DATA, "GS");
this.disablePageBlocks();
@ -1678,6 +1678,79 @@ 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 to 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);
};
/**
* 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);
};
/**
* checkMemoryException(addr, nb, fWrite)
*
* This "check" function is called by a Memory block to inform us that a memory read or write is occurring,
* 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
* "prefetch" model, that would also be a good time to include a signal to this function that any "read" accesses
* are actually "exec" accesses.
*
* @this {X86CPU}
* @param {number} addr
* @param {number} nb (# of bytes)
* @param {boolean|null} [fWrite] (false if read, true if write, null if exec)
*/
X86CPU.prototype.checkMemoryException = function(addr, nb, fWrite)
{
if (this.regDR[7] & X86.DR7.ENABLE) {
var regDR7 = this.regDR[7];
var bitsEnabled = X86.DR7.L0 | X86.DR7.G0;
var bitsRWMask = 0x00030000;
var bitsRWRequired = (fWrite? 0x00010000 : (fWrite == false? 0x00030000 : 0));
var bitsLENMask = 0x000C0000;
var bitsLENRequired = (nb == 1? 0x00000000 : (nb == 2? 0x00040000 : 0x000C000));
for (var i = 0; i < 4; i++) {
if ((regDR7 & bitsEnabled) && (regDR7 & bitsRWMask) == bitsRWRequired) {
if ((regDR7 & bitsLENMask) == bitsLENRequired && addr == this.regDR[i]) {
this.regDR[6] |= (1 << i);
X86.fnFault.call(this, X86.EXCEPTION.DEBUG);
return;
}
}
bitsEnabled <<= 2;
bitsRWMask <<= 2; bitsRWRequired <<= 2;
bitsLENMask <<= 2; bitsLENRequired <<= 2;
}
}
};
/**
* setProtMode(fProt, fV86)
*
@ -1742,8 +1815,8 @@ X86CPU.prototype.saveProtMode = function()
a.push(this.regCR1);
a.push(this.regCR2);
a.push(this.regCR3);
a.push(this.regDRn);
a.push(this.regTRn);
a.push(this.regDR);
a.push(this.regTR);
}
return a;
}
@ -1773,8 +1846,8 @@ X86CPU.prototype.restoreProtMode = function(a)
this.regCR1 = a[8];
this.regCR2 = a[9];
this.regCR3 = a[10];
this.regDRn = a[11];
this.regTRn = a[12];
this.regDR = a[11];
this.regTR = a[12];
}
this.setProtMode();
}
@ -3832,7 +3905,7 @@ X86CPU.prototype.checkINTR = function()
case 1:
if ((this.intFlags & X86.INTFLAG.TRAP)) {
this.intFlags &= ~X86.INTFLAG.TRAP;
X86.fnINT.call(this, X86.EXCEPTION.TRAP, null, 11);
X86.fnINT.call(this, X86.EXCEPTION.DEBUG, null, 11);
return true;
}
break;

View file

@ -3827,6 +3827,10 @@ X86.fnFaultMessage = function(nFault, nError, fHalt)
nFault == X86.EXCEPTION.GP_FAULT && bOpcode == X86.OPCODE.INTN) {
fHalt = false;
}
} else {
if (nFault == X86.EXCEPTION.NP_FAULT && bOpcode == 0x8E) {
fHalt = true;
}
}
/*

View file

@ -401,7 +401,7 @@ X86.opMOVrd = function MOVrd()
return;
}
this.setReg(bModRM & 0x7, this.regDRn[iSrc]);
this.setReg(bModRM & 0x7, this.regDR[iSrc]);
this.nStepCycles -= 22;
@ -443,7 +443,6 @@ X86.opMOVcr = function MOVcr()
}
var bModRM = this.getIPByte();
var reg = this.getReg(bModRM & 0x7);
switch((bModRM & 0x38) >> 3) {
@ -499,14 +498,36 @@ X86.opMOVdr = function MOVdr()
X86.opUndefined.call(this);
return;
}
var regDR = this.getReg(bModRM & 0x7);
/*
* TODO: Do something with the Debug registers....
*/
this.regDRn[iDst] = this.getReg(bModRM & 0x7);
if (iDst == 7) {
var regDR7 = this.regDR[7];
if ((regDR ^ regDR7) & X86.DR7.ENABLE) {
/*
* 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);
/*
* TODO: Implement BACKTRACK for this instruction....
*/
@ -542,9 +563,7 @@ X86.opMOVrt = function MOVrt()
X86.opUndefined.call(this);
return;
}
this.setReg(bModRM & 0x7, this.regTRn[iSrc]);
this.setReg(bModRM & 0x7, this.regTR[iSrc]);
this.nStepCycles -= 12;
/*
@ -586,7 +605,7 @@ X86.opMOVtr = function MOVtr()
/*
* TODO: Do something with the Test registers....
*/
this.regTRn[iDst] = this.getReg(bModRM & 0x7);
this.regTR[iDst] = this.getReg(bModRM & 0x7);
this.nStepCycles -= 12;