PCjs Machines

Home of the original IBM PC emulator for browsers.

Logo

PCjs Blog

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 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 cpux86.js, allowing the IOPL bits to be modified in real-mode on an 80386. When I had previously tweaked setPS() to accommodate 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.

SIDEBAR

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:

br ds:dx

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 di 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 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 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 with the "m port on;m timer on" Debugger commands reveals that the problem maybe an unsupported timer command:

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.

[PCjs Machine "deskpro386"]

Waiting for machine "deskpro386" to load....

Jeff Parsons
Jul 17, 2015