selftests: kvm: Add exception handling to selftests
Add the infrastructure needed to enable exception handling in selftests. This allows any of the exception and interrupt vectors to be overridden in the guest. Signed-off-by: Aaron Lewis <aaronlewis@google.com> Reviewed-by: Alexander Graf <graf@amazon.com> Message-Id: <20201012194716.3950330-4-aaronlewis@google.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
parent
85f2a4320e
commit
29faeb9632
@ -34,7 +34,7 @@ ifeq ($(ARCH),s390)
|
||||
endif
|
||||
|
||||
LIBKVM = lib/assert.c lib/elf.c lib/io.c lib/kvm_util.c lib/sparsebit.c lib/test_util.c
|
||||
LIBKVM_x86_64 = lib/x86_64/processor.c lib/x86_64/vmx.c lib/x86_64/svm.c lib/x86_64/ucall.c
|
||||
LIBKVM_x86_64 = lib/x86_64/processor.c lib/x86_64/vmx.c lib/x86_64/svm.c lib/x86_64/ucall.c lib/x86_64/handlers.S
|
||||
LIBKVM_aarch64 = lib/aarch64/processor.c lib/aarch64/ucall.c
|
||||
LIBKVM_s390x = lib/s390x/processor.c lib/s390x/ucall.c
|
||||
|
||||
@ -111,14 +111,21 @@ LDFLAGS += -pthread $(no-pie-option) $(pgste-option)
|
||||
include ../lib.mk
|
||||
|
||||
STATIC_LIBS := $(OUTPUT)/libkvm.a
|
||||
LIBKVM_OBJ := $(patsubst %.c, $(OUTPUT)/%.o, $(LIBKVM))
|
||||
EXTRA_CLEAN += $(LIBKVM_OBJ) $(STATIC_LIBS) cscope.*
|
||||
LIBKVM_C := $(filter %.c,$(LIBKVM))
|
||||
LIBKVM_S := $(filter %.S,$(LIBKVM))
|
||||
LIBKVM_C_OBJ := $(patsubst %.c, $(OUTPUT)/%.o, $(LIBKVM_C))
|
||||
LIBKVM_S_OBJ := $(patsubst %.S, $(OUTPUT)/%.o, $(LIBKVM_S))
|
||||
EXTRA_CLEAN += $(LIBKVM_C_OBJ) $(LIBKVM_S_OBJ) $(STATIC_LIBS) cscope.*
|
||||
|
||||
x := $(shell mkdir -p $(sort $(dir $(LIBKVM_OBJ))))
|
||||
$(LIBKVM_OBJ): $(OUTPUT)/%.o: %.c
|
||||
x := $(shell mkdir -p $(sort $(dir $(LIBKVM_C_OBJ) $(LIBKVM_S_OBJ))))
|
||||
$(LIBKVM_C_OBJ): $(OUTPUT)/%.o: %.c
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
|
||||
|
||||
$(OUTPUT)/libkvm.a: $(LIBKVM_OBJ)
|
||||
$(LIBKVM_S_OBJ): $(OUTPUT)/%.o: %.S
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
|
||||
|
||||
LIBKVM_OBJS = $(LIBKVM_C_OBJ) $(LIBKVM_S_OBJ)
|
||||
$(OUTPUT)/libkvm.a: $(LIBKVM_OBJS)
|
||||
$(AR) crs $@ $^
|
||||
|
||||
x := $(shell mkdir -p $(sort $(dir $(TEST_GEN_PROGS))))
|
||||
|
@ -294,6 +294,8 @@ int vm_create_device(struct kvm_vm *vm, struct kvm_create_device *cd);
|
||||
memcpy(&(g), _p, sizeof(g)); \
|
||||
})
|
||||
|
||||
void assert_on_unhandled_exception(struct kvm_vm *vm, uint32_t vcpuid);
|
||||
|
||||
/* Common ucalls */
|
||||
enum {
|
||||
UCALL_NONE,
|
||||
|
@ -36,6 +36,8 @@
|
||||
#define X86_CR4_SMAP (1ul << 21)
|
||||
#define X86_CR4_PKE (1ul << 22)
|
||||
|
||||
#define UNEXPECTED_VECTOR_PORT 0xfff0u
|
||||
|
||||
/* General Registers in 64-Bit Mode */
|
||||
struct gpr64_regs {
|
||||
u64 rax;
|
||||
@ -239,6 +241,11 @@ static inline struct desc_ptr get_idt(void)
|
||||
return idt;
|
||||
}
|
||||
|
||||
static inline void outl(uint16_t port, uint32_t value)
|
||||
{
|
||||
__asm__ __volatile__("outl %%eax, %%dx" : : "d"(port), "a"(value));
|
||||
}
|
||||
|
||||
#define SET_XMM(__var, __xmm) \
|
||||
asm volatile("movq %0, %%"#__xmm : : "r"(__var) : #__xmm)
|
||||
|
||||
@ -338,6 +345,23 @@ uint32_t kvm_get_cpuid_max_basic(void);
|
||||
uint32_t kvm_get_cpuid_max_extended(void);
|
||||
void kvm_get_cpu_address_width(unsigned int *pa_bits, unsigned int *va_bits);
|
||||
|
||||
struct ex_regs {
|
||||
uint64_t rax, rcx, rdx, rbx;
|
||||
uint64_t rbp, rsi, rdi;
|
||||
uint64_t r8, r9, r10, r11;
|
||||
uint64_t r12, r13, r14, r15;
|
||||
uint64_t vector;
|
||||
uint64_t error_code;
|
||||
uint64_t rip;
|
||||
uint64_t cs;
|
||||
uint64_t rflags;
|
||||
};
|
||||
|
||||
void vm_init_descriptor_tables(struct kvm_vm *vm);
|
||||
void vcpu_init_descriptor_tables(struct kvm_vm *vm, uint32_t vcpuid);
|
||||
void vm_handle_exception(struct kvm_vm *vm, int vector,
|
||||
void (*handler)(struct ex_regs *));
|
||||
|
||||
/*
|
||||
* Basic CPU control in CR0
|
||||
*/
|
||||
|
@ -350,3 +350,7 @@ void vcpu_args_set(struct kvm_vm *vm, uint32_t vcpuid, unsigned int num, ...)
|
||||
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void assert_on_unhandled_exception(struct kvm_vm *vm, uint32_t vcpuid)
|
||||
{
|
||||
}
|
||||
|
@ -1204,6 +1204,9 @@ int _vcpu_run(struct kvm_vm *vm, uint32_t vcpuid)
|
||||
do {
|
||||
rc = ioctl(vcpu->fd, KVM_RUN, NULL);
|
||||
} while (rc == -1 && errno == EINTR);
|
||||
|
||||
assert_on_unhandled_exception(vm, vcpuid);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
@ -50,6 +50,8 @@ struct kvm_vm {
|
||||
vm_paddr_t pgd;
|
||||
vm_vaddr_t gdt;
|
||||
vm_vaddr_t tss;
|
||||
vm_vaddr_t idt;
|
||||
vm_vaddr_t handlers;
|
||||
};
|
||||
|
||||
struct vcpu *vcpu_find(struct kvm_vm *vm, uint32_t vcpuid);
|
||||
|
@ -241,3 +241,7 @@ void vcpu_dump(FILE *stream, struct kvm_vm *vm, uint32_t vcpuid, uint8_t indent)
|
||||
fprintf(stream, "%*spstate: psw: 0x%.16llx:0x%.16llx\n",
|
||||
indent, "", vcpu->state->psw_mask, vcpu->state->psw_addr);
|
||||
}
|
||||
|
||||
void assert_on_unhandled_exception(struct kvm_vm *vm, uint32_t vcpuid)
|
||||
{
|
||||
}
|
||||
|
81
tools/testing/selftests/kvm/lib/x86_64/handlers.S
Normal file
81
tools/testing/selftests/kvm/lib/x86_64/handlers.S
Normal file
@ -0,0 +1,81 @@
|
||||
handle_exception:
|
||||
push %r15
|
||||
push %r14
|
||||
push %r13
|
||||
push %r12
|
||||
push %r11
|
||||
push %r10
|
||||
push %r9
|
||||
push %r8
|
||||
|
||||
push %rdi
|
||||
push %rsi
|
||||
push %rbp
|
||||
push %rbx
|
||||
push %rdx
|
||||
push %rcx
|
||||
push %rax
|
||||
mov %rsp, %rdi
|
||||
|
||||
call route_exception
|
||||
|
||||
pop %rax
|
||||
pop %rcx
|
||||
pop %rdx
|
||||
pop %rbx
|
||||
pop %rbp
|
||||
pop %rsi
|
||||
pop %rdi
|
||||
pop %r8
|
||||
pop %r9
|
||||
pop %r10
|
||||
pop %r11
|
||||
pop %r12
|
||||
pop %r13
|
||||
pop %r14
|
||||
pop %r15
|
||||
|
||||
/* Discard vector and error code. */
|
||||
add $16, %rsp
|
||||
iretq
|
||||
|
||||
/*
|
||||
* Build the handle_exception wrappers which push the vector/error code on the
|
||||
* stack and an array of pointers to those wrappers.
|
||||
*/
|
||||
.pushsection .rodata
|
||||
.globl idt_handlers
|
||||
idt_handlers:
|
||||
.popsection
|
||||
|
||||
.macro HANDLERS has_error from to
|
||||
vector = \from
|
||||
.rept \to - \from + 1
|
||||
.align 8
|
||||
|
||||
/* Fetch current address and append it to idt_handlers. */
|
||||
current_handler = .
|
||||
.pushsection .rodata
|
||||
.quad current_handler
|
||||
.popsection
|
||||
|
||||
.if ! \has_error
|
||||
pushq $0
|
||||
.endif
|
||||
pushq $vector
|
||||
jmp handle_exception
|
||||
vector = vector + 1
|
||||
.endr
|
||||
.endm
|
||||
|
||||
.global idt_handler_code
|
||||
idt_handler_code:
|
||||
HANDLERS has_error=0 from=0 to=7
|
||||
HANDLERS has_error=1 from=8 to=8
|
||||
HANDLERS has_error=0 from=9 to=9
|
||||
HANDLERS has_error=1 from=10 to=14
|
||||
HANDLERS has_error=0 from=15 to=16
|
||||
HANDLERS has_error=1 from=17 to=17
|
||||
HANDLERS has_error=0 from=18 to=255
|
||||
|
||||
.section .note.GNU-stack, "", %progbits
|
@ -12,9 +12,18 @@
|
||||
#include "../kvm_util_internal.h"
|
||||
#include "processor.h"
|
||||
|
||||
#ifndef NUM_INTERRUPTS
|
||||
#define NUM_INTERRUPTS 256
|
||||
#endif
|
||||
|
||||
#define DEFAULT_CODE_SELECTOR 0x8
|
||||
#define DEFAULT_DATA_SELECTOR 0x10
|
||||
|
||||
/* Minimum physical address used for virtual translation tables. */
|
||||
#define KVM_GUEST_PAGE_TABLE_MIN_PADDR 0x180000
|
||||
|
||||
vm_vaddr_t exception_handlers;
|
||||
|
||||
/* Virtual translation table structure declarations */
|
||||
struct pageMapL4Entry {
|
||||
uint64_t present:1;
|
||||
@ -557,9 +566,9 @@ static void vcpu_setup(struct kvm_vm *vm, int vcpuid, int pgd_memslot, int gdt_m
|
||||
sregs.efer |= (EFER_LME | EFER_LMA | EFER_NX);
|
||||
|
||||
kvm_seg_set_unusable(&sregs.ldt);
|
||||
kvm_seg_set_kernel_code_64bit(vm, 0x8, &sregs.cs);
|
||||
kvm_seg_set_kernel_data_64bit(vm, 0x10, &sregs.ds);
|
||||
kvm_seg_set_kernel_data_64bit(vm, 0x10, &sregs.es);
|
||||
kvm_seg_set_kernel_code_64bit(vm, DEFAULT_CODE_SELECTOR, &sregs.cs);
|
||||
kvm_seg_set_kernel_data_64bit(vm, DEFAULT_DATA_SELECTOR, &sregs.ds);
|
||||
kvm_seg_set_kernel_data_64bit(vm, DEFAULT_DATA_SELECTOR, &sregs.es);
|
||||
kvm_setup_tss_64bit(vm, &sregs.tr, 0x18, gdt_memslot, pgd_memslot);
|
||||
break;
|
||||
|
||||
@ -1119,3 +1128,102 @@ void kvm_get_cpu_address_width(unsigned int *pa_bits, unsigned int *va_bits)
|
||||
*va_bits = (entry->eax >> 8) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
struct idt_entry {
|
||||
uint16_t offset0;
|
||||
uint16_t selector;
|
||||
uint16_t ist : 3;
|
||||
uint16_t : 5;
|
||||
uint16_t type : 4;
|
||||
uint16_t : 1;
|
||||
uint16_t dpl : 2;
|
||||
uint16_t p : 1;
|
||||
uint16_t offset1;
|
||||
uint32_t offset2; uint32_t reserved;
|
||||
};
|
||||
|
||||
static void set_idt_entry(struct kvm_vm *vm, int vector, unsigned long addr,
|
||||
int dpl, unsigned short selector)
|
||||
{
|
||||
struct idt_entry *base =
|
||||
(struct idt_entry *)addr_gva2hva(vm, vm->idt);
|
||||
struct idt_entry *e = &base[vector];
|
||||
|
||||
memset(e, 0, sizeof(*e));
|
||||
e->offset0 = addr;
|
||||
e->selector = selector;
|
||||
e->ist = 0;
|
||||
e->type = 14;
|
||||
e->dpl = dpl;
|
||||
e->p = 1;
|
||||
e->offset1 = addr >> 16;
|
||||
e->offset2 = addr >> 32;
|
||||
}
|
||||
|
||||
void kvm_exit_unexpected_vector(uint32_t value)
|
||||
{
|
||||
outl(UNEXPECTED_VECTOR_PORT, value);
|
||||
}
|
||||
|
||||
void route_exception(struct ex_regs *regs)
|
||||
{
|
||||
typedef void(*handler)(struct ex_regs *);
|
||||
handler *handlers = (handler *)exception_handlers;
|
||||
|
||||
if (handlers && handlers[regs->vector]) {
|
||||
handlers[regs->vector](regs);
|
||||
return;
|
||||
}
|
||||
|
||||
kvm_exit_unexpected_vector(regs->vector);
|
||||
}
|
||||
|
||||
void vm_init_descriptor_tables(struct kvm_vm *vm)
|
||||
{
|
||||
extern void *idt_handlers;
|
||||
int i;
|
||||
|
||||
vm->idt = vm_vaddr_alloc(vm, getpagesize(), 0x2000, 0, 0);
|
||||
vm->handlers = vm_vaddr_alloc(vm, 256 * sizeof(void *), 0x2000, 0, 0);
|
||||
/* Handlers have the same address in both address spaces.*/
|
||||
for (i = 0; i < NUM_INTERRUPTS; i++)
|
||||
set_idt_entry(vm, i, (unsigned long)(&idt_handlers)[i], 0,
|
||||
DEFAULT_CODE_SELECTOR);
|
||||
}
|
||||
|
||||
void vcpu_init_descriptor_tables(struct kvm_vm *vm, uint32_t vcpuid)
|
||||
{
|
||||
struct kvm_sregs sregs;
|
||||
|
||||
vcpu_sregs_get(vm, vcpuid, &sregs);
|
||||
sregs.idt.base = vm->idt;
|
||||
sregs.idt.limit = NUM_INTERRUPTS * sizeof(struct idt_entry) - 1;
|
||||
sregs.gdt.base = vm->gdt;
|
||||
sregs.gdt.limit = getpagesize() - 1;
|
||||
kvm_seg_set_kernel_data_64bit(NULL, DEFAULT_DATA_SELECTOR, &sregs.gs);
|
||||
vcpu_sregs_set(vm, vcpuid, &sregs);
|
||||
*(vm_vaddr_t *)addr_gva2hva(vm, (vm_vaddr_t)(&exception_handlers)) = vm->handlers;
|
||||
}
|
||||
|
||||
void vm_handle_exception(struct kvm_vm *vm, int vector,
|
||||
void (*handler)(struct ex_regs *))
|
||||
{
|
||||
vm_vaddr_t *handlers = (vm_vaddr_t *)addr_gva2hva(vm, vm->handlers);
|
||||
|
||||
handlers[vector] = (vm_vaddr_t)handler;
|
||||
}
|
||||
|
||||
void assert_on_unhandled_exception(struct kvm_vm *vm, uint32_t vcpuid)
|
||||
{
|
||||
if (vcpu_state(vm, vcpuid)->exit_reason == KVM_EXIT_IO
|
||||
&& vcpu_state(vm, vcpuid)->io.port == UNEXPECTED_VECTOR_PORT
|
||||
&& vcpu_state(vm, vcpuid)->io.size == 4) {
|
||||
/* Grab pointer to io data */
|
||||
uint32_t *data = (void *)vcpu_state(vm, vcpuid)
|
||||
+ vcpu_state(vm, vcpuid)->io.data_offset;
|
||||
|
||||
TEST_ASSERT(false,
|
||||
"Unexpected vectored event in guest (vector:0x%x)",
|
||||
*data);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user