Hackernoon logoHow to Emulate CPUID in a KVM VM by@mvuksano

How to Emulate CPUID in a KVM VM

Author profile picture

@mvuksanoMarko Vuksanovic

PSS - Pragmatic problem solver

For most workloads it will be necessary to determine characteristics of CPU on which they are run. Most processors have some way of querying capabilities. x86 CPU uses
CPUID
instruction.
Unfortunately you cannot just start a VM and then execute the CPUID instruction. The program will not crash but you will not get useful information either. The reason for this is that sometimes we'd like to tell guests what is available and what is not available for use. At this point we will go with a simple approach - we will let our guest OS do whatever KVM features are available.
KVM has an ioctl to do that -
KVM_GET_SUPPORTED_CPUID
. It also has
KVM_SET_CPUID2
that defines vCPU response to CPUID instruction.
Before we can make a call to
KVM_GET_SUPPORTED_CPUID
we need to create
kvm_cpuid2
structure and allocate enough space for it.
The
kvm_cpuid2
structure is defined as follows:
struct kvm_cpuid2 {
	__u32 nent;
	__u32 padding;
	struct kvm_cpuid_entry2 entries[0];
};

struct kvm_cpuid_entry2 {
	__u32 function;
	__u32 index;
	__u32 flags;
	__u32 eax;
	__u32 ebx;
	__u32 ecx;
	__u32 edx;
	__u32 padding[3];
};
You can see that it has two elements but it also has a trailing array. The way we allocate memory for this type of structure is
sizeof(kvm_cpuid2) + number_of_entries * sizeof(kvm_cpuid_entry2)
← pseudo-code ⚠️
Following is piece of code that allocates memory for
kvm_cpuid2
structure and uses
KVM_GET_SUPPORTED_CPUID
to populate it.
struct kvm_cpuid2 *cpuid;
int nent = 40;
unsigned long size = sizeof(*cpuid) + nent * sizeof(*cpuid->entries);
cpuid = (struct kvm_cpuid2*) malloc(size);
bzero(cpuid, size);
cpuid->nent = nent;
if(ioctl(kvm, KVM_GET_SUPPORTED_CPUID, cpuid) == -1) {
    printf("KVM_GET_SUPPORTED_CPUID could not read CPUID info. Error code: %d\n", ret);
    return -1;
}
Now it can be useful to print out all the available options that KVM has reported. It could be useful to know this once the vCPU starts.
for(int i = 0; i < cpuid->nent; i++) {
    printf("F: 0x%08x, idx: 0x%08x, flags: 0x%08x, eax: 0x%08x, ebx: 0x%08x, ecx: 0x%08x, edx: 0x%08x\n", cpuid->entries[i].function, cpuid->entries[i].index, cpuid->entries[i].flags, cpuid->entries[i].eax, cpuid->entries[i].ebx, cpuid->entries[i].ecx, cpuid->entries[i].edx);
}
Now we can use our pointer to
cpuid
structure to configure vCPU.
if(ioctl(vcpufd, KVM_SET_CPUID2, cpuid) == -1) {
    printf("KVM_SET_CPUID2 could not set CPUID info. Error code: %d\n", ret);
    return -1;
}
And that is it! Now you can execute CPUID instruction on your vCPU and get correct results.
Check out `program.asm` in my kvm-playground repo (https://gitlab.com/mvuksano/kvm-playground/-/tree/master/03-cpuid) for an example of an assembly program that reads information about CPU using CPUID and outputs that information via serial port.
See you next time when we will start working on enabling paging on our x86 CPU 👋

📖 Resources:

Tags

The Noonification banner

Subscribe to get your daily round-up of top tech stories!