| /* |
| * XXX - Credit, licence, copyright |
| */ |
| |
| #include <assert.h> |
| #include <err.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <linux/kvm.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <time.h> |
| #include <sys/ioctl.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #include "helpers.h" |
| |
| #define GUEST_PHYS_ADDR (1UL << 30) |
| #define GUEST_MMIO_ADDR (1UL << 38) |
| #define GUEST_MEM_SIZE (PUD_SIZE*2) |
| |
| /* x0[0] = x1; */ |
| void guest_code() |
| { |
| asm( |
| "1: str x1, [x0] \n" |
| " brk #0 \n" |
| ::); |
| |
| } |
| #define GUEST_NR_INST 2 |
| |
| static void guest_write_memory(int vcpufd, struct kvm_run *run, uint64_t addr) |
| { |
| set_one_reg(vcpufd, REG_X(0), addr); |
| set_one_reg(vcpufd, REG_X(1), 1); |
| set_one_reg(vcpufd, REG_PC, GUEST_PHYS_ADDR); |
| assert(vcpu_run(vcpufd, run) == KVM_EXIT_DEBUG); |
| } |
| |
| struct kvm_userspace_memory_region guest_prepare_memory(int vmfd, int vcpufd) |
| { |
| uint8_t *mem_code; |
| |
| mem_code = mmap(NULL, GUEST_MEM_SIZE, PROT_READ | PROT_WRITE, |
| MAP_SHARED | MAP_ANONYMOUS, -1, 0); |
| if (mem_code == MAP_FAILED) |
| err(-ENOMEM, "allocating guest memory"); |
| memcpy(mem_code, guest_code, GUEST_NR_INST * ARM64_INST_SIZE); |
| |
| /* Map code memory to the second page frame. */ |
| struct kvm_userspace_memory_region region = { |
| .slot = 0, |
| .guest_phys_addr = GUEST_PHYS_ADDR, |
| .memory_size = GUEST_MEM_SIZE, |
| .userspace_addr = (uint64_t)mem_code, |
| }; |
| KVM_IOCTL(vmfd, KVM_SET_USER_MEMORY_REGION, ®ion); |
| |
| vm_add_mmio_page(vmfd, 1, GUEST_MMIO_ADDR); |
| set_one_reg(vcpufd, REG_X(3), GUEST_MMIO_ADDR); |
| |
| return region; |
| } |
| |
| #define RAND_PAGE() ((rand() % (GUEST_MEM_SIZE / PAGE_SIZE - 1)) + 1) |
| #define NR_DIRTY_PAGES 500 |
| int main(void) |
| { |
| struct kvm_userspace_memory_region region; |
| uint8_t *dirty_bm, *oracle_bm; |
| struct kvm_run *run = NULL; |
| struct kvm_dirty_log dlog; |
| uint64_t addr, offset; |
| int kvm, vmfd, vcpufd; |
| int i; |
| |
| srand(time(NULL)); |
| |
| /* Prepare the guest */ |
| kvm = get_kvm(); |
| vmfd = create_vm(kvm); |
| vcpufd = create_vcpu(kvm, vmfd, &run, true); |
| region = guest_prepare_memory(vmfd, vcpufd); |
| |
| /* We need two bitmap, one for dirty logging, and one for the oracle */ |
| dirty_bm = calloc(1, BITMAP_SIZE(GUEST_MEM_SIZE)); |
| oracle_bm = calloc(1, BITMAP_SIZE(GUEST_MEM_SIZE)); |
| if (!dirty_bm || !oracle_bm) |
| return -ENOMEM; |
| |
| /* Optional - poke random pages in memory, with logging disabled */ |
| for (i = 0; i < NR_DIRTY_PAGES ; i++) { |
| offset = RAND_PAGE() * PAGE_SIZE; |
| addr = offset + GUEST_PHYS_ADDR; |
| guest_write_memory(vcpufd, run, addr); |
| } |
| |
| /* Enable dirty logging, and try again */ |
| region.flags = KVM_MEM_LOG_DIRTY_PAGES; |
| KVM_IOCTL(vmfd, KVM_SET_USER_MEMORY_REGION, ®ion); |
| |
| for (i = 0; i < NR_DIRTY_PAGES ; i++) { |
| offset = RAND_PAGE() * PAGE_SIZE; |
| addr = offset + GUEST_PHYS_ADDR; |
| guest_write_memory(vcpufd, run, addr); |
| oracle_bm[OFFSET_IDX(offset)] |= OFFSET_BIT(offset); |
| } |
| |
| /* Dump the dirty log bitmap, and compare to the oracle */ |
| dlog.slot = region.slot; |
| dlog.dirty_bitmap = dirty_bm; |
| KVM_IOCTL(vmfd, KVM_GET_DIRTY_LOG, &dlog); |
| |
| assert(bitmap_equal(dirty_bm, oracle_bm, BITMAP_SIZE(GUEST_MEM_SIZE))); |
| printf("Passing.\n"); |
| } |