Query CPUID with Inline Assembly

As part of my bachelor thesis on Intel PT Hooking I had to determine the availability of different processor features. Since the usage details were not as well documented as I thought they could be, I will share my findings, explanations and examples here.

On all x86 processors (the feature was introduced in 1993) retrieving information about the processor’s features and capabilities is done by calling the CPUID machine instruction. Depending on the values in the registers EAX, EBX, ECX and EDX, different information can be queried.

The GNU C Compiler features a C wrapper for the CPUDID instruction (available through the header file <cpuid.h>), making it easy and convenient to use from native C code, but I wanted to make the function’s action explicit. I also wanted to take a look at GCC’s Inline Assembly since this feature can be very handy, but is also a bit arcane.

int a, b, c, d;
a = 0x1;
asm (
  "mov eax, %0\n\t"
  "cpuid\n\t"
  "mov %0, eax\n\t"
  "mov %1, ebx\n\t"
  "mov %2, ecx\n\t"
  "mov %3, edx\n\t"
  : "=r" (a), "=r" (b), "=r" (c), "=r" (d)
  : "0" (a)
);

The code above demonstrates a call of CPUID querying general processor information (such as model, family, bus frequency, etc.). The variables a, b, c and d are used to store the contents of the registers EAX, EBX, ECX and EDX in our program. After setting the value of a to 1 (line 2) and moving the value into the register EAX (line 4), we issue the cpuid instruction (line 5). Now the CPU itself reads the values from these registers and sets the appropriate bits in the very same registers. Then, we can retrieve the values from the registers (line 6-9) and write them to our local variables. The argument list after first colon (line 10) tells GCC’s Inline Assembly what the output variables are (our local variables) and from where they should be read (in this case registers, hence the =r). After the second colon follows the list of input variables. This is a bit unintuitive, since the the input variables are specified after the output variables, but that’s exactly the way it is.

With GCC’s Inline Assembly the same code can be written more concise, as we need not manually copy the values into and out of the registers, but rather can handle this by defining our arguments more precisely (see line 3).

int a = 0x1, b, c, d;
asm ( "cpuid\n\t"
  : "=a" (a), "=b" (b), "=c" (c), "=d" (d)
  : "0" (a)
);

For example, to detect the availability of Intel PT (which I needed for my bachelor thesis), we need to set EAX to 0x7, ECX to 0x0 and look at Bit 25 in EBX after calling CPUID. I got this information from Intel’s Software Developer Manuals (in particular Volume 3 - System Programming Guide, Section 36.3.1).

#define BIT(x) (1ULL << (x))

int a = 0x7, b, c = 0, d;
asm ("cpuid\n\t"
  : "=a" (a), "=b" (b), "=c" (c), "=d" (d)
  : "0" (a), "2" (c));

if ((b & BIT(25)) == 0)
  printf("Intel PT not supported\n");
else
  printf("Intel PT supported\n");

By calling CPUID with EAX set to 0x14 and ECX set to 0x0, we can enumerate specific features of Intel PT. Since the feature set and capabilities of x86 processors are gradually being expanded between different processor generations by the manufacturers, support for each used feature has to be checked individually.

I have to say the mechanism used for querying processor features with CPUID is quite ingenious. Put values into a handful of register, issue a machine instruction and retrieve the new values in the same registers. While it seems a bit cumbersome to a “high-level” programmer (like myself) at first, it’s quite an efficient method to communicate with “the machine”.