Identification of x86 CPUs and their features with CPUID
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!
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:
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.
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!
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:
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:
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.
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:
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 here. Until next time, see ya!
Identification of x86 CPUs with CPUID support
Iczelion's Win32 Assembly HomePage