From: Peter Feiner <pfeiner@xxxxxxxxxx> Signed-off-by: Peter Feiner <pfeiner@xxxxxxxxxx> Signed-off-by: David Matlack <dmatlack@xxxxxxxxxx> --- x86/unittests.cfg | 6 ++ x86/vmx.c | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++++- x86/vmx.h | 66 ++++++++++++++++++++ x86/vmx_tests.c | 94 ++++++++++++++++++++++++++++ 4 files changed, 343 insertions(+), 2 deletions(-) diff --git a/x86/unittests.cfg b/x86/unittests.cfg index 8a89eb0ef0ad..f79688af93f8 100644 --- a/x86/unittests.cfg +++ b/x86/unittests.cfg @@ -362,6 +362,12 @@ extra_params = -cpu host,+vmx -append exit_monitor_from_l2_test arch = x86_64 groups = vmx +[vmx_v2] +file = vmx.flat +extra_params = -cpu host,+vmx -append "v2_null_test v2_multiple_entries_test fixture_test_case1 fixture_test_case2" +arch = x86_64 +groups = vmx + [debug] file = debug.flat arch = x86_64 diff --git a/x86/vmx.c b/x86/vmx.c index cd4cb1219040..cae650ae2e68 100644 --- a/x86/vmx.c +++ b/x86/vmx.c @@ -42,10 +42,26 @@ u32 vpid_cnt; void *guest_stack, *guest_syscall_stack; u32 ctrl_pin, ctrl_enter, ctrl_exit, ctrl_cpu[2]; struct regs regs; + struct vmx_test *current; + +#define MAX_TEST_TEARDOWN_STEPS 10 + +struct test_teardown_step { + test_teardown_func func; + void *data; +}; + +static int teardown_count; +static struct test_teardown_step teardown_steps[MAX_TEST_TEARDOWN_STEPS]; + +static test_guest_func v2_guest_main; + u64 hypercall_field; bool launched; static int matched; +static int guest_finished; +static int in_guest; union vmx_basic basic; union vmx_ctrl_msr ctrl_pin_rev; @@ -63,6 +79,8 @@ extern void *guest_entry; static volatile u32 stage; +static jmp_buf abort_target; + struct vmcs_field { u64 mask; u64 encoding; @@ -634,7 +652,10 @@ static void test_vmclear(void) static void __attribute__((__used__)) guest_main(void) { - current->guest_main(); + if (current->v2) + v2_guest_main(); + else + current->guest_main(); } /* guest_entry */ @@ -1409,12 +1430,51 @@ static int handle_hypercall() switch (hypercall_no) { case HYPERCALL_VMEXIT: return VMX_TEST_VMEXIT; + case HYPERCALL_VMABORT: + return VMX_TEST_VMABORT; + case HYPERCALL_VMSKIP: + return VMX_TEST_VMSKIP; default: printf("ERROR : Invalid hypercall number : %ld\n", hypercall_no); } return VMX_TEST_EXIT; } +static void continue_abort(void) +{ + assert(!in_guest); + printf("Host was here when guest aborted:\n"); + dump_stack(); + longjmp(abort_target, 1); + abort(); +} + +void __abort_test(void) +{ + if (in_guest) + hypercall(HYPERCALL_VMABORT); + else + longjmp(abort_target, 1); + abort(); +} + +static void continue_skip(void) +{ + assert(!in_guest); + longjmp(abort_target, 1); + abort(); +} + +void test_skip(const char *msg) +{ + printf("%s skipping test: %s\n", in_guest ? "Guest" : "Host", msg); + if (in_guest) + hypercall(HYPERCALL_VMABORT); + else + longjmp(abort_target, 1); + abort(); +} + static int exit_handler() { int ret; @@ -1453,6 +1513,7 @@ static bool vmx_enter_guest(struct vmentry_failure *failure) { failure->early = 0; + in_guest = 1; asm volatile ( "mov %[HOST_RSP], %%rdi\n\t" "vmwrite %%rsp, %%rdi\n\t" @@ -1478,6 +1539,7 @@ static bool vmx_enter_guest(struct vmentry_failure *failure) : [launched]"m"(launched), [HOST_RSP]"i"(HOST_RSP) : "rdi", "memory", "cc" ); + in_guest = 0; failure->vmlaunch = !launched; failure->instr = launched ? "vmresume" : "vmlaunch"; @@ -1509,6 +1571,7 @@ static int vmx_run() case VMX_TEST_RESUME: continue; case VMX_TEST_VMEXIT: + guest_finished = 1; return 0; case VMX_TEST_EXIT: break; @@ -1527,26 +1590,67 @@ static int vmx_run() } } +static void run_teardown_step(struct test_teardown_step *step) +{ + step->func(step->data); +} + static int test_run(struct vmx_test *test) { + int r; + + /* Validate V2 interface. */ + if (test->v2) { + int ret = 0; + if (test->init || test->guest_main || test->exit_handler || + test->syscall_handler) { + report("V2 test cannot specify V1 callbacks.", 0); + ret = 1; + } + if (ret) + return ret; + } + if (test->name == NULL) test->name = "(no name)"; if (vmx_on()) { printf("%s : vmxon failed.\n", __func__); return 1; } + init_vmcs(&(test->vmcs)); /* Directly call test->init is ok here, init_vmcs has done vmcs init, vmclear and vmptrld*/ if (test->init && test->init(test->vmcs) != VMX_TEST_START) goto out; + teardown_count = 0; + v2_guest_main = NULL; test->exits = 0; current = test; regs = test->guest_regs; vmcs_write(GUEST_RFLAGS, regs.rflags | 0x2); launched = 0; + guest_finished = 0; printf("\nTest suite: %s\n", test->name); - vmx_run(); + + r = setjmp(abort_target); + if (r) { + assert(!in_guest); + goto out; + } + + + if (test->v2) + test->v2(); + else + vmx_run(); + + while (teardown_count > 0) + run_teardown_step(&teardown_steps[--teardown_count]); + + if (launched && !guest_finished) + report("Guest didn't run to completion.", 0); + out: if (vmx_off()) { printf("%s : vmxoff failed.\n", __func__); @@ -1555,6 +1659,77 @@ out: return 0; } +/* + * Add a teardown step. Executed after the test's main function returns. + * Teardown steps executed in reverse order. + */ +void test_add_teardown(test_teardown_func func, void *data) +{ + struct test_teardown_step *step; + + TEST_ASSERT_MSG(teardown_count < MAX_TEST_TEARDOWN_STEPS, + "There are already %d teardown steps.", + teardown_count); + step = &teardown_steps[teardown_count++]; + step->func = func; + step->data = data; +} + +/* + * Set the target of the first enter_guest call. Can only be called once per + * test. Must be called before first enter_guest call. + */ +void test_set_guest(test_guest_func func) +{ + assert(current->v2); + TEST_ASSERT_MSG(!v2_guest_main, "Already set guest func."); + v2_guest_main = func; +} + +/* + * Enters the guest (or launches it for the first time). Error to call once the + * guest has returned (i.e., run past the end of its guest() function). Also + * aborts if guest entry fails. + */ +void enter_guest(void) +{ + struct vmentry_failure failure; + + TEST_ASSERT_MSG(v2_guest_main, + "Never called test_set_guest_func!"); + + TEST_ASSERT_MSG(!guest_finished, + "Called enter_guest() after guest returned."); + + if (!vmx_enter_guest(&failure)) { + print_vmentry_failure_info(&failure); + abort(); + } + + launched = 1; + + if (is_hypercall()) { + int ret; + + ret = handle_hypercall(); + switch (ret) { + case VMX_TEST_VMEXIT: + guest_finished = 1; + break; + case VMX_TEST_VMABORT: + continue_abort(); + break; + case VMX_TEST_VMSKIP: + continue_skip(); + break; + default: + printf("ERROR : Invalid handle_hypercall return %d.\n", + ret); + abort(); + } + } +} + extern struct vmx_test vmx_tests[]; static bool diff --git a/x86/vmx.h b/x86/vmx.h index 3e4015660797..966fff653561 100644 --- a/x86/vmx.h +++ b/x86/vmx.h @@ -54,6 +54,8 @@ struct vmx_test { int (*entry_failure_handler)(struct vmentry_failure *failure); struct vmcs *vmcs; int exits; + /* Alternative test interface. */ + void (*v2)(void); }; union vmx_basic { @@ -490,10 +492,14 @@ enum vm_instruction_error_number { #define VMX_TEST_VMEXIT 1 #define VMX_TEST_EXIT 2 #define VMX_TEST_RESUME 3 +#define VMX_TEST_VMABORT 4 +#define VMX_TEST_VMSKIP 5 #define HYPERCALL_BIT (1ul << 12) #define HYPERCALL_MASK 0xFFF #define HYPERCALL_VMEXIT 0x1 +#define HYPERCALL_VMABORT 0x2 +#define HYPERCALL_VMSKIP 0x3 #define EPTP_PG_WALK_LEN_SHIFT 3ul #define EPTP_AD_FLAG (1ul << 6) @@ -693,4 +699,64 @@ void check_ept_ad(unsigned long *pml4, u64 guest_cr3, void clear_ept_ad(unsigned long *pml4, u64 guest_cr3, unsigned long guest_addr); +void enter_guest(void); + +typedef void (*test_guest_func)(void); +typedef void (*test_teardown_func)(void *data); +void test_set_guest(test_guest_func func); +void test_add_teardown(test_teardown_func func, void *data); +void test_skip(const char *msg); + +void __abort_test(void); + +#define TEST_ASSERT(cond) \ +do { \ + if (!(cond)) { \ + report("%s:%d: Assertion failed: %s", 0, \ + __FILE__, __LINE__, #cond); \ + dump_stack(); \ + __abort_test(); \ + } \ +} while (0) + +#define TEST_ASSERT_MSG(cond, fmt, args...) \ +do { \ + if (!(cond)) { \ + report("%s:%d: Assertion failed: %s\n" fmt, 0, \ + __FILE__, __LINE__, #cond, ##args); \ + dump_stack(); \ + __abort_test(); \ + } \ +} while (0) + +#define __TEST_EQ(a, b, a_str, b_str, assertion, fmt, args...) \ +do { \ + typeof(a) _a = a; \ + typeof(b) _b = b; \ + if (_a != _b) { \ + char _bin_a[BINSTR_SZ]; \ + char _bin_b[BINSTR_SZ]; \ + binstr(_a, _bin_a); \ + binstr(_b, _bin_b); \ + report("%s:%d: %s failed: (%s) == (%s)\n" \ + "\tLHS: 0x%016lx - %s - %lu\n" \ + "\tRHS: 0x%016lx - %s - %lu%s" fmt, 0, \ + __FILE__, __LINE__, \ + assertion ? "Assertion" : "Expectation", a_str, b_str, \ + (unsigned long) _a, _bin_a, (unsigned long) _a, \ + (unsigned long) _b, _bin_b, (unsigned long) _b, \ + fmt[0] == '\0' ? "" : "\n", ## args); \ + dump_stack(); \ + if (assertion) \ + __abort_test(); \ + } \ +} while (0) + +#define TEST_ASSERT_EQ(a, b) __TEST_EQ(a, b, #a, #b, 1, "") +#define TEST_ASSERT_EQ_MSG(a, b, fmt, args...) \ + __TEST_EQ(a, b, #a, #b, 1, fmt, ## args) +#define TEST_EXPECT_EQ(a, b) __TEST_EQ(a, b, #a, #b, 0, "") +#define TEST_EXPECT_EQ_MSG(a, b, fmt, args...) \ + __TEST_EQ(a, b, #a, #b, 0, fmt, ## args) + #endif diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c index 918cade2a3e8..5758b6becfa6 100644 --- a/x86/vmx_tests.c +++ b/x86/vmx_tests.c @@ -1897,6 +1897,95 @@ static int exit_monitor_from_l2_handler(void) return VMX_TEST_EXIT; } +static void assert_exit_reason(u64 expected) +{ + u64 actual = vmcs_read(EXI_REASON); + + TEST_ASSERT_EQ_MSG(expected, actual, "Expected %s, got %s.", + exit_reason_description(expected), + exit_reason_description(actual)); +} + +static void skip_exit_vmcall() +{ + u64 guest_rip = vmcs_read(GUEST_RIP); + u32 insn_len = vmcs_read(EXI_INST_LEN); + + assert_exit_reason(VMX_VMCALL); + vmcs_write(GUEST_RIP, guest_rip + insn_len); +} + +static void v2_null_test_guest(void) +{ +} + +static void v2_null_test(void) +{ + test_set_guest(v2_null_test_guest); + enter_guest(); + report(__func__, 1); +} + +static void v2_multiple_entries_test_guest(void) +{ + vmx_set_test_stage(1); + vmcall(); + vmx_set_test_stage(2); +} + +static void v2_multiple_entries_test(void) +{ + test_set_guest(v2_multiple_entries_test_guest); + enter_guest(); + TEST_ASSERT_EQ(vmx_get_test_stage(), 1); + skip_exit_vmcall(); + enter_guest(); + TEST_ASSERT_EQ(vmx_get_test_stage(), 2); + report(__func__, 1); +} + +static int fixture_test_data = 1; + +static void fixture_test_teardown(void *data) +{ + *((int *) data) = 1; +} + +static void fixture_test_guest(void) +{ + fixture_test_data++; +} + + +static void fixture_test_setup(void) +{ + TEST_ASSERT_EQ_MSG(1, fixture_test_data, + "fixture_test_teardown didn't run?!"); + fixture_test_data = 2; + test_add_teardown(fixture_test_teardown, &fixture_test_data); + test_set_guest(fixture_test_guest); +} + +static void fixture_test_case1(void) +{ + fixture_test_setup(); + TEST_ASSERT_EQ(2, fixture_test_data); + enter_guest(); + TEST_ASSERT_EQ(3, fixture_test_data); + report(__func__, 1); +} + +static void fixture_test_case2(void) +{ + fixture_test_setup(); + TEST_ASSERT_EQ(2, fixture_test_data); + enter_guest(); + TEST_ASSERT_EQ(3, fixture_test_data); + report(__func__, 1); +} + +#define TEST(name) { #name, .v2 = name } + /* name/init/guest_main/exit_handler/syscall_handler/guest_regs */ struct vmx_test vmx_tests[] = { { "null", NULL, basic_guest_main, basic_exit_handler, NULL, {0} }, @@ -1929,5 +2018,10 @@ struct vmx_test vmx_tests[] = { { "into", into_init, into_guest_main, into_exit_handler, NULL, {0} }, { "exit_monitor_from_l2_test", NULL, exit_monitor_from_l2_main, exit_monitor_from_l2_handler, NULL, {0} }, + /* Basic V2 tests. */ + TEST(v2_null_test), + TEST(v2_multiple_entries_test), + TEST(fixture_test_case1), + TEST(fixture_test_case2), { NULL, NULL, NULL, NULL, NULL, {0} }, }; -- 2.12.2.816.g2cccc81164-goog