Cleaned up segment zeroing rules and added Windows 95 debugger configuration

This commit is contained in:
Jeff Parsons 2016-03-21 14:36:38 -07:00
commit f94467168d
12 changed files with 2424 additions and 2320 deletions

View file

@ -12,4 +12,4 @@ COMPAQ DeskPro 386 Machines
* [COMPAQ DeskPro 386 with 2Mb RAM and COMPAQ VGA (Debugger)](other/2048kb/debugger/),([Backtrack](other/2048kb/debugger/backtrack/))
* [COMPAQ DeskPro 386 with 2Mb RAM and IBM VGA](vga/2048kb/) ([Debugger](vga/2048kb/debugger/))
* [COMPAQ DeskPro 386 with 4Mb RAM and IBM VGA](vga/4096kb/) ([Debugger](vga/4096kb/debugger/))
* [COMPAQ DeskPro 386 with 4Mb RAM and IBM VGA running Windows 95](/disks/pc/windows/win95/4.00.950/)
* [COMPAQ DeskPro 386 with 4Mb RAM and IBM VGA running Windows 95](/disks/pc/windows/win95/4.00.950/) ([Debugger](/disks/pc/windows/win95/4.00.950/debugger/))

View file

@ -26,7 +26,7 @@ Windows 95 was released to manufacturing on July 14, 1995 and went on sale at mi
It is shown running below, following a "Compact Installation" on a 68Mb hard disk. Before the machine can
start, it must download the disk image, which may take a minute or two, depending on the speed of your
internet connection.
internet connection. You can also run Windows 95 with the [PCjs Debugger](debugger/).
More information about this Windows 95 demo is available in the [PCjs Blog](/blog/2015/09/21/).

View file

