How to get physical and virtual address bits with C/C++ by CPUID command

1

I'm getting physical and virtual address bits size with C by using CPUID command in windows. I can get the processor information this way, but I'm confused by getting the address bits. Looks like I should you the 80000008 instruction but I do this way, only 7-8 digits change continuously are displayed. I want to learn how this command works and solve this problem

#include <stdio.h>

void getcpuid(int T, int* val) {
    int reg_ax;
    int reg_bx;
    int reg_cx;
    int reg_dx;
    __asm {
        mov eax, T;
        cpuid;
        mov reg_ax, eax;
        mov reg_bx, ebx;
        mov reg_cx, ecx;
        mov reg_dx, edx;
    }
    *(val + 0) = reg_ax;
    *(val + 1) = reg_bx;
    *(val + 2) = reg_cx;
    *(val + 3) = reg_dx;
}

int main() {
    int val[5]; val[4] = 0;
    getcpuid(0x80000002, val);
    printf("%s\r\n", &val[0]);
    getcpuid(0x80000003, val);
    printf("%s\r\n", &val[0]);
    getcpuid(0x80000004, val);
    printf("%s\r\n", &val[0]);
    return 0;
}

when operate this code with putting EAX = 80000002, 80000003, 80000004, Intel processor brand string was displayed. And I put 80000008 To getting physical and virtual address bits but random numbers changing constantly was displayed. I want to know how to use this cpuid commend with 80000008 to get those address bits

i'm programming and operating system beginner. Please let me know what I have to do.

visual-c++
x86
operating-system
bit
cpuid
asked on Stack Overflow Oct 24, 2020 by kkkkkk • edited Oct 26, 2020 by Michael Petch

1 Answer

1

The inline assembly you're using may be right; but this depends on which compiler it is. I think it is right for Microsoft's MSVC (but I've never used it and can't be sure). For GCC (and CLANG) you'd have to inform the compiler that you're modifying the contents of registers and memory (via. a clobber list), and it would be more efficient to tell the compiler that you're outputting 4 values in 4 registers.

The main problem is that you're trying to treat the output as a (null terminated) string; and the data returned by CPUID is never a null terminated string (even for "get vendor string" and "get brand name string", it's a whitespace padded string with no zero terminator).

To fix that you could:

void getcpuid(int T, int* val) {
    unsigned int reg_ax;
    unsigned int reg_bx;
    unsigned int reg_cx;
    unsigned int reg_dx;
    __asm {
        mov eax, T;
        cpuid;
        mov reg_ax, eax;
        mov reg_bx, ebx;
        mov reg_cx, ecx;
        mov reg_dx, edx;
    }
    *(val + 0) = reg_ax;
    *(val + 1) = reg_bx;
    *(val + 2) = reg_cx;
    *(val + 3) = reg_dx;
}

int main() {
    uint32_t val[5]; val[4] = 0;
    getcpuid(0x80000002U, val);
    printf("0x%08X\r\n", val[0]);
    getcpuid(0x80000003U, val);
    printf("0x%08X\r\n", val[1]);
    getcpuid(0x80000004U, val);
    printf("0x%08X\r\n", val[2]);
    return 0;
}

The next problem is extracting the virtual address size and physical address size values. These are 8-bit values packed into the first and second byte of eax; so:

int main() {
    uint32_t val[5]; val[4] = 0;
    int physicalAddressSize;
    int virtualAddressSize;

    getcpuid(0x80000008U, val);
    physicalAddressSize = val[0] & 0xFF;
    virtualAddressSize= (val[0] >> 8) & 0xFF;

    printf("Virtual %d, physical %d\r\n", virtualAddressSize, physicalAddressSize);
    return 0;
}

That should work on most recent CPUs; which means that it's still awful and broken on older CPUs.

To start fixing that you want to check that the CPU supports "CPUID leaf 0x80000008" before you assume it exists:

int main() {
    uint32_t val[5]; val[4] = 0;
    int physicalAddressSize;
    int virtualAddressSize;

    getcpuid(0x80000000U, val);
    if(val(0) < 0x80000008U) {
        physicalAddressSize = -1;
        virtualAddressSize = -1;
    } else {
        getcpuid(0x80000008U, val);
        physicalAddressSize = val[0] & 0xFF;
        virtualAddressSize= (val[0] >> 8) & 0xFF;
    }
    printf("Virtual %d, physical %d\r\n", virtualAddressSize, physicalAddressSize);
    return 0;
}

You can return correct results when "CPUID leaf 0x80000008" doesn't exist. For all CPUs that don't support "CPUID leaf 0x80000008"; virtual address size is 32 bits, and the physical address size is either 36 bits (if PAE is supported) or 32 bits (if PAE is not supported). You can use CPUID to determine if the CPU supports PAE, so it ends up a bit like this:

int main() {
    uint32_t val[5]; val[4] = 0;
    int physicalAddressSize;
    int virtualAddressSize;

    getcpuid(0x80000000U, val);
    if(val(0) < 0x80000008U) {
        getcpuid(0x00000000U, val);
        if(val[0] == 0) {
            physicalAddressSize = 32;          // "CPUID leaf 0x00000001" not supported
        } else {
            getcpuid(0x00000001U, val);
            if( val[3] & (1 << 6) != 0) {
                physicalAddressSize = 36;      // PAE is supported
            } else {
                physicalAddressSize = 32;      // PAE not supported
            }
        }
        virtualAddressSize = 32;
    } else {
        getcpuid(0x80000008U, val);
        physicalAddressSize = val[0] & 0xFF;
        virtualAddressSize= (val[0] >> 8) & 0xFF;
    }
    printf("Virtual %d, physical %d\r\n", virtualAddressSize, physicalAddressSize);
    return 0;
}

The other problem is that sometimes CPUID is buggy; which means that you have to trawl through every single errata sheet for every CPU (from Intel, AMD, VIA, etc) to be sure the results from CPUID are actually correct. For example, there are 3 models of "Intel Pentium 4 Processor on 90 nm Process" where "CPUID leaf 0x800000008" is wrong and says "physical addresses are 40 bits" when they are actually 36 bits.

For all of these cases you need to implement work-arounds (e.g. get the CPU vendor/family/model/stepping information from CPUID and if it matches one of the 3 buggy models of Pentium 4, do an "if(physicalAddressSize == 40) physicalAddressSize = 36;" to fix the CPU's bug).

answered on Stack Overflow Oct 24, 2020 by Brendan • edited Oct 25, 2020 by Brendan

User contributions licensed under CC BY-SA 3.0