Set up a test framework that verifies an exception occurring in L2 is forwarded to the right place (L0?, L1?, L2?). To add a test to this framework just add the exception and callbacks to the vmx_exception_tests array. This framework tests two things: 1) It tests that an exception is handled by L2. 2) It tests that an exception is handled by L1. To test that this happens, each exception is triggered twice; once with just an L2 exception handler registered, and again with both an L2 exception handler registered and L1's exception bitmap set. The expectation is that the first exception will be handled by L2 and the second by L1. To implement this support was added to vmx.c to allow more than one L2 test be run in a single test. Previously there was a hard limit of only being allowed to set the L2 guest code once in a given test. That is no longer a limitation with the addition of test_set_guest_restartable(). Support was also added to allow the test to complete without running through the entirety of the L2 guest code. Calling the function test_set_guest_finished() marks the guest code as completed, allowing it to end without running to the end. Signed-off-by: Aaron Lewis <aaronlewis@xxxxxxxxxx> --- lib/x86/desc.c | 2 +- lib/x86/desc.h | 1 + x86/unittests.cfg | 7 ++++ x86/vmx.c | 17 +++++++++ x86/vmx.h | 2 ++ x86/vmx_tests.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 116 insertions(+), 1 deletion(-) diff --git a/lib/x86/desc.c b/lib/x86/desc.c index 16b7256..c2eb16e 100644 --- a/lib/x86/desc.c +++ b/lib/x86/desc.c @@ -91,7 +91,7 @@ struct ex_record { extern struct ex_record exception_table_start, exception_table_end; -static const char* exception_mnemonic(int vector) +const char* exception_mnemonic(int vector) { switch(vector) { case 0: return "#DE"; diff --git a/lib/x86/desc.h b/lib/x86/desc.h index 9b81da0..ad6277b 100644 --- a/lib/x86/desc.h +++ b/lib/x86/desc.h @@ -224,6 +224,7 @@ void set_intr_alt_stack(int e, void *fn); void print_current_tss_info(void); handler handle_exception(u8 v, handler fn); void unhandled_exception(struct ex_regs *regs, bool cpu); +const char* exception_mnemonic(int vector); bool test_for_exception(unsigned int ex, void (*trigger_func)(void *data), void *data); diff --git a/x86/unittests.cfg b/x86/unittests.cfg index 9fcdcae..0353b69 100644 --- a/x86/unittests.cfg +++ b/x86/unittests.cfg @@ -368,6 +368,13 @@ arch = x86_64 groups = vmx nested_exception check = /sys/module/kvm_intel/parameters/allow_smaller_maxphyaddr=Y +[vmx_exception_test] +file = vmx.flat +extra_params = -cpu max,+vmx -append vmx_exception_test +arch = x86_64 +groups = vmx nested_exception +timeout = 10 + [debug] file = debug.flat arch = x86_64 diff --git a/x86/vmx.c b/x86/vmx.c index f4fbb94..9908746 100644 --- a/x86/vmx.c +++ b/x86/vmx.c @@ -1895,6 +1895,23 @@ void test_set_guest(test_guest_func func) v2_guest_main = func; } +/* + * Set the target of the first enter_guest call and reset the RIP so 'func' + * will start from the beginning. This can be called multiple times per test. + */ +void test_set_guest_restartable(test_guest_func func) +{ + assert(current->v2); + v2_guest_main = func; + init_vmcs_guest(); + guest_finished = 0; +} + +void test_set_guest_finished(void) +{ + guest_finished = 1; +} + static void check_for_guest_termination(union exit_reason exit_reason) { if (is_hypercall(exit_reason)) { diff --git a/x86/vmx.h b/x86/vmx.h index 4423986..5321a7e 100644 --- a/x86/vmx.h +++ b/x86/vmx.h @@ -1055,7 +1055,9 @@ void hypercall(u32 hypercall_no); typedef void (*test_guest_func)(void); typedef void (*test_teardown_func)(void *data); void test_set_guest(test_guest_func func); +void test_set_guest_restartable(test_guest_func func); void test_add_teardown(test_teardown_func func, void *data); void test_skip(const char *msg); +void test_set_guest_finished(void); #endif diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c index 3d57ed6..018db2f 100644 --- a/x86/vmx_tests.c +++ b/x86/vmx_tests.c @@ -10701,6 +10701,93 @@ static void vmx_pf_vpid_test(void) __vmx_pf_vpid_test(invalidate_tlb_new_vpid, 1); } +struct vmx_exception_test { + u8 vector; + void (*guest_code)(void); + void (*init_test)(void); + void (*uninit_test)(void); +}; + +struct vmx_exception_test vmx_exception_tests[] = { +}; + +static u8 vmx_exception_test_vector; + +static void vmx_exception_handler(struct ex_regs *regs) +{ + report(regs->vector == vmx_exception_test_vector, + "Handling %s in L2's exception handler", + exception_mnemonic(vmx_exception_test_vector)); + vmcall(); +} + +static void handle_exception_in_l2(u8 vector) +{ + handler old_handler = handle_exception(vector, vmx_exception_handler); + + vmx_exception_test_vector = vector; + + enter_guest(); + report(vmcs_read(EXI_REASON) == VMX_VMCALL, + "%s handled by L2", exception_mnemonic(vector)); + + test_set_guest_finished(); + + handle_exception(vector, old_handler); +} + +static void handle_exception_in_l1(u32 vector) +{ + handler old_handler = handle_exception(vector, vmx_exception_handler); + u32 old_eb = vmcs_read(EXC_BITMAP); + + vmx_exception_test_vector = 0xff; + + vmcs_write(EXC_BITMAP, old_eb | (1u << vector)); + + enter_guest(); + + report((vmcs_read(EXI_REASON) == VMX_EXC_NMI) && + ((vmcs_read(EXI_INTR_INFO) & 0xff) == vector), + "%s handled by L1", exception_mnemonic(vector)); + + test_set_guest_finished(); + + vmcs_write(EXC_BITMAP, old_eb); + handle_exception(vector, old_handler); +} + +static void vmx_exception_test(void) +{ + struct vmx_exception_test *t; + int i; + + for (i = 0; i < ARRAY_SIZE(vmx_exception_tests); i++) { + t = &vmx_exception_tests[i]; + + TEST_ASSERT(t->guest_code); + test_set_guest_restartable(t->guest_code); + + if (t->init_test) + t->init_test(); + + handle_exception_in_l2(t->vector); + + if (t->uninit_test) + t->uninit_test(); + + test_set_guest_restartable(t->guest_code); + + if (t->init_test) + t->init_test(); + + handle_exception_in_l1(t->vector); + + if (t->uninit_test) + t->uninit_test(); + } +} + #define TEST(name) { #name, .v2 = name } /* name/init/guest_main/exit_handler/syscall_handler/guest_regs */ @@ -10810,5 +10897,6 @@ struct vmx_test vmx_tests[] = { TEST(vmx_pf_no_vpid_test), TEST(vmx_pf_invvpid_test), TEST(vmx_pf_vpid_test), + TEST(vmx_exception_test), { NULL, NULL, NULL, NULL, NULL, {0} }, }; -- 2.34.1.173.g76aa8bc2d0-goog