Why Windows NT from October 1992 refuses to install on modern CPUs

Attempting to install the October 1992 pre-release on any CPU less than about 20 years old is likely to result in the following error message:

This is similar to the behavior of the official Windows NT 3.1 and 3.5 releases, but harder to work around, and likely to happen even on Pentiums and some 486s. The reason for that is that Microsoft knew too much, but not enough.

In the October 1992 beta version of Windows NT, the installer recognized 386, 486, and 586 CPUs. This was a change from the July 1992 pre-release, which only knew about 386s and 486s. The Pentium processor was in fact only released in March 1993, after unexpected delays.

Microsoft had clearly inside information from Intel and the October 1992 Windows NT beta uses the CPUID instruction to identify the processor. This may well be the first more or less public use of the CPUID instruction, about six months before the official release of the first processors with CPUID support.

Unfortunately Microsoft’s usage completely contradicts the published Pentium documentation. It seems safe to assume that Intel gave Microsoft early specifications which then changed before the chip was released. Why Microsoft applied this information before it could be tested on any released hardware is a different question.

What is certain is that the NT kernel (typically NTOSKRNL.EXE) detects the CPU type during startup. This is done in a function called KiSetProcessorType.

KiSetProcessorType uses the standard Intel CPU identification algorithm, with the assumption that the CPU has to be at least a 386, as it is already executing 32-bit code. First the code attempts to modify the AC (Alignment Check) bit in EFLAGS. If that succeeds, the CPU is assumed to be at least a 486. Otherwise KiSetProcessorType goes off to determine the 386 stepping by checking for specific implementation quirks.

If the CPU is determined to be at least a 486, KiSetProcessorType attempts to modify the ID bit in EFLAGS to verify CPUID support. If the bit can’t be modified, the function again goes off to determine the 486 stepping. If CPUID support is found, KiSetProcessorType executes CPUID and uses the standard technique to extract the processor family and stepping.

Except Microsoft’s code doesn’t work. It appears that some early CPUID specification presumed that only a single set of information would be returned by CPUID in EAX/EBX/ECX/EDX. Later someone realized that it would be much better to make the mechanism extensible, and the contents of EAX register determine what information CPUID returns. For the original Pentium, only EAX values of 0 and 1 were supported, and all other values returned zeros in EAX/EBX/ECX/EDX.

The writers of Windows NT were clearly unaware of this and did not set EAX to any specific value prior to executing CPUID. KiSetProcessorType copies CR0 contents into EAX on function entry, so the typical EAX value when executing CPUID might be something like 8000003Fh.

The effect this has on CPUID is processor specific. For the Pentium, the documented response is to return zeros. For the Pentium Pro and for 486s with CPUID support, the response is undocumented. For current Intel processors, the documented response is to return the data for the highest supported standard CPUID leaf. The latter behavior was verified with Core 2 and Core i7 CPUs, as well as with an old Pentium II system.

The upshot is that the October 1992 beta of Windows NT probably won’t get the expected response on any processor with CPUID support, not even on a Pentium or a 486. The detected family might end up as 0 (on a Pentium), as 1 (on at least some Pentium II systems), or some other more or less random value which the installer certainly will not like.

What can be done about this? Either run this beta version of Windows NT on a CPU old enough that it doesn’t support the CPUID instruction (a 386 or an old 486), or patch the NT kernel. If the latter strategy is chosen, the following instruction sequence

or ebx,0x00200000
push ebx

needs to be replaced with something along the lines of

xor eax,eax
inc eax
nop ...

The effect of this change is that the CPUID bit in EFLAGS is not restored (which isn’t necessary) and EAX is forced to 1, which will cause CPUID to deliver the data the rest of the routine expects. Naturally this will also require the actual CPUID to be faked, except when running on a 486 or a Pentium. But that’s a lot easier.

Alternatively the NT kernel could be patched to force a specific processor family (not using CPUID at all), though this was not explored. Note that this build of Windows NT has no checksums or other mechanisms that would make patching troublesome; simply modifying the code in the executable image is enough.

With the patched kernel in place, the Windows NT setup finally relents and does not prevent installation anymore.

Update: New information has come to light officially confirming the above speculation. Preliminary P5 (aka Pentium) documentation specified a much simpler CPUID instruction which took no input and reported processor model information in the AX register.

The information returned by CPUID was designed to match the data placed into the DX register after reset, but it was readable at any time, available from unprivileged code, and the CPUID instruction was also intended to be a serializing instruction that unprivileged code could use. The latter use is still valid, but complicated by the fact that CPUID implemented in production processors destroys the contents of EAX/EBX/ECX/EDX registers, which is hardly helpful for serialization.

The initial CPUID design did not involve EFLAGS at all and users were expected to handle the invalid opcode exception on 486 CPUs. Someone presumably explained to Intel that a less intrusive design was needed, because applications could not necessarily recover from invalid opcode exceptions. The October ’92 NT pre-release was clearly written to a newer specification which used EFLAGS, but did not yet change the operation of the CPUID instruction itself.

This entry was posted in Intel, NT. Bookmark the permalink.

