Being able to identify the processor in which your program is running, can be a
very useful feature, if not to ensure that your program will work on a wider
range of computers, at least to provide minimum compatibility and guarantee it
not to crash on some processors.
The first part of this article explains how to distinguish between older 80486
and lower processors by checking for known behaviours, while the second part
(written by Chris) takes it one step forward, explaining how to use the CPUID
instruction on newer processors, checking the ID register by means of a TFR and
how to correctly identify a Cyrix processor.
EFLAGS Register---------------
On old pre-286 CPUs, bits 12 through 15 of the FLAGS register are always set,
so we can check for this type of processor, in opposition to newer ones, by
attempting to clear those bits:
pushf
pop ax
and ax, 0fffh ; clear bits 12-15
push ax
popf
pushf
pop ax
and ax, 0f000h
cmp ax, 0f000h ; check if bits 12-15 are set
je _is_an_older_cpu
jne _is_a_286_or_higher
Once we know that we are at least on a 286 processor, we can then check to see
if we're on a 32-bit processor (386 or higher) or on an actual 286. For this
purpose we know that bits 12-15 of the FLAGS register are always clear on a 286
processor in real mode:
pushf
pop ax
or ax, 0f000h ; set bits 12-15
push ax
popf
pushf
pop ax
and ax, 0f000h ; check if bits 12-15 are clear
jz _is_a_286
jnz _is_a_386_or_higher
If instead, the processor is running in protected mode these bits are used for
the IOPL (bits 12-13) and NT (bit 14) flags. Note that bits 12-14 hold the last
value loaded into them on 32-bit processors in real mode. Also remember that
there is no virtual-8086 mode on 16-bit processors.
In order to find out if the processor is in real or protected mode we must test
if the Protection Enable flag (bit 0 of CR0) is set, if so then we're in
protected mode:
smsw ax
and ax, 0001h ; check if bit 0 (PE) is clear
jz _real_mode
jnz _protected_mode
To find out if it is a 486 or a newer processor we'll try to set the AC flag
(bit 18), since it is always clear on a 386 processor (also NexGen Nx586),
unlike newer ones that allow it to be toggled:
pushfd
pop eax
mov ebx,eax
xor eax,40000h ; toggle bit 18
push eax
popfd
pushfd
pop eax
xor eax,ebx ; check if bit 18 changed
jz _is_a_386
jnz _is_a_486_or_higher
And finally to check if we're in an old 486 or in a new 486 and other newer
processors (i.e. Pentium), we'll try to toggle the ID flag (bit 21) which
indicates the presence of a processor that supports the CPUID instruction. This
part is explained below in a section about CPUID.
PUSH SP Instruction
-------------------
Before the 286, processors implemented the "PUSH SP" instruction in a different
way, updating the stack pointer before the value of SP is pushed onto the
stack, unlike newer processors which push the value of the SP register as it
existed before the instruction was executed (both in real and virtual-8086
modes).
(credit for the PUSH SP algorithm representation goes to Robert Collins)
So all one has to do is see if the values of the SP register are different
before and after the PUSH SP:
push sp
pop ax
cmp ax, sp ; check if SP values differ
je _is_a_286_or_higher
jne _is_an_older_cpu
Note - If you want the same result on all processors, use the following code
instead of a PUSH SP instruction:
push bp
mov bp, sp
xchg bp, [bp]
Shift and Rotate Instructions
-----------------------------
Starting with the 186/88, all processors mask shift/rotate counts by modulo 32,
restricting the maximum count to 31 (in all operating modes, including the
virtual-8086 mode). Earlier CPUs do not mask the shift/rotation count, using
all 8-bits of CL. So, if we try to perform a 32-bit shift, on newer processors
we'll end up with the same result (since the shift count is masked to 0),
whereas on an older processor the result will be zero:
mov ax, 0ffffh
mov cl, 32
shl ax, cl ; check if result is zero
jz _is_an_older_cpu
jnz _is_a_18x_or_higher
MUL Instruction
---------------
NEC processors differ from Intel's with respect to the handling of the zero
flag (ZF) during a MUL operation. While a NEC V20/V30 does not clear ZF after a
non-zero multiplication result, but only according to it, an Intel 8086/88 will
always clear it (note that this is only true for the specified processors):
xor al, al ; force ZF to set
mov al, 40h
mul al ; check if ZF is clear
jz _is_a_NEC_V20_V30
jnz _is_an_Intel_808x
In addition to the list of sites where you can find more information, provided
by Chris at the end of this article, you can also try this one:
And also the following packages/programs (available somewhere in the net):
The Undocumented PC (Frank van Gilluwe)
HelpPC (David Jurgens)
80x86.CPU file (Christian Ludloff)
ID Register
-----------
Beginning with the 80386 processor, Intel included a so-called ID register,
which contains information about the processor model and stepping. This
register is accessible in an unusual way - it is passed in DX after reset.
To read the ID register one must proceed the following steps:
1. By storing value 0Ah (resume with jump) at address 0Fh (reset code) in the
CMOS data area, inform BIOS not to issue POST after reset, but to return
the control to the program.
2. Update after-reset-far-jump address at 0040h:0067h.
3. Set shutdown status word (0040h:0072h) to 0, to avoid undesirable
side-effects.
4. Cause a reset.
Causing a reset is typically done by issuing a so-called triple-fault-reset,
i.e. causing an error from which the processor cannot recover and enters
a reset state. TFR (triple...) can be done only if we have enough control
over the processor, i.e. under plain DOS in real mode (no EMS) or under
Win'95 (this is risky). The following code shows how to do it in DOS. The code
is assumed to be in a COM program.
GDT dd 0, 0 ; Selector 0 is empty
dd 0000FFFFh, 00009A00h ; Selector 8 - code segment
GDTR dw 000Fh, 0, 0 ; Limit 0Fh - two selectors
IDTR dw 0, 0, 0 ; Empty IDT will cause TFR
section .text
; Ensure that we are in real mode, not in V86
smsw ax
and al, 1
jnz near _skip_tfr_since_in_v86_mode
; Update code descriptor as we are going to enter pmode
xor eax, eax
mov ax, cs
shl eax, 4
or [GDT+10], eax
add eax, GDT
mov [GDTR+2], eax
; Update reset code in CMOS data area
cli ; Disable interrupts
mov [SaveSP], sp ; Save stack pointer
mov al, 0Fh ; Address 0Fh in CMOS area
out 70h, al
times 3 jmp short $+2 ; Short delay
mov al, 0Ah ; Value 0Ah - far jump
out 71h, al
; Update resume address
push word 0
pop es
mov [es:0467h], word _tfr ; offset
mov [es:0469h], cs ; segment
mov [es:0472h], word 0 ; Update shutdown status
; Switch to pmode
lgdt [GDTR] ; Load GDT
lidt [IDTR] ; Load empty IDT
smsw ax
or al, 01h ; Set pmode bit
lmsw ax
jmp 0008h:_reset ; Reload CS
_reset: mov ax, [cs:0FFFFh] ; Reach beyond segment limit
; After reset we are here with DX containing the ID register
_tfr: cli
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, [SaveSP]
sti
This format of the ID register was used in Intel 386 processors (all except
RapidCAD), AMD 386 processors and most of IBM 486 processors.
Another format of the ID register was introduced with Intel 486 processors.
This format is similar to the format of CPUID model information (see below),
and until the Pentium was kept the same. However newer processors do not keep
any useful information in the ID register (it is usually 0). This also concerns
Cyrix 486 processors.
bits 15..14 - unused, zero
bits 13..12 - typically indicate overdrive
bits 11..8 - model
bits 7..4 - stepping
bits 3..0 - revision
And some example ID register values with this format for Intel processors:
0401 i486DX-25/33
0421 i486SX
0451 i486SX2
Cyrix DIR
---------
All Cyrix processors have a Device-Identification-Registers, which are used to
identify these processors. To read DIRs, one first has to determine that he
uses a Cyrix processor. This can be accomplished in two ways:
1. On modern processors using CPUID instruction.
2. On first Cyrix processors issuing 5/2 method.
If there is no CPUID instruction, one has to use the other way of
determination. If one knows that he is on a 486 processor, he can use the
following code:
mov ax, 0005h
mov cl, 2
sahf
div cl
lahf
cmp ah, 2
je _we_are_on_cyrix
jne _this_is_not_cyrix
Once we have determined we are on a Cyrix processor, we can read its DIRs to
get its model and stepping information. All Cyrix processors have their special
registers accessible through ports 22h and 23h. Port 22h keeps register number
and port 23h register value.
; This function reads a Cyrix control register
; It expects a register address in AL and returns value also in AL
ReadCCR: out 22h, al ; select register
times 3 jmp short $+2 ; delay
in al, 23h ; get register contents
ret
DIRs have offsets 0FEh (DIR1) and 0FFh (DIR0). DIR1 contains revision, while
DIR0 contains model/stepping. The following code reads them:
mov al, 0FEh
call ReadCCR
mov [DIR1], al
mov al, 0FFh
call ReadCCR
mov [DIR0], al
CPUID Instruction
-----------------
All newer processors have the CPUID instruction, which helps to identify on
what processor we are. Before using it, we must first determine if it is
supported, by flipping the ID flag (bit 21 of EFLAGS).
pushfd
pop eax
xor eax, 00200000h ; flip bit 21
push eax
popfd
pushfd
pop ecx
xor eax, ecx ; check if bit 21 was flipped
jnz _cpuid_supported
jz _no_cpuid
The only problem may be that NexGen processors do not support the ID flag, but
they do support the CPUID instruction. To determine that, we must hook Invalid
Opcode exception (int6) and execute the instruction. If the exception is
triggered, CPUID is not supported.
Also some early Cyrix processors (namely 5x86 and 6x86) have the CPUID
instruction disabled. To enable it, we must first enable extended CCRregisters
and then enable the instruction, setting bit 7 in CCR4.
; Enable extended CCRs
mov al, 0C3h ; C3 corresponds to CCR3
call ReadCCR
and ah, 0Fh ; bits 7..4 of CCR3 <- 0001b
or ah, 10h
call WriteCCR
; Enable CPUID
mov al, 0E8h ; E8 corresponds to CCR4
call ReadCCR
or ah, 80h ; bit 7 enables CPUID
call WriteCCR
The following functions are used to read/write CCRs:
ReadCCR: out 22h, al ; Select control register
times 3 jmp short $+2
xchg al, ah
in al, 23h ; Read the register
xchg al, ah
ret
WriteCCR: out 22h, al ; Select control register
times 3 jmp short $+2
mov al, ah
out 23h, al ; Write the register
ret
After enabling CPUID we must test if it is supported by flipping the ID flag,
unless of course we have determined that we are not on a 5x86 or 6x86 by
reading DIRs.
Once we have determined that CPUID is supported, we can use it to identify the
processor. The instruction expects EAX to hold a function number and returns
information corresponding to this number in EAX, ECX,EDX and EBX. The two most
important levels are listed below.
level 0 (eax=0) returns:
eax Maximum available level
ebx:edx:ecx Vendor ID in ASCII characters
Intel - "GenuineIntel" (ebx='Genu', bl='G'(47h))
AMD - "AuthenticAMD"
Cyrix - "CyrixInstead"
Rise - "RiseRiseRise"
Centaur - "CentaurHauls"
NexGen - "NexGenDriven"
UMC - "UMC UMC UMC "
level 1 (eax=1) returns:
eax bits 13..12 0 - normal
1 - overdrive
2 - secondary in dual system
bits 11..8 model
bits 7..4 stepping
bits 3..0 revision
If Processor Serial Number is enabled, all 32
bits are treated as the high bits (95..64) of
the number.
edx Processor features (e.g. bit 23 indicates MMX)
There are also other levels, i.e. level 2 returns cache and TLB descriptors,
level 3 the rest of Processor Serial Number.
Other processors (AMD, Cyrix) also support extended levels. The first extended
level is 80000000h and it returns in EAX the maximum extended level. These
extended levels return information specific to that processors, e.g. 3DNow!
support or processor name.
This example code determines MMX support:
; First check maximum available level
xor eax, eax ; eax = 0 (level 0)
cpuid
cmp eax, 0
jng _no_higher_levels
; Now check MMX support
mov eax, 1 ; level 1
cpuid
test edx, 00800000h ; bit 23 is set if MMX is supported
jnz _mmx_supported
jz _no_mmx
As this is not the place for listing all the available information about what
values are returned by CPUID, ID register or DIRs, you should get the most
recent information from the processor vendors:
www.intel.com
www.amd.com
www.cyrix.com
Also you can find very valuable information about the identification topic on: