Hi all,
Currently I am experimenting with KVM. I want to measure the cost of data accesses over the KVM_EXIT_MMIO mechanism.
I want to only map the executable code in a memory slot and let all variable access use the KVM_EXIT_MMIO mechanism.
I placed the .text section of my executable in a range of a mapped memory slot, whereas the .data* and *.bss sections where linked to an unmapped address range.
Then I observed some strange behavior:
When accessing unmapped address with str instructions they lead to an KVM_EXIT_MMIO as expected.
However, if the str instruction is post-indexed, KVM exits with KVM_EXIT_UNKNOWN.
Is this behavior expected?
Thanks in advance!
Best Regards
Jan
----------------------------------------------------------------
For convenience I built a minimal example to demonstrate my point (running on a raspi3 b+, consisting of start.s and minimal_kvm.c):
as start.s -o start.o
objcopy -S -O binary start.o start.img
gcc minimal kvm.c -o minimal_kvm
./minimal_kvm
mmio_exit when accessing address 0x5000.
mmio_exit when accessing address 0x5008.
unknown exit.
start.s:
.global _start:
_start:
// move an unmapped addr to a gpr
mov x2, #0x5000
// first try direct mmio-exit
str xzr, [x2]
// then try pre-indexed str
str xzr, [x2, #8]
// then try the post-indexed str
str xzr, [x2], #8
minimal_kvm.c:
#include <err.h>
#include <fcntl.h>
#include <linux/kvm.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <asm/kvm.h>
#include <stddef.h>
#include <asm-generic/errno-base.h>
int main(void)
{
int kvm, vmfd, vcpufd, ret;
int fd;
char *mem;
size_t mmap_size;
struct kvm_run *run;
struct kvm_vcpu_init vcpu_init;
struct stat sb;
fd = open("./start.img", O_RDWR);
fstat(fd, &sb);
const char *memblock;
memblock = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (memblock == MAP_FAILED)
err(1, "mem map failed");
/* check if /dev/kvm is there with proper permissions */
kvm = open("/dev/kvm", O_RDWR | O_CLOEXEC);
if (kvm == -1)
err(1, "/dev/kvm");
/* Make sure we have the stable version of the API */
ret = ioctl(kvm, KVM_GET_API_VERSION, NULL);
if (ret == -1)
err(1, "KVM_GET_API_VERSION");
if (ret != 12)
errx(1, "KVM_GET_API_VERSION %d, expected 12", ret);
/* create vm of type 0 (recommended by documentation) */
vmfd = ioctl(kvm, KVM_CREATE_VM, (unsigned long)0);
if (vmfd == -1)
err(1, "KVM_CREATE_VM");
/* Allocate one aligned page of guest memory to hold the code. */
mem = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (mem == MAP_FAILED)
err(1, "allocating guest memory");
memcpy(mem, memblock, sb.st_size);
struct kvm_userspace_memory_region region = {
.slot = 0,
.guest_phys_addr = 0x0000,
.memory_size = 0x1000,
.userspace_addr = (uint64_t)mem,
};
ret = ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, ®ion);
if (ret == -1)
err(1, "KVM_SET_USER_MEMORY_REGION");
/* create virtual cpu with id 0 */
vcpufd = ioctl(vmfd, KVM_CREATE_VCPU, (unsigned long)0);
if (vcpufd == -1)
err(1, "KVM_CREATE_VCPU");
/* get the preferred guest type to match underlying host */
ret = ioctl(vmfd, KVM_ARM_PREFERRED_TARGET, &vcpu_init);
if (ret == -1){
err(1, "KVM_ARM_PREFERRED_TARGET");
}
/* init vcpu with preferred guest type */
ret = ioctl(vcpufd, KVM_ARM_VCPU_INIT, &vcpu_init);
if (ret == -1){
err(1, "KVM_ARM_VCPU_INIT");
}
/* Map the shared kvm_run structure and following data. */
ret = ioctl(kvm, KVM_GET_VCPU_MMAP_SIZE, NULL);
if (ret == -1)
err(1, "KVM_GET_VCPU_MMAP_SIZE");
mmap_size = ret;
if (mmap_size < sizeof(*run))
err(1, "KVM_GET_VCPU_MMAP_SIZE unexpectedly small");
run = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, vcpufd, 0);
if (!run)
err(1, "mmap vcpu");
// set pc
struct kvm_one_reg reg;
reg.id = KVM_REG_ARM64 | KVM_REG_ARM_CORE | KVM_REG_ARM_CORE_REG(regs.pc) | KVM_REG_SIZE_U64;
__u64 reg_value = 0;
reg.addr =(__u64)(®_value);
ret = ioctl(vcpufd, KVM_SET_ONE_REG, ®);
if (ret != 0){
err(1, "KVM_SET_ONE_REG");
}
do {
ret = ioctl(vcpufd, KVM_RUN, NULL);
if (run->exit_reason == KVM_EXIT_MMIO) {
__u64 guest_addr = run->mmio.phys_addr;
printf("mmio_exit when accessing address %#x\n", guest_addr);
}
if (run->exit_reason == KVM_EXIT_UNKNOWN) {
__u64 pc = ioctl(vcpufd, KVM_GET_ONE_REG, ®);
printf("unknown exit.\n");
}
} while (run->exit_reason != KVM_EXIT_UNKNOWN);
munmap((void*)memblock, sb.st_size);
return 0;
}