Cpuid Implementations in C++

CPUID implementations in C++

Accessing raw CPUID information is actually very easy, here is a C++ class for that which works in Windows, Linux and OSX:

#ifndef CPUID_H
#define CPUID_H

#ifdef _WIN32
#include <limits.h>
#include <intrin.h>
typedef unsigned __int32 uint32_t;

#else
#include <stdint.h>
#endif

class CPUID {
uint32_t regs[4];

public:
explicit CPUID(unsigned i) {
#ifdef _WIN32
__cpuid((int *)regs, (int)i);

#else
asm volatile
("cpuid" : "=a" (regs[0]), "=b" (regs[1]), "=c" (regs[2]), "=d" (regs[3])
: "a" (i), "c" (0));
// ECX is set to zero for CPUID function 4
#endif
}

const uint32_t &EAX() const {return regs[0];}
const uint32_t &EBX() const {return regs[1];}
const uint32_t &ECX() const {return regs[2];}
const uint32_t &EDX() const {return regs[3];}
};

#endif // CPUID_H

To use it just instantiate an instance of the class, load the CPUID instruction you are interested in and examine the registers. For example:

#include "CPUID.h"

#include <iostream>
#include <string>

using namespace std;

int main(int argc, char *argv[]) {
CPUID cpuID(0); // Get CPU vendor

string vendor;
vendor += string((const char *)&cpuID.EBX(), 4);
vendor += string((const char *)&cpuID.EDX(), 4);
vendor += string((const char *)&cpuID.ECX(), 4);

cout << "CPU vendor = " << vendor << endl;

return 0;
}

This Wikipedia page tells you how to use CPUID: http://en.wikipedia.org/wiki/CPUID

EDIT: Added #include <intrin.h> for Windows, per comments.

Implementing __cpuid example from MSDN gives insanely large numbers

Ok I figured it out, the example in MSDN has a bug - if the feature is not supported it sets it to a negative value, but nExIds is an unsigned int, so when you set it to the negative invalid value, it automatically gets set to 2147483656.So I must use int instead of unsigned int.

How portable is __get_cpuid?

It has two leading underscores. Any leading underscore at all is a big hint that the variable or function is not portable.

Differing CPUID usage from high-level languages

As Jester says, in GNU C the cpuid.h wrapper intrinsic is probably your best bet.


There's also __builtin_cpu_supports("popcnt") or "avx" or whatever, which works after you call __builtin_cpu_init(). Only the really major feature-bits are supported, though. For example, the docs don't mention the feature-bit for rdrand, so __builtin_cpu_supports("rdrand") probably doesn't work.



Custom inline-assembly versions:

The implementation from Linux can inline with no wasted instructions, and it looks well-written, so there's no reason to use anything else. It's remotely possible that you might get a complaint about not being able to satisfy the "=b" constraint; if so see below for what clang's cpuid.h does. (But I think that's never necessary and the result of a documentation mistake).

It doesn't actually need volatile, though, if you're using it for the values produced rather than the serializing effect on the pipeline: Running CPUID with the same inputs will give the same result, so we can let the optimizer move it around or hoist it out of loops. (So it runs fewer times). This is probably not helpful because normal code won't use it in a loop in the first place, though.


The source for clang's implementation of cpuid.h does some weird stuff, like preserving %rbx because apparently some x86-64 environments might not be able to satisfy a constraint that uses %rbx as an output operand? The comment is /* x86-64 uses %rbx as the base register, so preserve it. */, but I have no idea what they're talking about. If anything x86-32 PIC code in the SysV ABI uses %ebx for a fixed purpose (as a pointer to the GOT), but I don't know about anything like that for x86-64. Perhaps that code is motivated by a mistake in the ABI documentation? See HJ Lu's mailing list post about it.


Most importantly, the first version in the question (inside main()) is broken because it clobbers the red-zone with push.

To fix it, just tell the compiler the result will be in ebx (with "=b"), and let it worry about saving/restoring ebx/rbx at the start/end of the function.

What is the CPUID standard function 01H?

I think it means that %eax should be 1 when invoking the cpuid function. Hardware guys have strange conventions, so they say 01H instead of 1 or 0x1.

See the intel manual Vol 2. Ch 3. Table 3.8. Initial value of eax is 01H. ECX on return is full of feature information in table 3.10. At the bottom of that table for bit 30, it says a value of 1 means the processor supports RDRAND.

If you have gcc, you can try something like this:

