Statistics

Members: 1927
News: 293
Web Links: 1
Visitors: 3961992

Who's Online

We have 1 guest online
Damn Vulnerable LinuxDamn Vulnerable Linux (DVL) is a Linux-based (modified Damn Small Linux) tool for IT-Security & IT-Anti- Security and Attack & Defense. [CLICK HERE FOR MORE INFOS! ]

Featured Conference Video

T16-Recon2006-Joe_Stewart-OllyBonE.gif OllyBone - Semi-Automatic Unpacking on IA-32. View the conference video here!
Home arrow Submit Your Paper!
Processor Identification
User Rating: / 1
PoorBest 
Written by Chris Dragan & Chili   


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      isan_older_cpu
jne     isa286or_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      _isa286
jnz     isa386or_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      realmode
jnz     protectedmode

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      _isa386
jnz     isa486or_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).
  Older CPUs            286+
{                     {
SP = SP - 2           TEMP = SP
SS:SP = SP            SP = SP - 2
}                      SS:SP = TEMP
}

(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      isa286or_higher
jne     isan_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      isan_older_cpu
jnz     isa18xor_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      isaNECV20_V30
jnz     isan_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:

{http://grafi.ii.pw.edu.pl/gbm/x86/} (Grzegorz Mazur)

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.

;------------------------------------------------------------------------------

section .data

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 skiptfr_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

;------------------------------------------------------------------------------

Of course there are also other ways of reading the ID register. They are well described in DDJ (www.x86.org).

As said before, the ID register contains information about processor model and stepping. The format of the register is as follows:

        bits 15..12     - stepping
bits 11..8      - model
bits 7..0       - revision

Some example ID register values:

        0303    i386DX
2303    i386SX
3301    i376

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      weare_on_cyrix
jne     thisis_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

Example DIR0 values:

        1B      Cx486DX2
31      6x86(L) clock x2
55      6x86MX clock x4

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     cpuidsupported
jz      nocpuid

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     nohigher_levels
; Now check MMX support
mov     eax, 1          ; level 1
cpuid
test    edx, 00800000h  ; bit 23 is set if MMX is supported
jnz     mmxsupported
jz      nommx

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:

        www.sandpile.org
www.x86.org
www.cs.cmu.edu/~ralf/files.html