@@ -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
@@ -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
@@ -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
@@ -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} },
};