• 10/05/00 04:47 PM
    Sign in to follow this  
    Followers 0

    Identification of x86 CPUs and their features with CPUID

    General and Gameplay Programming

    Myopic Rhino

    In my explorations as a beginner game-programmer across the web, I haven't found very much information on detecting the cpu information. I wanted to write a function that would check to see that the machine my game is running on is a Pentium Processor, whether or not it supports floating point math and whether or not it supports MMX. So in this article I will demonstrate how I check the computer for these qualities.

    Being an assembly programmer, and being that you need to use an assembly command to operate the CPUID instruction, my code will be in assembly, although it should all fairly easily be put it into some in-line assembly for you C and C++ users. The end result of this tutorial will be a dll which you should also be able to call from C or C++.

    So let's get started!

    [size="5"]Assembler and CPUID

    So for those of you brushing up on your assembler I'll give a quick explanation as to what assembly is and how to use it.

    Assembly is a programming language which deals directly with the cpu instructions. So each instruction or command is directly translated into an opcode, which is a hex code that the cpu can understand. So in this article we'll be using the CPUID instruction as a means to detect the features on this system.

    When Intel released the pentium cpu, they added a number of new cpu instructions. One of those new instructions being CPUID. This instruction will return information on the cpu. It will return different information on the cpu with different arguments, which are placed in the eax register (The x86 accumulator register).

    So if you place the value '0' into the accumulator and call the cpuid instruction like this:

    For example:

    mov eax, 0 ;Move 0 into the eax register
    CPUID ;Call the cpuid instruction
    mov DWORD PTR [CPUMsg[0]], ebx
    mov DWORD PTR [CPUMsg[4]], edx
    mov DWORD PTR [CPUMsg[8]], ecx
    invoke MessageBox,NULL,addr CPUMsg,addr szDLLName,MB_OK
    It will return two things. 1.) The largest value for an argument with cpuid 2.) the cpu identification string. We won't be using the 1st however the second is useful mostly in debuging. The cpu identification string is a 12 letter string returned in ebx,edx,ecx in that order (all x86 registers). This string usally tells you something about the manufacturer. For example intel's cpu identification string is "GenuineIntel", AMD's identification string is "AuthenticAMD", and Cyrix's identification string is "CyrixInstead". These strings can be lied about in clone's or are sometimes funny in engineering models and prototypes. For example early AMD engineering prototypes would return "AMD ISBETTER". So it's not too usefull in determining the cpu's features. However if you use the value '1' as the argument, it will return, in edx, the cpu feature flags. These flags are all bits within the edx register which represent the presence of a number of features. The table of cpu feature flags is as follows.

    Standard CPU features returned by CPUID with EAX=1

    [color="white"]bit[/color][color="white"]mnemonic[/color][color="white"]description[/color]0FPUFloating Point Unit1VMEV86 Mode Extensions2DEDebug Extensions - I/O breakpoints3PSEPage Size Extensions (4 MB pages)4TSCTime Stamp Counter and RDTSC instruction5MSRModel Specific Registers6PAEPhysical Address Extensions (36-bit address, 2MB pages)7MCEMachine Check Exception8CX8CMPXCHG8B instruction available9APICLocal APIC present (multiprocesssor operation support) [color="#FF0000"]AMD K5 model 0 (SSA5) only: global pages supported ![/color]10 reserved (Fast System Call on AMD K6 model 6 and Cyrix)11SEPFast system call (SYSENTER and SYSEXIT instructions) - guaranteed only if signature >= 0633!12MTRRMemory Type Range Registers13PGEPage Global Enable - global oages support14MCAMachine Check Architecture and MCG_CAP register15CMOVConditional MOVe instructions16PATPage Attribute Table (MSR 277h)17PSE3636 bit Page Size Extenions (36-bit addressing for 4 MB pages with 4-byte descriptors) 23MMXMultiMedia Extensions24FXSRFXSAVE and FXRSTOR instructions25..31 reserved So these values can all be interpretted to find out if our cpu has certain features. Continue on to see how we can interpret these!

    [size="5"]Extracting information

    So we're only really interested in Bit 0 (FPU) and Bit 23 (MMX). Although if you want to, feel free to implement more. So what I'm going to do is perform the 'and' operation on the returned flags to find the out if that bit is on. So for example if I want to check the first bit, in binary the first bit has a value of 1, the second has a value of 2, the third a value of 4 and the fifth the value of 8, double as it goes along (For you math guys it's base 2). So if I want to figure out if the first bit is on, I 'and' the original returned falgs with the value for that bit, in this case it's the value "1". For example:

    mov eax, 1 ;We want to use the value 1 as our argument
    cpuid ;call the cpuid instruction
    and edx, 1 ;'and' out the first bit
    cmp edx, 0 ;compare the result with 0
    je err ;If it's zero this feature's not supported
    ;Other wise continue
    mov eax, 1

    mov eax, 0
    So in the above code, we first put in our argument '1'. Then we call the cpuid instruction. Now we have the features flags in edx. So we 'and' bit 0, which has a value of 1. Then we can check to see if the result is 0. If it is zero then, this bit was not on, this cpu does not support this feature (FPU). So now you say what about mmx? Well here's the next code snippet:

    mov eax, 1 ;We want to use the value 1 as our argument
    cpuid ;call the cpuid instruction
    push edx ;We need to store an extra copy of the flags on the stack

    and edx, 1 ;now we can 'and' out the first bit
    cmp edx, 0 ;compare the result with 0
    je err ;If it's zero this feature's not supported

    pop edx ;Now we need to restore the flags to the original for another test
    shr edx, 23 ;Shift the flags to the right 23 times so the mmx value is in the first bit
    and edx, 1 ;Now check for mmx
    cmp edx, 0 ;Check to see if it's supported
    je err ;If it's not, jmp to err.

    ;Other wise continue
    mov eax, 1

    mov eax, 0
    So in the above code, we've done the same thing as before, except we've saved off a copy of the returned feature flags. To restore after the first test. Then we restore it to do the second test, mmx. To test for mmx, which is bit 23, we first shoft the register to the right 23 times, to put the mmx bit into the first bit. This way we can just check the first bit with a value of "1" again.

    [size="5"]Structured Exception Handling

    So we have one last, little problem, what happens if you run this code on a machine that does not support the cpuid instruction like a 486 or some of the early clone pentiums. Well you get that evil "This program has performed an illegal error" message and windows shuts your program down. That's not very professional looking, and besides maybe their computer can handle your game it's just a clone which does not support this cpuid instruction.

    Structured Exception handling to the rescue! We can register a structured exception handler with windows for this problem. That way if the computer doesn't know what this instruction is we get a chance to deal with the problem BEFORE windows does. A structured exception handler is simply a procedure that gets called when you run into an error that would normally shut down your program. So I would place a message box in the exception handler stating that my program cannot detect a pentium cpu and then give the option to continue anyways if they feel it's a mistake. It's not worth putting all the code in here as it's rather simple and then you'd have to show the whole dll's code. So just grab the source code yourself and take a look. I use the api call "SetUnhandledExceptionFilter" to switch the exception handler from windows to mine and then at the end of the detection procedure back to windows.

    The SetUnhandledExceptionFilter function lets an application supersede the top-level exception handler that Win32 places at the top of each thread and process. After calling this function, if an exception occurs in a process that is not being debugged, and the exception makes it to the Win32 unhandled exception filter, that filter will call the exception filter function specified by the parameter. The SetUnhandledExceptionFilter function returns the address of the previous exception filter established with the function. A NULL return value means that there is no current top-level exception handler.

    So you'll notice that after I call it the first time I push the returned address onto the stack, so that we can use it to call the same function at the end of our work (To set it back to normal).

    Take a look at the exception handling code in the source code.


    So if I wanted to use the completed dll, in assembler I would use a macro similar to following:

    CheckCPU MACRO
    LOCAL lbl
    LOCAL LibName
    LOCAL ProcName
    jmp lbl
    LibName db "Local.dll",0
    ProcName db "CheckCPU",0
    invoke LoadLibrary,ADDR LibName
    invoke GetProcAddress,eax,ADDR ProcName
    call eax
    Of course C or C++ would be very similar. However I am not a C or C++ programmer and so I can't do more than guess. I would use the "LoadLibrary" api call to load the dll, then I would use the "GetProcAddress" api call to get the procedure's address and then call our procedure. I'm sure if you post a question on the Message Board someone will know how to do this in C or C++!

    I hope this has helped you on your quest to becoming a professional game developer or maybe taught you something you didn't know if you are a profesional game developer! Remember you can download the source code attached to this article. Until next time, see ya!
    - Ben


    Identification of x86 CPUs with CPUID support

    This is where to get some extensive information on the CPUID instruction.

    Iczelion's Win32 Assembly HomePage
    Where I learned to make DLL's in assembly as well as how to do structured exception handling in assembly.


    Please send comments and feedback to [email="Cyberben@home.com"]Cyberben@home.com[/email]
    (C) 2000 Benjamin Swayne, All Rights Reserved.


    Sign in to follow this  
    Followers 0

    User Feedback

    Create an account or sign in to leave a review

    You need to be a member in order to leave a review

    Create an account

    Sign up for a new account in our community. It's easy!

    Register a new account

    Sign in

    Already have an account? Sign in here.

    Sign In Now

    There are no reviews to display.