135 lines
7.6 KiB
Markdown
135 lines
7.6 KiB
Markdown
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](/modules/pcjs/lib/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.
|
|
|
|
[Embedded DeskPro 386](/devices/pc/machine/compaq/deskpro386/vga/4096kb/machine.xml "PCjs:deskpro386::uncompiled:debugger")
|
|
|
|
*[@jeffpar](http://twitter.com/jeffpar)*
|
|
*July 17, 2015*
|