paint-brush
How to Emulate CPUID in a KVM VMby@mvuksano
2,239 reads
2,239 reads

How to Emulate CPUID in a KVM VM

by Marko VuksanovicJune 19th, 2020
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

How to Emulate CPUID in a KVM VM? Emulate an example for an example. Marko Vuksanovic PSS - Pragmatic problem solver @ Facebook Facebook page. How to emulate CPUID on an x86 CPU using an ioctl to do that. The next time we will start working on our x86 VM, we will work on enabling paging on our CPU on x86. We need to createstructure and allocate enough space for it. The way we allocate memory for this type of structure is a trailing array.
featured image - How to Emulate CPUID in a KVM VM
Marko Vuksanovic HackerNoon profile picture

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: