Search code examples
cx86-64inline-assemblycpuid

Checking for ID flag in RFLAGS using inline assembly in C


I have written the following inline assembly to try check for the ID flag. I know that it should be set because I can use the cpuid instruction, however this function returns 0 when I test it.

_Bool /* Checks whether the cpu has the cpuid instruction available */
cpu_has_cpuid(void) {
        uint64_t out;
        asm ("pushfq\n\t"
             "popq %[out]\n\t"
             : [out] "=r" (out)
             :: "memory");
        return out & 0x200000;
}

Does anyone know what I did wrong here and can you help me?

If it helps, the value I get without the bitmask is 582.


Solution

  • You can and should use GNU C #include <cpuid.h> in your own code instead of GNU C inline asm. (How do I call "cpuid" in Linux?) Example on Godbolt of how it compiles, including the 32-bit mode check for CPUID availability.


    x86-64 guarantees the availability of CPUID; you only need to check for it if your code might run on a 486 or earlier. But 486 can't run 64-bit code in the first place.

    The actual problem with your code is that it doesn't try to flip the ID bit. Apparently 0 is normal for its current status. https://wiki.osdev.org/CPUID#Checking_CPUID_availability shows standard detection code (for 32-bit mode Intel-syntax, but easy enough to port) that uses a memory-destination XOR before a popf / pushf / pop reg to see if the setting "took". (Then it restores the original EFLAGS, which is probably unnecessary.)

    For a GNU C inline asm implementation, see GCC's own cpuid.h (github) where it's done inside an #ifndef __x86_64__ block in __get_cpuid_max. It even has dialect-alternatives for AT&T vs. Intel-syntax, so code that uses #include <cpuid.h> doesn't break if compiled with -masm=intel.

    Porting that to x86-64 would let you verify that it actually works, if you're curious.


    Also, your push/pop is unsafe on Linux / Mac (x86-64 System V): you step on the red-zone. You need to add $-128, %rsp before and sub $-128, %rsp after, to avoid your push/pop stepping on the red-zone below the compiler's RSP, where it might be keeping local vars. See Using base pointer register in C++ inline asm

    -128 fits in an imm8 but +128 doesn't, that's why I suggest add/sub $-128 instead of sub/add $128.