This week (July 14, 2015) was the 20th anniversary of Windows 95 RTM (“Release To Manufacturing”). So I decided to throw a PCjs party and try running Windows 95 Setup inside a PCx86 machine for the first time.
Sadly, it immediately failed:
Please wait while Setup initializes. Windows requires a computer with an 80386 processor or higher.
The failing code:
0E36:08FD 06 PUSH ES 0E36:08FE 1E PUSH DS 0E36:08FF 9C PUSHF 0E36:0900 33C0 XOR AX,AX 0E36:0902 50 PUSH AX 0E36:0903 9D POPF 0E36:0904 9C PUSHF 0E36:0905 58 POP AX 0E36:0906 A90080 TEST AX,8000 0E36:0909 7517 JNZ 0922 0E36:090B B80070 MOV AX,7000 0E36:090E 50 PUSH AX 0E36:090F 9D POPF 0E36:0910 FB STI 0E36:0911 9C PUSHF 0E36:0912 58 POP AX 0E36:0913 A90070 TEST AX,7000 0E36:0916 7405 JZ 091D 0E36:0918 B88603 MOV AX,0386 0E36:091B EB08 JMP 0925 0E36:091D B88602 MOV AX,0286 0E36:0920 EB03 JMP 0925 0E36:0922 B88600 MOV AX,0086 0E36:0925 9D POPF 0E36:0926 1F POP DS 0E36:0927 07 POP ES 0E36:0928 C3 RET
was easily fixed with a change to x86cpu.js, allowing the IOPL bits to be modified in real-mode on an 80386. When I had previously tweaked setPS() to accomodate 80286/80386 discrimination logic in OS/2 1.0, there was no 80386 support in PCx86 at that time, so it was sufficient to never allow the IOPL bits to be altered in real-mode.
The next problem was triggered by Setup’s CAB (“Diamond”) decompression code, which uses all 32 bits of the 80386’s 32-bit registers. That in itself was not a problem, but by leaving stray bits in the upper halves of registers like EDX, it exposed a bug in the PCx86 I/O instruction handlers, which neglected to mask EDX with 0xFFFF before performing port lookups, causing mysterious I/O failures that usually manifested themselves as hard disk I/O errors.
Then PCx86 crashed in the middle of the decompression of the first CAB file (MINI.CAB). It turned out the stack had been improperly adjusted because a “RETF n” instruction mistakenly believed that a stack switch had occurred. This was, in fact, a left-over condition from a protected-mode stack-switch. That was easily cleared.
Next, I discovered that I had never finished updating a handful of instructions to full 32-bit operation; namely, INC, DEC, NEG, NOT, TEST, MOVSB and MOVSW. Those are done now.
Then I ran into a couple of Windows 95 oddities. First, some kernel initialization code deliberately executed an invalid opcode (0x0F,0xFF) and expected its DPMI exception (0x06) handler to field the exception; that was fixed by flagging the opcode as genuinely invalid.
By default, PCx86 marks opcodes as invalid only if they have been confirmed invalid. PCx86 considers the vast majority of unused/undocumented opcodes to be merely “undefined” until I’ve seen them in the wild. An instruction will be marked invalid if I either discover that real hardware treats it as an Invalid Opcode (by throwing the exception) or that real software expects it to. For opcode 0x0F,0xFF, it was the latter.
Don’t be confused that Intel also refers to the Invalid Opcode exception (0x06) as the #UD (“Undefined”) opcode exception. Every opcode that triggers that exception is, by definition, defined: it’s defined as invalid. I consider it a misnomer to refer to any invalid instruction as “undefined”.
The other recent Windows 95 oddity I ran into was an instruction with multiple address-override (0x67) prefixes; the first prefix changed the instruction’s addressing mode from 16-bit to 32-bit, and the second prefix changed it back to 16-bit. PCx86 should have simply ignored the second prefix.
With all of the above changes in place, PCx86 v1.18.4 is able to run Windows 95 Setup a bit farther, but still far from completion. If you want to give it a spin yourself, start the machine below (click the Run button) and once it has finished booting, run SETUP from drive B, where the first Windows 95 diskette is already loaded.
If, when it crashes (and it will), you’re interested in examining the instructions that were executed prior to the crash, you can dump the instruction history buffer using the Debugger’s “dh” command.
NOTE: The diskette images contain a pre-release version of Windows 95, as I don’t currently have the RTM version on diskette.
August 13, 2015 Update
PCx86 v1.18.8 has made a little more progress running Windows 95 Setup, but CAB decompression still fails almost immediately. To monitor DOS calls until the first 36-byte read of PRECOPY1.CAB, try setting the following breakpoint and then starting the machine, using the PCx86 Debugger input field next to the Enter button:
m dos off bp 1ED4:16B4 "set fn=ah;dos;if fn!=3f||cx!=24" g
Alternatively, you can hard-code those commands into the Debugger component of the machine.xml file; eg:
<debugger id="debugger" messages="fault|tss|int" commands='m dos off;bp 1ED4:16B4 "set fn=ah;dos;if fn!=3f||cx!=24"'/>
Once the 36-byte read is hit, you’ll probably want to stop on the next instruction that examine those bytes, by using a memory read breakpoint:
and you also might want to change the first breakpoint to stop on any file read, and add a second breakpoint to dump the initial contents of those reads:
bp 1ED4:16B4 "set fn=ah;dos;if fn!=3f" bp FDC8:422A "if fn==3f;di ds:dx;db ds:dx;h;else"
In the current machine,
1ED4:16B4 is the DOS INT 0x21 entry point and
FDC8:422A is the corresponding IRET.
The first breakpoint sets an internal variable,
fn, to the value of the AH register on entry, so that the
second breakpoint can check the value on exit.
Regarding the other Debugger commands shown above, the
dos command describes the current DOS operation
(alternatively, you could use the
m int on; m dos on commands to turn on DOS interrupt messages). The
command dumps PCx86 BACKTRACK(tm) information, to help you visually confirm which INT 0x21 calls are reading
PRECOPY1.CAB. Finally, the
if command evaluates the given expression, and if the result is non-zero (“true”),
all subsequent commands are executed, up to to any
else command; otherwise, only commands after the
command will be executed, and if there is no
else command, execution will stop.
This test machine below has been updated to load WDEB386.EXE prior to starting B:SETUP.EXE, if you prefer using WDEB386. Make sure the machine is running (ie, click the Run button, or use the PCx86 Debugger “g” command), and then click on the Debugger output control to give it focus and press CTRL-C to trigger WDEB386.
The Debugger input field is used exclusively for PCx86 Debugger commands, whereas the output textarea combines all Debugger output and WDEB386 COM2 serial port I/O. You can even use the PCx86 Debugger to debug the WDEB386 debugger; just make sure the appropriate text control has focus before typing a command.
To help reduce confusion, the PCx86 Debugger displays a double-character command prefix, to differentiate its commands from WDEB386’s single-character command prompt – but it’s still easy to get confused.
A quick recap of those command prefixes (which you won’t see until AFTER you’ve typed a PCx86 command):
80386 Debug register (DR0-DR7) support was recently added, so even WDEB386 read/write breakpoints should work now.
August 21, 2015 Update
PCx86 v1.19.1 has finally solved a number of nagging bugs. The CAB decompression code itself was running fine; it would crash after loading a 32-bit value into EAX and a timer interrupt occurred. A path through the interrupt handler was trashing the upper bits of EAX. The culprit: any of the MOV instructions that move an immediate value into one of the “high” 8-bit registers (AH, BH, CH, or DH). Those instructions were failing to preserve the upper 16 bits of the entire register. Further proof that the most exasperating bugs sometimes have the most mundane causes.
Also recently fixed: the annoying VGA video glitch that would occur whenever the mouse was moved, improper updates to the ACCESSED bit in a selector’s descriptor table entry, improper error codes when a selector load generated a fault, and the RETF instruction’s failure to properly restart when the return address referred to a not-present segment.
The next problem appears to be timer-related. When Windows 95 Setup begins its hardware analysis, it
gets “stuck” in code that’s reading and writing timer ports (0x40 and 0x43). Turning on timer port messages
"m port on;m timer on" Debugger commands reveals that the problem maybe an unsupported timer
chipset.outPort(0x0043,PIT1_CTRL,0xD2) @1847:DD02 PIT1_CTRL: Read-Back command not supported (yet)
September 12, 2015 Update
Lots of bugs have been squashed in the past few weeks – not enough to finish setting up Windows 95, but it’s getting closer. More details on recent releases can be found here.
The machine below has been reconfigured with a hard disk image containing all the Windows 95 Setup files. The machine is at the point where Windows 95 SETUP has just rebooted.
July 17, 2015 (updated September 12, 2015)