pcjs/blog/2015/07/17
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2015-08-14 12:42:39 -07:00
..
README.md More explanatory blog text 2015-08-14 12:42:39 -07:00

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.

Embedded DeskPro 386

@jeffpar
July 17, 2015