diff mbox series

[3/4] selftests: kvm: Add exception handling to selftests

Message ID 20201006210444.1342641-4-aaronlewis@google.com (mailing list archive)
State New, archived
Headers show
Series Test MSR exits to userspace | expand

Commit Message

Aaron Lewis Oct. 6, 2020, 9:04 p.m. UTC
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>
---
 tools/testing/selftests/kvm/Makefile          |  19 ++--
 .../selftests/kvm/include/x86_64/processor.h  |  24 +++++
 tools/testing/selftests/kvm/lib/kvm_util.c    |  15 +++
 .../selftests/kvm/lib/kvm_util_internal.h     |   2 +
 .../selftests/kvm/lib/x86_64/handlers.S       |  81 ++++++++++++++
 .../selftests/kvm/lib/x86_64/processor.c      | 100 +++++++++++++++++-
 6 files changed, 232 insertions(+), 9 deletions(-)
 create mode 100644 tools/testing/selftests/kvm/lib/x86_64/handlers.S

Comments

Alexander Graf Oct. 7, 2020, 1:08 p.m. UTC | #1
On 06.10.20 23:04, Aaron Lewis wrote:
> 
> 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>
> ---
>   tools/testing/selftests/kvm/Makefile          |  19 ++--
>   .../selftests/kvm/include/x86_64/processor.h  |  24 +++++
>   tools/testing/selftests/kvm/lib/kvm_util.c    |  15 +++
>   .../selftests/kvm/lib/kvm_util_internal.h     |   2 +
>   .../selftests/kvm/lib/x86_64/handlers.S       |  81 ++++++++++++++
>   .../selftests/kvm/lib/x86_64/processor.c      | 100 +++++++++++++++++-
>   6 files changed, 232 insertions(+), 9 deletions(-)
>   create mode 100644 tools/testing/selftests/kvm/lib/x86_64/handlers.S
> 
> diff --git a/tools/testing/selftests/kvm/Makefile b/tools/testing/selftests/kvm/Makefile
> index 7ebe71fbca53..aaaf992faf87 100644
> --- a/tools/testing/selftests/kvm/Makefile
> +++ b/tools/testing/selftests/kvm/Makefile
> @@ -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
> 
> @@ -110,14 +110,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_C_OBJ) $(LIBKVM_S_OBJ))))
> +$(LIBKVM_C_OBJ): $(OUTPUT)/%.o: %.c
> +       $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
> 
> -x := $(shell mkdir -p $(sort $(dir $(LIBKVM_OBJ))))
> -$(LIBKVM_OBJ): $(OUTPUT)/%.o: %.c
> +$(LIBKVM_S_OBJ): $(OUTPUT)/%.o: %.S
>          $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
> 
> -$(OUTPUT)/libkvm.a: $(LIBKVM_OBJ)
> +LIBKVM_OBJS = $(LIBKVM_C_OBJ) $(LIBKVM_S_OBJ)
> +$(OUTPUT)/libkvm.a: $(LIBKVM_OBJS)
>          $(AR) crs $@ $^
> 
>   x := $(shell mkdir -p $(sort $(dir $(TEST_GEN_PROGS))))
> diff --git a/tools/testing/selftests/kvm/include/x86_64/processor.h b/tools/testing/selftests/kvm/include/x86_64/processor.h
> index 0a65e7bb5249..02530dc6339b 100644
> --- a/tools/testing/selftests/kvm/include/x86_64/processor.h
> +++ b/tools/testing/selftests/kvm/include/x86_64/processor.h
> @@ -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
>    */
> diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c b/tools/testing/selftests/kvm/lib/kvm_util.c
> index 74776ee228f2..9eed3fc21c39 100644
> --- a/tools/testing/selftests/kvm/lib/kvm_util.c
> +++ b/tools/testing/selftests/kvm/lib/kvm_util.c
> @@ -1195,6 +1195,21 @@ int _vcpu_run(struct kvm_vm *vm, uint32_t vcpuid)
>          do {
>                  rc = ioctl(vcpu->fd, KVM_RUN, NULL);
>          } while (rc == -1 && errno == EINTR);
> +
> +#ifdef __x86_64__

Could this be an arch callback?

> +       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);
> +       }
> +#endif
> +
>          return rc;
>   }
> 
> diff --git a/tools/testing/selftests/kvm/lib/kvm_util_internal.h b/tools/testing/selftests/kvm/lib/kvm_util_internal.h
> index 2ef446520748..f07d383d03a1 100644
> --- a/tools/testing/selftests/kvm/lib/kvm_util_internal.h
> +++ b/tools/testing/selftests/kvm/lib/kvm_util_internal.h
> @@ -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);
> diff --git a/tools/testing/selftests/kvm/lib/x86_64/handlers.S b/tools/testing/selftests/kvm/lib/x86_64/handlers.S
> new file mode 100644
> index 000000000000..aaf7bc7d2ce1
> --- /dev/null
> +++ b/tools/testing/selftests/kvm/lib/x86_64/handlers.S
> @@ -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
> diff --git a/tools/testing/selftests/kvm/lib/x86_64/processor.c b/tools/testing/selftests/kvm/lib/x86_64/processor.c
> index 1ccf6c9b3476..c15817b36267 100644
> --- a/tools/testing/selftests/kvm/lib/x86_64/processor.c
> +++ b/tools/testing/selftests/kvm/lib/x86_64/processor.c
> @@ -12,6 +12,13 @@
>   #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
> 
> @@ -557,9 +564,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 +1126,90 @@ 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;
> +
> +       handlers = (handler *)rdmsr(MSR_GS_BASE);