@ -0,0 +1,24 @@
---
layout: page
title: "Microsoft Windows 95 (First Retail Release) with Debugger"
permalink: /disks/pc/windows/win95/4.00.950/debugger/
machines:
- type: pc
id: deskpro386
debugger: true
state: /disks/pc/windows/win95/4.00.950/deskpro386.json
config: /devices/pc/machine/compaq/deskpro386/vga/4096kb/debugger/machine.xml
drives: '[{name:"68Mb Hard Disk",type:4,path:"http://archive.pcjs.org/disks/pc/fixed/68mb/win95.json"}]'
autoMount: ''
---
Microsoft Windows 95 (First Retail Release) with Debugger
---
Windows 95 is shown running below, following a "Compact Installation" on a 68Mb hard disk. Before the machine can
start, it must download the disk image, which may take a minute or two, depending on the speed of your
internet connection. You can also run [Windows 95](../) without the PCjs Debugger.
More information about this Windows 95 demo is available in the [PCjs Blog](/blog/2015/09/21/).
{% include machine.html id="deskpro386" %}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -99,10 +99,10 @@ var DbgAddr;
* How do you figure out where the code for GlobalAlloc is in the first place? You need to have
* BACKTRACK support enabled (which currently means running the non-COMPILED version), so that as
* the Disk component loads disk images, it will automatically extract symbolic information from all
* "NE" (New Executable) binaries on those disks, which the Debugger's "di" command can then search
* "NE" (New Executable) binaries on those disks, which the Debugger's "dt" command can then search
* for you; eg:
*
* ## di globalalloc
* ## dt globalalloc
* GLOBALALLOC: KRNL386.EXE 0001:022B len 0xC570
*
* And then you just need to do a bit more sleuthing to find the right CODE segment. And that just
@ -1506,10 +1506,10 @@ if (DEBUGGER) {
}
this.messageDump(Messages.BUS, function onDumpBus(asArgs) { dbg.dumpBus(asArgs); });
this.messageDump(Messages.MEM, function onDumpMem(asArgs) { dbg.dumpMem(asArgs); });
this.messageDump(Messages.DESC, function onDumpSel(asArgs) { dbg.dumpSel(asArgs); });
this.messageDump(Messages.TSS, function onDumpTSS(asArgs) { dbg.dumpTSS(asArgs); });
this.messageDump(Messages.DOS, function onDumpDOS(asArgs) { dbg.dumpDOS(asArgs); });
this.messageDump(Messages.MEM, function onDumpMem(asArgs) { dbg.dumpMem(asArgs); });
this.messageDump(Messages.TSS, function onDumpTSS(asArgs) { dbg.dumpTSS(asArgs); });
if (Interrupts.WINDBG.ENABLED || Interrupts.WINDBGRM.ENABLED) {
this.fWinDbg = null;
@ -2763,36 +2763,44 @@ if (DEBUGGER) {
};
/**
* dumpDOS(asArgs)
*
* Dumps DOS MCBs (Memory Control Blocks).
*
* TODO: Add some code to detect the current version of DOS (if any) and locate the first MCB automatically.
* dumpBackTrack(asArgs)
*
* @this {Debugger}
* @param {Array.<string>} asArgs
*/
Debugger.prototype.dumpDOS = function(asArgs)
Debugger.prototype.dumpBackTrack = function(asArgs)
{
var mcb;
var sMCB = asArgs[0];
if (sMCB) {
mcb = this.parseValue(sMCB);
}
if (mcb === undefined) {
this.println("invalid MCB");
return;
}
this.println("dumpMCB(" + str.toHexWord(mcb) + ')');
while (mcb) {
var dbgAddr = this.newAddr(0, mcb);
var bSig = this.getByte(dbgAddr, 1);
var wPID = this.getShort(dbgAddr, 2);
var wParas = this.getShort(dbgAddr, 5);
if (bSig != 0x4D && bSig != 0x5A) break;
this.println(this.toHexOffset(0, mcb) + ": '" + String.fromCharCode(bSig) + "' PID=" + str.toHexWord(wPID) + " LEN=" + str.toHexWord(wParas) + ' "' + this.getSZ(dbgAddr, 8) + '"');
mcb += 1 + wParas;
var sInfo = "no information";
if (BACKTRACK) {
var sAddr = asArgs[0];
var dbgAddr = this.parseAddr(sAddr, true, true, false);
if (dbgAddr) {
var addr = this.getAddr(dbgAddr);
if (dbgAddr.type != Debugger.ADDRTYPE.PHYSICAL) {
var pageInfo = this.getPageInfo(addr);
if (pageInfo) {
dbgAddr.addr = pageInfo.addrPhys;
dbgAddr.type = Debugger.ADDRTYPE.PHYSICAL;
}
}
sInfo = this.toHexAddr(dbgAddr) + ": " + (this.bus.getSymbol(addr, true) || sInfo);
} else {
var component, componentPrev = null;
while (component = this.cmp.getMachineComponent("Disk", componentPrev)) {
var aInfo = component.getSymbolInfo(sAddr);
if (aInfo.length) {
sInfo = "";
for (var i in aInfo) {
var a = aInfo[i];
if (sInfo) sInfo += '\n';
sInfo += a[0] + ": " + a[1] + ' ' + str.toHex(a[2], 4) + ':' + str.toHex(a[3], 4) + " len " + str.toHexWord(a[4]);
}
}
componentPrev = component;
}
}
}
return sInfo;
};
/**
@ -2871,6 +2879,78 @@ if (DEBUGGER) {
this.dumpBlocks(this.cpu.aBusBlocks, asArgs[0]);
};
/**
* dumpDOS(asArgs)
*
* Dumps DOS MCBs (Memory Control Blocks).
*
* TODO: Add some code to detect the current version of DOS (if any) and locate the first MCB automatically.
*
* @this {Debugger}
* @param {Array.<string>} asArgs
*/
Debugger.prototype.dumpDOS = function(asArgs)
{
var mcb;
var sMCB = asArgs[0];
if (sMCB) {
mcb = this.parseValue(sMCB);
}
if (mcb === undefined) {
this.println("invalid MCB");
return;
}
this.println("dumpMCB(" + str.toHexWord(mcb) + ')');
while (mcb) {
var dbgAddr = this.newAddr(0, mcb);
var bSig = this.getByte(dbgAddr, 1);
var wPID = this.getShort(dbgAddr, 2);
var wParas = this.getShort(dbgAddr, 5);
if (bSig != 0x4D && bSig != 0x5A) break;
this.println(this.toHexOffset(0, mcb) + ": '" + String.fromCharCode(bSig) + "' PID=" + str.toHexWord(wPID) + " LEN=" + str.toHexWord(wParas) + ' "' + this.getSZ(dbgAddr, 8) + '"');
mcb += 1 + wParas;
}
};
/**
* dumpIDT(asArgs)
*
* Dumps an IDT vector entry.
*
* @this {Debugger}
* @param {Array.<string>} asArgs
*/
Debugger.prototype.dumpIDT = function(asArgs)
{
var sIDT = asArgs[0];
if (!sIDT) {
this.println("no IDT vector");
return;
}
var nIDT = this.parseValue(sIDT);
if (nIDT === undefined || nIDT < 0 || nIDT > 255) {
this.println("invalid vector: " + sIDT);
return;
}
var ch = '&', fProt = this.cpu.getProtMode(), fAddr32 = false;
var addrIDT = this.cpu.addrIDT + (nIDT << (fProt? 3 : 2));
var off = this.cpu.getShort(addrIDT + X86.DESC.LIMIT.OFFSET);
var sel = this.cpu.getShort(addrIDT + X86.DESC.BASE.OFFSET);
if (fProt) {
ch = '#';
var acc = this.cpu.getShort(addrIDT + X86.DESC.ACC.OFFSET);
if (acc & X86.DESC.ACC.TYPE.NONSEG_386) {
fAddr32 = true;
off |= this.cpu.getShort(addrIDT + X86.DESC.EXT.OFFSET) << 16;
}
}
this.println("dumpIDT(" + str.toHexWord(nIDT) + "): " + ch + str.toHex(sel, 4) + ':' + str.toHex(off, fAddr32? 8 : 4));
};
/**
* dumpMem(asArgs)
*
@ -2934,47 +3014,6 @@ if (DEBUGGER) {
return pageInfo;
};
/**
* dumpInfo(asArgs)
*
* @this {Debugger}
* @param {Array.<string>} asArgs
*/
Debugger.prototype.dumpInfo = function(asArgs)
{
var sInfo = "no information";
if (BACKTRACK) {
var sAddr = asArgs[0];
var dbgAddr = this.parseAddr(sAddr, true, true, false);
if (dbgAddr) {
var addr = this.getAddr(dbgAddr);
if (dbgAddr.type != Debugger.ADDRTYPE.PHYSICAL) {
var pageInfo = this.getPageInfo(addr);
if (pageInfo) {
dbgAddr.addr = pageInfo.addrPhys;
dbgAddr.type = Debugger.ADDRTYPE.PHYSICAL;
}
}
sInfo = this.toHexAddr(dbgAddr) + ": " + (this.bus.getSymbol(addr, true) || sInfo);
} else {
var component, componentPrev = null;
while (component = this.cmp.getMachineComponent("Disk", componentPrev)) {
var aInfo = component.getSymbolInfo(sAddr);
if (aInfo.length) {
sInfo = "";
for (var i in aInfo) {
var a = aInfo[i];
if (sInfo) sInfo += '\n';
sInfo += a[0] + ": " + a[1] + ' ' + str.toHex(a[2], 4) + ':' + str.toHex(a[3], 4) + " len " + str.toHexWord(a[4]);
}
}
componentPrev = component;
}
}
}
return sInfo;
};
/**
* dumpPage(asArgs)
*
@ -6376,10 +6415,11 @@ if (DEBUGGER) {
this.println("\tdw [a] [#] dump # words at address a");
this.println("\tdd [a] [#] dump # dwords at address a");
this.println("\tdh [#] [#] dump # instructions from history");
if (BACKTRACK) {
this.println("\tdi [a] dump backtrack info for address a");
}
this.println("\tdi [#] dump descriptor info for IDT #");
this.println("\tds [#] dump descriptor info for selector #");
if (BACKTRACK) {
this.println("\tdt [a] dump backtrack info for address a");
}
if (sDumpers.length) this.println("dump extension commands:\n\t" + sDumpers);
return;
}
@ -6470,7 +6510,13 @@ if (DEBUGGER) {
if (sCmd == "di") {
asArgs.shift();
var sInfo = this.dumpInfo(asArgs);
this.dumpIDT(asArgs);
return;
}
if (sCmd == "dt") {
asArgs.shift();
var sInfo = this.dumpBackTrack(asArgs);
this.println(sInfo);
return;
}

View file

@ -240,9 +240,10 @@ var X86 = {
GATE386_CALL: 0x0C00, // 80386 and up
GATE386_INT: 0x0E00, // 80386 and up
GATE386_TRAP: 0x0F00, // 80386 and up
CODE_OR_DATA: 0x1E00,
DATA_READONLY: 0x1000,
DATA_WRITABLE: 0x1200,
DATA_EXPDOWN_READONLY: 0x1400,
DATA_EXPDOWN: 0x1400,
DATA_EXPDOWN_WRITABLE: 0x1600,
CODE_EXECONLY: 0x1800,
CODE_READABLE: 0x1A00,

View file

@ -1084,7 +1084,8 @@ X86CPU.prototype.resetRegs = function()
* of accessing or changing them, so this is an implementation detail those processors are unaware of.
*/
this.regCR0 = X86.CR0.MSW.ON;
this.addrIDT = 0; this.addrIDTLimit = 0x03FF;
this.addrIDT = 0;
this.addrIDTLimit = 0x03FF;
this.regPS = this.nIOPL = 0;// these should be set before the first setPS() call
/*
@ -1275,21 +1276,6 @@ X86CPU.prototype.resetRegs = function()
this.setProtMode();
};
/**
* zeroSeg(seg)
*
* Helper to zero a segment register as privilege transitions require.
*
* @this {X86CPU}
* @param {X86Seg} seg
*/
X86CPU.prototype.zeroSeg = function(seg)
{
if ((seg.sel & X86.SEL.MASK) && seg.dpl < this.nCPL && (seg.acc & X86.DESC.ACC.TYPE.CODE_CONFORMING) != X86.DESC.ACC.TYPE.CODE_CONFORMING) {
seg.load(0);
}
};
/**
* setAddrSize(size)
*
@ -1733,6 +1719,28 @@ X86CPU.prototype.checkMemoryException = function(addr, nb, fWrite)
}
};
/**
* getProtMode()
*
* @this {X86CPU}
* @return {boolean} true if protected-mode, false if not
*/
X86CPU.prototype.getProtMode = function()
{
return !!(this.regCR0 & X86.CR0.MSW.PE);
};
/**
* getV68Mode()
*
* @this {X86CPU}
* @return {boolean} true if V86-mode, false if not
*/
X86CPU.prototype.getV86Mode = function()
{
return !!(this.regPS & X86.PS.VM);
};
/**
* setProtMode(fProt, fV86)
*
@ -1752,12 +1760,12 @@ X86CPU.prototype.checkMemoryException = function(addr, nb, fWrite)
X86CPU.prototype.setProtMode = function(fProt, fV86)
{
if (fProt === undefined) {
fProt = !!(this.regCR0 & X86.CR0.MSW.PE);
fProt = this.getProtMode();
}
if (fV86 === undefined) {
fV86 = !!(this.regPS & X86.PS.VM);
fV86 = this.getV86Mode();
}
if (DEBUG && (!fProt != !(this.regCR0 & X86.CR0.MSW.PE) || fV86 != !!(this.regPS & X86.PS.VM)) && this.messageEnabled()) {
if (DEBUG && (fProt != this.getProtMode() || fV86 != this.getV86Mode()) && this.messageEnabled()) {
this.printMessage("CPU switching to " + (fProt? (fV86? "v86" : "protected") : "real") + "-mode", this.bitsMessage, true);
}
this.aOpGrp6 = (fProt && !fV86? X86.aOpGrp6Prot : X86.aOpGrp6Real);

View file

@ -462,9 +462,9 @@ X86.helpSRCxx = function()
X86.helpCALLF = function(off, sel)
{
/*
* Since we always push the return address AFTER calling setCSIP(), and since either push could trigger a
* fault (eg, segment fault, page fault, etc), we must not only snapshot regLSP into opLSP, but also the
* current CS into opCS, so that helpFault() can always make CALLF restartable. Ditto for opSS and the SS register.
* Since we always push the return address AFTER calling setCSIP(), and since either push could trigger
* a fault (eg, segment fault, page fault, etc), we must not only snapshot regSS and regLSP, but also regCS,
* so that helpFault() can always make CALLF restartable.
*/
this.opCS = this.getCS();
this.opSS = this.getSS();
@ -532,11 +532,7 @@ X86.helpINT = function(nIDT, nError, nCycles)
*/
X86.helpIRET = function()
{
/*
* 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.opSS = this.getSS();
this.opLSP = this.regLSP;
this.nStepCycles -= this.cycleCounts.nOpCyclesIRet;
@ -612,6 +608,7 @@ X86.helpIRET = function()
}
this.opLSP = X86.ADDR_INVALID;
this.opSS = -1;
};
/**
@ -626,11 +623,7 @@ X86.helpIRET = function()
*/
X86.helpRETF = function(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.opSS = this.getSS();
this.opLSP = this.regLSP;
var newIP = this.popWord();
@ -656,16 +649,17 @@ X86.helpRETF = function(n)
* it safe and using CODE_CONFORMING instead of CODE_CONFORMING_READABLE. Also, for the record, I've not
* seen this situation occur yet (eg, in OS/2 1.0).
*/
this.zeroSeg(this.segDS);
this.zeroSeg(this.segES);
X86.zeroSeg.call(this, this.segDS);
X86.zeroSeg.call(this, this.segES);
if (I386 && this.model >= X86.MODEL_80386) {
this.zeroSeg(this.segFS);
this.zeroSeg(this.segGS);
X86.zeroSeg.call(this, this.segFS);
X86.zeroSeg.call(this, this.segGS);
}
}
if (n == 2 && this.cIntReturn) this.checkIntReturn(this.regLIP);
this.opLSP = X86.ADDR_INVALID;
this.opSS = -1;
};
/**
@ -755,8 +749,8 @@ X86.helpFault = function(nFault, nError, nCycles, fHalt)
*
* TODO: The following opCS/opLIP/opSS/opLSP checks are primarily required for 80386-based machines
* with paging enabled, because page faults introduce a new set of complex faults that our current
* segment load "probes" are insufficient to catch. So as a stop-gap measure, we rely on these FOUR
* "snapshot" registers to temporarily resolve the general instruction restartability problem.
* segment load "probes" are insufficient to catch. So as a stop-gap measure, we rely on these four
* "snapshot" registers to resolve the general instruction restartability problem (for now).
*
* If you want to closely examine the underlying causes of these more complex faults, set breakpoints
* where indicated below, and examine the stack trace.
@ -938,12 +932,13 @@ X86.helpCheckFault = function(nFault, nError, fHalt)
* interrupts in V86-mode regardless (they generate a GP_FAULT if IOPL < 3, and even when IOPL == 3, only
* the protected-mode IDT handler gets to run).
*/
if ((this.regPS & X86.PS.VM)) {
if (this.regPS & X86.PS.VM) {
if (nFault == X86.EXCEPTION.UD_FAULT && bOpcode == X86.OPCODE.ARPL ||
nFault == X86.EXCEPTION.GP_FAULT && bOpcode == X86.OPCODE.INTN) {
fHalt = false;
}
}
// else if (DEBUG && nFault == X86.EXCEPTION.GP_FAULT && fHalt === undefined) fHalt = true;
/*
* If fHalt has been explicitly set to false, we also take that as a cue to disable fault messages
@ -1006,3 +1001,23 @@ X86.helpCheckFault = function(nFault, nError, fHalt)
}
return fHalt;
};
/**
* zeroSeg(seg)
*
* Helper to zero a segment register whenever transitioning to a less privileged (numerically higher) level.
*
* @this {X86CPU}
* @param {X86Seg} seg
*/
X86.zeroSeg = function(seg)
{
var acc = seg.acc & X86.DESC.ACC.TYPE.CODE_OR_DATA;
if (seg.sel & X86.SEL.MASK) {
if (acc == X86.DESC.ACC.TYPE.CODE_EXECONLY || // non-readable code segment (not allowed)
acc == X86.DESC.ACC.TYPE.CODE_CONFORMING || // non-readable code segment (not allowed)
acc < X86.DESC.ACC.TYPE.CODE_CONFORMING && seg.dpl < this.nCPL && seg.dpl < (seg.sel & X86.SEL.RPL)) {
seg.load(0);
}
}
};

View file

@ -805,6 +805,12 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe)
*/
if (rpl >= this.cpl) {
if (rpl > this.cpl) {
/*
* TODO: See if we can defer calling setSS() and setSP() until AFTER the final checks
* below, because if, for example, the new CS is not PRESENT, we must generate a fault,
* which in turn must restore the original stack, which means helpRETF() must snapshot
* the stack registers.
*/
regSP = cpu.popWord();
cpu.setSS(cpu.popWord(), true);
cpu.setSP(regSP);

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff