| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .. | ||
| README.md | ||
Windows 95
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 PCjs 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 PCjs 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 PCjs 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 PCjs 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.
SIDEBAR
By default, PCjs marks opcodes as invalid only if they have been confirmed invalid. PCjs 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. PCjs should have simply ignored the second prefix.
With all of the above changes in place, PCjs 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.
Update for August 13, 2015
PCjs 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 the following breakpoints and start the machine, using the PCjs Debugger input field next to the Enter button:
bp 1ED4:16B4 "let fn=ah;dos;if fn!=3f||cx!=24"
bp FDC8:422A "if fn==3f;di ds:dx;db ds:dx lcx;h;else"
g
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 di
command dumps PCjs 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 else
command will be executed, and if there is no else command, execution will stop.
Debugger expressions may contain the usual variety of arithmetic, bitwise and logical binary operators, and they are evaluated using traditional operator precedence (ie, the same as C or JavaScript); any other operators, such as parentheses, assignment operators, and unary or ternary operators, are not supported in expressions.
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 PCjs 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 PCjs Debugger commands, whereas the output textarea combines all Debugger output and WDEB386 COM2 serial port I/O. You can even use the PCjs Debugger to debug the WDEB386 debugger; just make sure the appropriate text control has focus before typing a command.
To help reduce confusion, the PCjs 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 PCjs command):
>>indicates real-mode##indicates protected-mode--indicates V86-mode
80386 Debug register (DR0-DR7) support was recently added, so even WDEB386 read/write breakpoints should work now.
@jeffpar
July 17, 2015