Geoff Chappell - Software Analyst
Intel-compatible processors from any one vendor are identified by a combination of family, model and stepping numbers (in order of decreasing significance). You might think these are so easily and reliably established that there can’t be much that’s worth writing on the matter. But there have long been quirks.
The primary means of identifying a modern x86 processor is of course the cpuid instruction, which was introduced with some models of Intel’s 80486 processor. Only recently has the kernel stopped defending against being run on a processor that predates the instruction. Up to and including version 6.2, the kernel regards the cpuid instruction as unimplemented if either:
With no cpuid instruction, the processor must be an 80386 or 80486 (discussed below). The cpuid instruction takes a leaf number in the eax register. Execution with zero in eax produces in eax the maximum supported leaf. The family, model and stepping are expected from leaf 1. Starting with version 3.50, if cpuid leaf 0 does not return at least 1 in eax, then although the cpuid instruction is implemented, it is not the slightest bit useful to the kernel and is dismissed as unsupported, such that the processor must be an 80386 or 80486.
Before the version 4.0 from Windows NT 4.0 SP6, the kernel also regards cpuid as unusable if leaf 0 returns with eax greater than 3. Instead of writing off the processor as an 80386 or 80486, the kernel assigns it to family 5, model 0 and stepping 0, as if to recognise a Pentium. Whatever the merits as a defence against implausibility when cpuid was a relatively new and perhaps unstable facility, it risked a future compatibility problem with any processors that genuinely are advanced enough to have a cpuid leaf 4 or 5, etc. When these early Windows versions are run on such a processor, the non-recognition of cpuid has (at best) the unhappy consequence that even functionality that might have been learnt from cpuid leaves 1, 2 and 3 instead goes unused.
Later processors from Intel can be configured to limit the returned maximum to 3 by setting bit 22 in the model-specific register IA32_MISC_ENABLE (0x01A0). Intel’s literature suggests that the “BIOS should contain a setup question that allows users to specify when the installed OS does not support CPUID functions greater than 3.”1 It is not known whether these early versions of Windows were the one and only cause, but if you wanted to install any of them on any computer manufactured in the last 20 years or so, you might do well to look for this BIOS option.
You might think that there the matter ends, but this would be to underestimate how compatibility problems too easily only look to have been solved. As Windows itself advances in its ability to use the increasing capabilities of Intel’s processors, and even begins to regard new features as highly desirable if not yet essential, there comes a question of whether Windows should want to respect a BIOS setting that looks for all the world like a safety provision for a long-gone version and which might only have got turned on by oversight. In version 6.0 and higher, if the identification algorithms described below show that the kernel is being run on a sufficiently modern processor, then the kernel clears bit 22 in IA32_MISC_ENABLE so that it does not miss out on the higher cpuid functions. What counts as sufficiently modern is a genuine Intel processor for which either
Having confirmed that the cpuid instruction is usable, the kernel accepts whatever it gives for the family, model and stepping. In early Windows versions, this is as simple as proceeding to leaf 1 and picking out the family, model and stepping as bit fields from whatever is returned in eax. But as processors advanced, the interpretation got complicated, first just for the bit fields, and then because of vendor-specific differences.
Execution with 0 in eax returns something in eax, as noted above, but it also returns a vendor identifier in other registers. If the values returned in ebx, edx and ecx are stored at successive memory locations, they read as a string of single-byte characters. Starting with the builds of version 5.1 from Windows XP SP2 and of version 5.2 from Windows Server 2003 SP1, the kernel knows it cannot safely interpret the family, model and stepping from leaf 1 without knowing the vendor string from leaf 0.
The family, model and stepping are bit fields in the processor signature that is returned in eax when cpuid is executed with 1 in eax:
|stepping||0 to 3||3.51 and higher|
|model||4 to 7||3.51 and higher|
|family||8 to 10||4.0 before SP6|
|8 to 11||4.0 from SP6, and higher|
|extended model||16 to 19||5.1 before Windows XP SP2;
5.2 before Windows Server 2003 SP1
|family is 15|
|5.1 from Windows XP SP2 and higher;
5.2 from Windows Server 2003 SP1 and higher;
6.0 to 6.1
|family is 15;
or family is 6 and vendor string is GenuineIntel
|6.2 and higher||family is 15;
or family is 6 and vendor string is GenuineIntel or CentaurHauls
|extended family||20 to 27||5.1 and higher||family is 15|
The family and model were originally 4-bit numbers, which early versions of the Windows kernel take not quite directly from corresponding 4-bit fields. That early versions of Windows looked only for a 3-bit family surely caused a lot more trouble than first appears. It plausibly is even the reason that no known processor has a family between 8 and 14: a new processor with any such family would have looked to early Windows versions like an old processor with a family between 0 and 6.
Recent processors provide for 8-bit family and model numbers, each composed from two fields: the original and an extension. It is this composition that has the vendor-specific differences.
Version 5.1 is the first to recognise the extended fields. The 8-bit family and model are both indicated when the 4-bit family field is full, i.e., contains 15. The 8-bit family is then formed by adding the 4-bit family field, i.e., 15, to the 8-bit extended family field. The 8-bit model is formed by taking the 4-bit model field for the low bits and the 4-bit extended model field for the high bits.
Starting with the builds of version 5.1 from Windows XP SP2 and of version 5.2 from Windows Server 2003 SP1, the kernel knows a vendor-specific additional case. If the vendor string is GenuineIntel, then the 8-bit model alone is indicated when the 4-bit family field contains 6. In this case, the family truly is 6 but the 8-bit model is again formed by taking the 4-bit model field for the low bits and the 4-bit extended model field for the high bits.
Only in version 6.2 and higher does the kernel recognise that Intel’s addition to the interpretation applies also to processors whose vendor string is CentaurHauls.
The processor signature predates the cpuid instruction, but only as the initial value of the edx register immediately after the processor is reset. Many, if not all, computers with these processors have BIOS support through which this value can be retrieved on a running machine. This magic involves resetting the processor without losing memory, having configured the BIOS not to reinitialise as if from a reboot but to resume execution at a given address, such that the edx register is preserved from the reset. This is perhaps a bit much for the kernel, if not for anyone. When faced with a processor that does not have a cpuid instruction to report the processor signature, the kernel invents family, model and stepping numbers from the results of various tests. How closely these correspond with the actual processor signature (or with information from Intel) is not known.
Of course, with the Pentium being effectively a minimal requirement in Windows XP and higher (because of CMPXCHG8B support), the tests that the Windows kernel uses for identifying processors that do not support cpuid are surely of interest only to historians and perhaps to hobbyists who have enough time on their hands to try running a modern Windows on an 80486 for the dubious fun of seeing what happens. The code for testing that the CPU is an 80386 or 80486, and then for identifying what type of 80386 or 80486, is unchanged, byte for byte, from version 3.10 until it was discarded for version 6.3.
Given a processor that has no cpuid instruction that implements at least leaf 1, the kernel looks to the AC bit (masked by 0x00040000) in the eflags register. If this can be changed, then the processor is deemed to be an 80486 (family 4). Otherwise, it is an 80386 (family 3).
Some 80486 processors, perhaps even many, do implement the cpuid instruction acceptably. For those that do not, the kernel tests successively for what seem mostly to be defects. Any 80486 that passes all the tests is said to be model 3.
|4||0||0||ET bit (0x10) of cr0 can be cleared|
|4||1||0||reading dr4 causes Invalid Opcode exception|
|4||2||0||numeric coprocessor not present;
or pseudo-denormal not normalised for fractional fscale
According to Intel (see, for instance, section 17.17.1 of the Intel 64 and IA-32 Architectures Software Developer’s Manual Volume 3A: System Programming Guide, Part 1), the ET bit of cr0 is hard-wired to 1 for Intel486 processors. Presumably then, it can be changed on some early 80486 processors only as a defect, which distinguishes what Microsoft regards as model 0.
The dr4 register has long been documented as reserved, but Intel notes (see sections 17.22.3 and 18.2.2) that it has long been aliased to dr6. Intel gives no indication of when this started, but Microsoft seems to think it begins after what Microsoft calls model 1.
Detection of a numeric coprocessor is a standard test. The kernel clears the MP, EM, TS and ET bits of the cr0 register, initialises the floating-point unit (FPU) and reads the floating-point status word. An FPU is present if all flags in the low byte are clear. With the test done, the kernel sets the EM, TS and NE bits in cr0, and also the ET bit if the coprocessor was detected.
Model 2 is identified by a defect in the fscale instruction’s handling of pseudo-denormals. These are 80-bit floating-point numbers that have zero as the biased exponent and 1 as the integer part. They ought never be given as operands, but are tolerated for compatibility. They supposedly cannot be generated as the result of a floating-point operation. They, along with actual denormals, are supposed to be normalised automatically if the Denormal Operand exception is masked. Scaling by a fraction leaves a normalised operand unchanged. Model 2 is apparently defective in that fractional scaling leaves a pseudo-denormal operand un-normalised. For testing the fscale instruction, the kernel clears the MP, EM, TS and ET bits of the cr0 register and masks all floating-point exceptions (by setting the low 8 bits of the floating-point control word). The 80-bit pseudo-denormal used for the test is zero except for having 1 as its integer part. If scaling this pseudo-denormal by 0.5 leaves the exponent as zero, then the processor is model 2.
Finer identification of 80386 processors is largely academic. Whatever the model or stepping, the 80386 processor is unsupported since version 4.0, and soon causes the bug check UNSUPPORTED_PROCESSOR (0x5D), though not without the kernel having worked its way through more tests for defects to identify models and steppings. For any 80386 processor that passes all tests, the model and stepping leap ahead to 3 and 1.
|3||0||0||32-bit mul not reliably correct|
|3||1||0||supports xbts instruction|
|3||1||1||set TF bit (0x0100) in eflags causes Debug exception (interrupt 0x01) only at completion of rep movsb|
Versions 3.50 and 3.51 reject any 80386 that doesn’t pass all three tests. The bug check in this case, however, is HAL_INITIALIZATION_FAILED (0x5C). Version 3.10 doesn’t have a bug check for this but instead displays the following text message and hangs:
Your system may be using an early version of the Intel 386(tm) DX CPU which is not supported in this beta version of Windows NT. Please contact Intel at 1-800-228-4561, in Europe at 44-793-431144, or 1-503-629-7354 to determine if you need to acquire an Intel 386 CPU upgrade.
The particular multiplication that distinguishes model 0 is of 0x81 by 0x0417A000. This same test was used by Microsoft at least as far back as Windows 3.10 Enhanced Mode, to advise
The Intel 80386 processor in this computer does not reliably execute 32-bit multiply operations. Windows usually works correctly on computers with this problem but may occasionally fail. You may want to replace your 80386 processor. Press any key to continue...
The instruction whose support is tested for model 1 stepping 0 has opcode bytes 0x0F 0xA6 followed by a Mod R/M byte and by whatever more this byte indicates is needed. This opcode is disassembled as xbts by Microsoft’s DUMPBIN utility from Visual C++, and has been since at least the mid-90s. However, the same opcode was apparently used for the cmpxchg instruction on some 80486 processors. The confusion seems to have left a lasting mark: Intel’s opcode charts leave 0x0F 0xA6 unassigned even now. The specific test performed by the Windows kernel is to load eax and edx with zero and ecx with 0xFF00. If executing xbts ecx,edx does not cause an Invalid Opcode exception and clears ecx to zero (which cmpxchg ecx,edx would not), then xbts is deemed to be supported and the processor is model 1 stepping 0. This case of 80386 processor also was known to Windows 3.10 Enhanced Mode, and was rejected as fatal:
Windows may not run correctly with the 80386 processor in this computer. Upgrade your 80386 processor or start Windows in standard mode by typing WIN /s at the MS-DOS prompt.
When string instructions such as movsb are repeated because of a rep prefix, each operation is ordinarily interruptible. As Intel says (for rep in the Intel 64 and IA-32 Architectures Software Developer’s Manual Volume 2B: Instruction Set Reference N-Z), this “allows long string operations to proceed without affecting the interrupt response time of the system.” It ordinarily applies also to the Debug exception, such as raised by the processor at the end of executing an instruction for which the TF bit is set in the eflags when the instruction started. Programmers may have noticed this in the real world of assembly-language debugging. If the debugger actually does implement its trace command as a trace, as opposed to setting an int 3 breakpoint where the instruction is calculated to end, then a two-byte rep movsb may take many keystrokes to trace through! That model 1 stepping 1 traces through a rep movsb without interruption may be helpful when debugging, but it is surely a defect.
The immediate place at which the kernel stores the results of this identification is in the formally opaque KPRCB structure, reachable from (and embedded in) the partially opaque KPCR structure. Each processor has its own KPCR structure. The kernel defines a segment for each KPCR and ordinarily keeps the corresponding selector in the processor’s fs register. In the KPCR, the SelfPcr member holds an address for access to the KPCR without further need of the segment override, and the Prcb member points to the KPRCB. For both structures, Microsoft’s names for all members are known from type information in the widely published symbol files for Windows 2000 SP3 and higher (and finding the offsets for earlier versions is a relatively simple exercise in reverse engineering). The KPRCB members that are relevant to CPU identification as detailed above are:
|0x18 (3.10 to 6.1);
|0x19 (3.10 to 6.1);
|1, if cpuid instruction is supported;
|0x1A (3.10 to 6.1);
|0x1B (3.10 to 6.1);
|0x1BA8 (6.0 before SP1);
0x1C28 (6.0 starting with SP1);
0x03C4 (6.1 to 6.2);
|numeric code for identified vendor|
0x0A78 (5.2 before SP1);
0x0B60 (5.2 starting with SP1);
0x1BAC (6.0 before SP1);
0x1C2C (6.0 starting with SP1);
0x3C7C (6.2 to 6.3);
UCHAR VendorString [0x0D];
|vendor string, null-terminated|
The CpuType, CpuID, CpuStepping and CpuModel are at reliable positions all the way back from version 3.10 until a change for Windows 7. The CpuStepping and CpuModel members are also accessed as one 16-bit word, named CpuStep. Beware that the CpuType member corresponds most closely with what the Intel literature describes as the family, not the type.
The offsets for CpuVendor and VendorString vary even from one build to another. They are surely not intended to be accessed programmatically from outside the kernel, but they can be useful to know when debugging and they are anyway a natural reference point for tracking which vendors had to wait for which Windows versions. The VendorString is saved not long after the identification of family, model and stepping. All versions except 3.10 match it against supported vendors. In version 4.0 and higher, identification is codified as a vendor number. Version 6.0 starts storing that vendor number in the KPRCB as the CpuVendor. Microsoft’s programmatic names for the possible vendor numbers are available, as type information for a CPU_VENDORS enumeration, from symbol files for the kernel and some other modules in some Windows versions. They are more formally disclosed in a header file, NTOSP.H, from a release of the Windows Driver Kit (WDK) for Windows 10. The same header’s definition of an “architecturally defined section” of the KPRCB structure has a comment that suggests CpuType, CpuID, CpuStepping, CpuModel and even CpuVendor are now at stable positions.
|Vendor Number||Symbolic Name||Vendor String||Applicable Versions|
|1||CPU_INTEL||GenuineIntel||3.50 and higher|
|2||CPU_AMD||AuthenticAMD||3.50 and higher|
|3||CPU_CYRIX||CyrixInstead||4.0 and higher|
|4||CPU_TRANSMETA||GenuineTMx86||5.1 and higher|
|CentaurHauls||5.1 and higher|
|6||CPU_RISE||RiseRiseRise||5.1 from Windows XP SP2;
5.2 from Windows Server 2003 SP1;
6.0 and higher
The vendor number is 0 (CPU_NONE) if no vendor string is obtained (because cpuid is absent or inadequate). A vendor number greater than any that is specifically supported for the version means that a vendor string is obtained but is not recognised. For all recent versions, this value is 7 (CPU_UNKNOWN).
No interface is known for retrieving each processor’s identification from its KPRCB. However, all known versions of the kernel write the details to the registry.
Data for the Identifier value is typically x86 Family family Model model Stepping stepping, in which the placeholders are resolved as decimal numbers from CpuType, CpuModel and CpuStepping respectively. For processors on which a usable cpuid instruction is unavailable, the data for the Identifier value takes the form 80x86-yz , where x, y and z represent the family, model and stepping as found by testing for defects, but with y as a letter (A for 0, B for 1, etc).
The data for the VendorIdentifier value is the vendor string. Except for the curiosity noted in the next paragraph, this value is not set unless a vendor string is known from the cpuid instruction. In versions 4.0 and higher, the data is set from the VendorString member of the KPRCB. Though earlier versions also record the VendorString, they get the vendor string afresh when writing to the registry.
Some Cyrix processors that do not support cpuid can be recognised by the presence of configuration registers that are accessible through I/O ports 0x22 and 0x23. For these, the kernel sets the VendorIdentifier value to CyrixInstead without having recorded a VendorString in the KPRCB. The relevant code is as old as version 3.50 even though it’s not until version 4.0 that the kernel knows anything of CyrixInstead as a vendor string from cpuid. It’s retained even as late as version 10.0 which has no code for discovering that cpuid is not supported.
 Or so it was written in the Intel 64 and IA-32 Architectures Software Developer’s Manual Volume 3B: System Programming Guide, Part 2, Appendix B Model-Specific Registers (MSRs), dated November 2006. Ten years later, the Intel 64 and IA-32 Architectures Software Developer’s Manual Volume 3C: System Programming Guide, Part 3, Chapter 35 Model-Specific Registers (MSRs), dated April 2016, has it that the returned maximum is 2.