evaitl@bb ~/se $ cat foo.c 
#include <stdio.h>
#include <cpuid.h>
int main(){
unsigned eax=0, ebx=0, ecx=0, edx=0;
unsigned max_lvl=__get_cpuid_max(0,&ebx);
printf("Max level: %d sig %x\n",max_lvl,ebx);
ebx=0;
for(int lvl=0; lvl<=max_lvl; ++lvl){
__get_cpuid(lvl,&eax, &ebx, &ecx, &edx);
printf("lvl %-2d eax %08x ebx %08x ecx %08x edx %08x\n",
lvl, eax, ebx, ecx, edx);
eax=ebx=ecx=edx=0;
}
return 0;
}
evaitl@bb ~/se $ ./foo
Max level: 13 sig 756e6547
lvl 0 eax 0000000d ebx 756e6547 ecx 6c65746e edx 49656e69
lvl 1 eax 000306e4 ebx 07200800 ecx 7fbee3bf edx bfebfbff
lvl 2 eax 76036301 ebx 00f0b2ff ecx 00000000 edx 00ca0000
lvl 3 eax 00000000 ebx 00000000 ecx 00000000 edx 00000000
lvl 4 eax 00000000 ebx 00000000 ecx 00000000 edx 00000000
lvl 5 eax 00000040 ebx 00000040 ecx 00000003 edx 00001120
lvl 6 eax 00000077 ebx 00000002 ecx 00000009 edx 00000000
lvl 7 eax 00000000 ebx 00000000 ecx 00000000 edx 00000000
lvl 8 eax 00000000 ebx 00000000 ecx 00000000 edx 00000000
lvl 9 eax 00000001 ebx 00000000 ecx 00000000 edx 00000000
lvl 10 eax 07300403 ebx 00000000 ecx 00000000 edx 00000603
lvl 11 eax 00000000 ebx 00000000 ecx 0000006e edx 00000007
lvl 12 eax 00000000 ebx 00000000 ecx 00000000 edx 00000000
lvl 13 eax 00000000 ebx 00000000 ecx 00000000 edx 00000000

It'll take quite a bit of time to decode everything, but there are defines in <cpuid.h> that can help:

/* %ecx */
#define bit_SSE3 (1 << 0)
#define bit_PCLMUL (1 << 1)
#define bit_LZCNT (1 << 5)
#define bit_SSSE3 (1 << 9)
#define bit_FMA (1 << 12)
#define bit_CMPXCHG16B (1 << 13)
#define bit_SSE4_1 (1 << 19)
#define bit_SSE4_2 (1 << 20)
#define bit_MOVBE (1 << 22)
#define bit_POPCNT (1 << 23)
#define bit_AES (1 << 25)
#define bit_XSAVE (1 << 26)
#define bit_OSXSAVE (1 << 27)
#define bit_AVX (1 << 28)
#define bit_F16C (1 << 29)
#define bit_RDRND (1 << 30)

There are a bunch more in the header file. You'll notice that bit 30 of my ecx (7fbee3bf) is set at level 1, so I have the RDRND instruction available.

More information can actually be pulled out by this instruction. There is extended information with eax having the top bit set when invoking cpuid. Also a number of levels have different "leafs" depending on the value of ecx when you invoke cpuid. Somebody who is bored could spend a day or two coding up something to do all of the feature extraction and make it look pretty. Alternatively, one could do a "grep flags /proc/cpuinfo" and get something like this:

flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov
pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx
pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl
xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor
ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic
popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm epb
tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms xsaveopt
dtherm ida arat pln pts

CPU ID using C++ - windows

Do you mean "serial number", "who made the processor", or the "string that identifies the make and model of the processor".

Serial number:

Unless you have a Pentium III, you do not have a "unique ID" associated with your CPU.

Intel introduced the unique id (serial number) instruction with the P3. But after a huge uproar over privacy, they quickly disabled that feature in subsequent CPU releases.

For the record, the instruction that executed this feature in assembly:

mov eax, 3
cpuid

The processor serial number was the concatenation of eax, edx, and ecx together

You can achieve the same thing with __cpuid function by passing "3" as the second parameter. But it won't work or return a serial number unless you have a P3.

Vendor (who made the processor)

int regs[4] = {0};
char vendor[13];
__cpuid(regs, 0); // mov eax,0; cpuid
memcpy(vendor, ®s[1], 4); // copy EBX
memcpy(vendor+4, ®s[3], 4); // copy EDX
memcpy(vendor+8, ®s[2], 4); // copy ECX
vendor[12] = '\0';
print("My CPU is a %s\n", vendor);

In your case, this should print "GenuineIntel".

Make and Model (BRAND String)

If you want all the details of the CPUID instruction, including on how to get the make, model, and stepping of your CPU, as well as the "Brand String" such as "Intel(R) Core (TM)i7-3770 CPU @ 3.4GHZ...." you can reference the Intel manual at the link below. Scroll down the document to find the docs for CPUID. I'm too lazy to type it up for you.

http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-vol-2a-manual.pdf

The __cpuid() instruction provided by the MSVC compiler maps "InfoType" to EAX before the call to the cpuid instruction. After that instruction returns, EAX, EBX, ECX, and EDX get copied to the CPUInfo[4] array you passed into this function.



Related Topics



Leave a reply



Submit