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