15 Responses to Why Windows NT from October 1992 refuses to install on modern CPUs

  1. Yuhong Bao says:

    From http://www.sandpile.org/x86/cpuid.htm :
    “#1 According to [1] and [2] the pre-B0 step Intel P5 processors return EAX=0000_05xxh.
    #2 According to [1] and [2] the pre-B0 step Intel P5 processors don’t return a vendor ID string.”
    Also see http://fornax.elf.stuba.sk/SUPERMAN/SYSTEMS/DOS/ASM/p5asm.mac

  2. michaln says:

    Thanks for confirming what I already deduced…

  3. Rauli says:

    Wasn’t there a Cyrix CPU (P5 or P6 compatible) in which CPUID instruction could be disabled? In fact I remember it had to be enabled, because it was initially disabled. If it is correct, NT could run on that Cyrix CPU, but not on a regular Pentium… The idea of a “Designed for Cyrix” sticker on the NT box makes me laugh 🙂

  4. michaln says:

    Assuming that NT would otherwise run on such a CPU (presumably it would), then yes, disabling the CPUID capability would actually help with this NT build. The mind boggles…

    Intel was lucky that by the time they actually shipped the Pentium, there was a newer NT build without this problem. Otherwise they might have implemented some CPUID disabling hack for NT, similar to what they did a few times later.

  5. Yuhong Bao says:

    Rauli: AFAIK the Cyrix 6×86 disabled it by default. Intel tried to hide many of the new features of the Pentium from competitors by requiring an NDA to be signed. AMD reverse-engineered the new features and did the K5 with them, Cyrix only implemented the 486 features and even disabled CPUID by default.

  6. Figures as much… Just as there is some other issue installing NT 4.0 on P4’s or higher … Although I’ve had fine luck xcopy’ing a previously installed NT 4.0 with SP6a installed onto a 2Gig fat partition, and converting away to NTFS without issue…

  7. michaln says:

    The funny part of the NT 3.x installation issues is that it’s only the installer/setup that’s upset by the unknown CPU. The OS itself works just fine… And it’s not too hard to fix up the .INF file throwing the error, either.

    With NT 4 it’s worse, as the system BSODs when it finds a CPU it doesn’t like.

  8. DednDave says:

    Judging from this code:

    or ebx,0x00200000
    push ebx

    one might guess that the EFLAGS ID bit (bit 21) may have been used to select between CPUID leaf 0 and leaf 1. That would explain why a signature is returned
    in EAX, rather than the maximum supported level. There doesn’t seem to be any
    other logical explanation for explicitly setting the ID bit.

    Now, I have a question. I see in many places where this anomoly is refered to as
    “pre-B0 P5”. What does B0 mean? If I’m not mistaken, if that were a hexadecimal
    value it would be 8 bits wide, and the stepping value is only 4 bits wide.


  9. Michal Necasek says:

    The other possibility is that the ID bit in EFLAGS had to be set for the CPUID instruction to work at all. Since these were all pre-production CPUs, there is no official documentation.

    B0 is the processor manufacturing stepping, but it does not directly correspond to the ‘stepping’ part of CPUID. See for example ftp://download.intel.com/support/processors/pentium/sb/243326.pdf — A80501 Pentiums with B1 stepping had stepping 3 in CPUID, stepping C1 had 5 in CPUID, stepping D1 had 7 in CPUID (see page 4 in the document). There isn’t always a 1:1 correspondence. Don’t ask me why Intel had a “manufacturing stepping” and a “stepping” which meant almost but not quite the same thing.

  10. DednDave says:

    “Since these were all pre-production CPUs, there is no official documentation.”

    Thanks, Michal – that clears up a lot 🙂
    Other than those macro files, I guess no official documentation exists.

    Good point on bit 21 “enabling” CPUID.
    I don’t have access to any such CPU’s for testing.
    Not very likely we’d see them running Win2000 or newer, either.

    Yah – I had forgotten about the definition of “stepping”.
    Been a while since I have worked with CPUID stuff.
    The larger stepping values demark how they were sold,
    not what they report in CPUID.


  11. Yuhong Bao says:

    I guess that the fact Intel want to backport VME and PSE to 486s finally made the CPUID instruction the way it is today, right?

  12. Yuhong Bao says:

    And of course, there is also still the problem of V86 monitors trapping PUSHFD/POPFD. But I don’t think the Pentium’s (or even the 486’s) new features are particularly useful to DOS apps. Even with for example RDTSC, I think the original Pentium always trapped on execution from V86 mode regardless of CR4.TSD, so it would be fairly useless.

  13. Yuhong Bao says:

    Though it does bring up the topic of why many V86 monitors don’t allow V86 code to handle illegal opcode exceptions, when the interrupt vector was not in use by BIOS.

  14. Sean McDonough says:

    >If the latter strategy is chosen, the following instruction sequence

    [bad instruction sequence]

    needs to be replaced with something along the lines of

    [patched instruction sequence]<

    How exactly does one actually go about doing so, if I may ask? (For instance, if using a hex editor, which disk image would I have to open up, and what sequence would I have to replace with what?)

    (And how I wish that VirtualBox provided an option in the GUI to let you manually set the type of CPU for a particular VM rather than forcing you to use VBoxManage…)

  15. Michal Necasek says:

    The patching is left as an exercise for the reader. I honestly don’t remember what I did five years ago 🙂

    The VirtualBox GUI only offers options for typical users, it never provided everything that VBoxManage did. It’s probably better that way.

Leave a Reply

Your email address will not be published. Required fields are marked *