Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call cpuid instruction in a Mac framework?

I want to use the cpuid instruction to identify features of an Intel CPU. I found the cpuid.h header in Kernel.framework, so I added Kernel.framework to my project and included <Kernel/i386/cpuid.h> in my source file. That produced

kern/kern_types.h: No such file or directory

which I don't understand. But the function do_cpuid, which is what I think I want to use, is defined inline, so I tried just copying that into my source.

static inline void
do_cpuid(uint32_t selector, uint32_t *data)
{
    asm("cpuid"
        : "=a" (data[0]),
          "=b" (data[1]),
          "=c" (data[2]),
          "=d" (data[3])
        : "a"(selector));
}

That gave me errors:

error: can't find a register in class 'BREG' while reloading 'asm'
error: 'asm' operand has impossible constraints

Googling that error led me to this question: Problem on Mac : "Can't find a register in class BREG while reloading asm"

But the solution to that question was to use the dynamic-no-pic option (GCC_DYNAMIC_NO_PIC build setting), and Xcode's help on build settings says "Not appropriate for shared libraries (which need to be position-independent)." I'm building a framework, which I think counts as a shared library. So how can I make this work?

like image 297
JWWalker Avatar asked Aug 31 '12 19:08

JWWalker


2 Answers

This rather cryptic error message:

error: can't find a register in class 'BREG' while reloading 'asm'
error: 'asm' operand has impossible constraints

is occurring because one of the constraints isn't allowed. In this case it is EBX. When compiling 32 bit code with the -fPIC option (position independent code) the EBX register is used for relocation. It can't be used as an output or appear as a clobbered register.

Although most people suggest compiling with special compiler flags, the function can be rewritten to support x86-64/IA32 and PIC/non-PIC by modifying the assembler code to save the EBX register itself and restore it after. This can be done with code like:

#include <inttypes.h>

static inline void
do_cpuid(uint32_t selector, uint32_t *data)
{
    __asm__ __volatile__ (
        "xchg %%ebx, %k[tempreg]\n\t"
        "cpuid\n\t"
        "xchg %%ebx, %k[tempreg]\n"
        : "=a" (data[0]),
          [tempreg]"=&r" (data[1]),
          "=c" (data[2]),
          "=d" (data[3])
        : "a"(selector),
          "c"(0));
}

The significant change is that the data[1] value will be returned in an available register the compiler chooses. The =&r constraint tells the compiler that whatever register it chooses can't be any of the other input registers (we early clobber the register with the xchg code). In the code we exchange EBX with available register the compiler chose. We then exchange it back afterward. When finished EBX will contain its original value and the free register that was chosen will contain what was returned by CPUID. The assembler template will then move the contents of that free register to data[1].

Effectively we have circumvented the problem by allowing the compiler to choose a free register. The compiler is smart enough not to use EBX if it is tied up because it might be used for relocatable code. In 64 bit code EBX isn't used for relocation like it is with 32 bit code so it may be available for usage.

An astute observer might have noticed the xor %%ecx,%%ecx. This is a change I made independent of the EBX issue. It is now considered good practice to clear ECX because some processors made by AMD may return stale values if ECX is not zero. If you are developing for a non-PPC Mac platform only, this change isn't necessary since Apple uses Intel processors that don't exhibit such behavior.

Generally EBX is special in 32 bit relocatable/PIC code which is why the compiler originally complained with its cryptic message.

like image 182
Michael Petch Avatar answered Sep 30 '22 22:09

Michael Petch


So, this might not answer the question but should provide an interesting enough workaround for at least some people finding their way here.


sysctl provides a lot of information about a machine's CPU on macOS. For starters type man sysctl in Terminal and look at everything prefixed with machdep.cpu. Take for example feature_bits as replacement for uint64_t cpuid_features(void); from cpuid.h:

sysctl machdep.cpu.feature_bits gives you the actual features bitmask, although not very readable. sysctl machdep.cpu.features does the job a little better and provides you with mostly comprehensible acronyms.

Now, doing this programmatically is even more interesting and actually pretty feasible:

#include <sys/sysctl.h>

// ...

int64_t value = 0;
size_t valueByteSize = sizeof(value);
int error = sysctlbyname("machdep.cpu.feature_bits", &value, &valueByteSize, NULL, 0);
if (!error)
{
   // Check for the next best bit defined in cpuid.h
   // Since you can't build with cpuid.h directly, just copy over the definitions you actually need
   bool hasFPU = value & CPUID_FEATURE_FPU;
}
like image 34
bfx Avatar answered Sep 30 '22 22:09

bfx