Why can't this just be a global? Using GS to transmit the pointer might 
collide with test cases that modify GS, no?


Alex

> +
> +       if (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;
> +       /* Use GS Base to pass the pointer to the handlers to the guest.*/
> +       kvm_seg_set_kernel_data_64bit(NULL, DEFAULT_DATA_SELECTOR, &sregs.gs);
> +       sregs.gs.base = (unsigned long) vm->handlers;
> +       vcpu_sregs_set(vm, vcpuid, &sregs);
> +}
> +
> +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;
> +}
> --
> 2.28.0.806.g8561365e88-goog
> 



Amazon Development Center Germany GmbH
Krausenstr. 38
10117 Berlin
Geschaeftsfuehrung: Christian Schlaeger, Jonathan Weiss
Eingetragen am Amtsgericht Charlottenburg unter HRB 149173 B
Sitz: Berlin
Ust-ID: DE 289 237 879
diff mbox series

Patch

diff --git a/tools/testing/selftests/kvm/Makefile b/tools/testing/selftests/kvm/Makefile
index 7ebe71fbca53..aaaf992faf87 100644
--- a/tools/testing/selftests/kvm/Makefile
+++ b/tools/testing/selftests/kvm/Makefile
@@ -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
 
@@ -110,14 +110,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_C_OBJ) $(LIBKVM_S_OBJ))))
+$(LIBKVM_C_OBJ): $(OUTPUT)/%.o: %.c
+	$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
 
-x := $(shell mkdir -p $(sort $(dir $(LIBKVM_OBJ))))
-$(LIBKVM_OBJ): $(OUTPUT)/%.o: %.c
+$(LIBKVM_S_OBJ): $(OUTPUT)/%.o: %.S
 	$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
 
-$(OUTPUT)/libkvm.a: $(LIBKVM_OBJ)
+LIBKVM_OBJS = $(LIBKVM_C_OBJ) $(LIBKVM_S_OBJ)
+$(OUTPUT)/libkvm.a: $(LIBKVM_OBJS)
 	$(AR) crs $@ $^
 
 x := $(shell mkdir -p $(sort $(dir $(TEST_GEN_PROGS))))
diff --git a/tools/testing/selftests/kvm/include/x86_64/processor.h b/tools/testing/selftests/kvm/include/x86_64/processor.h
index 0a65e7bb5249..02530dc6339b 100644
--- a/tools/testing/selftests/kvm/include/x86_64/processor.h
+++ b/tools/testing/selftests/kvm/include/x86_64/processor.h
@@ -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
  */
diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c b/tools/testing/selftests/kvm/lib/kvm_util.c
index 74776ee228f2..9eed3fc21c39 100644
--- a/tools/testing/selftests/kvm/lib/kvm_util.c
+++ b/tools/testing/selftests/kvm/lib/kvm_util.c
@@ -1195,6 +1195,21 @@  int _vcpu_run(struct kvm_vm *vm, uint32_t vcpuid)
 	do {
 		rc = ioctl(vcpu->fd, KVM_RUN, NULL);
 	} while (rc == -1 && errno == EINTR);
+
+#ifdef __x86_64__
+	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);
+	}
+#endif
+
 	return rc;
 }
 
diff --git a/tools/testing/selftests/kvm/lib/kvm_util_internal.h b/tools/testing/selftests/kvm/lib/kvm_util_internal.h
index 2ef446520748..f07d383d03a1 100644
--- a/tools/testing/selftests/kvm/lib/kvm_util_internal.h
+++ b/tools/testing/selftests/kvm/lib/kvm_util_internal.h
@@ -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);
diff --git a/tools/testing/selftests/kvm/lib/x86_64/handlers.S b/tools/testing/selftests/kvm/lib/x86_64/handlers.S
new file mode 100644
index 000000000000..aaf7bc7d2ce1
--- /dev/null
+++ b/tools/testing/selftests/kvm/lib/x86_64/handlers.S
@@ -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
diff --git a/tools/testing/selftests/kvm/lib/x86_64/processor.c b/tools/testing/selftests/kvm/lib/x86_64/processor.c
index 1ccf6c9b3476..c15817b36267 100644
--- a/tools/testing/selftests/kvm/lib/x86_64/processor.c
+++ b/tools/testing/selftests/kvm/lib/x86_64/processor.c
@@ -12,6 +12,13 @@ 
 #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
 
@@ -557,9 +564,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 +1126,90 @@  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;
+
+	handlers = (handler *)rdmsr(MSR_GS_BASE);
+
+	if (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;
+	/* Use GS Base to pass the pointer to the handlers to the guest.*/
+	kvm_seg_set_kernel_data_64bit(NULL, DEFAULT_DATA_SELECTOR, &sregs.gs);
+	sregs.gs.base = (unsigned long) vm->handlers;
+	vcpu_sregs_set(vm, vcpuid, &sregs);
+}
+
+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;
+}