x86: nVMX: Add some corner-case VMX-preemption timer tests
PATCH 1/2 x86: nVMX: Add some corner-case VMX-preemption timer tests
Verify that both injected events and debug traps that result from pending debug exceptions take precedence over a "VMX-preemption timer expired" VM-exit resulting from a zero-valued VMX-preemption timer.
--- x86/vmx_tests.c | 120 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+)
x86/vmx_tests.c +120 -0
@@ -8319,6 +8319,125 @@ static void vmx_store_tsc_test(void) msr_entry.value, low, high); } +static void vmx_preemption_timer_zero_test_db_handler (struct ex_regs *regs) +{ +} + +static void vmx_preemption_timer_zero_test_guest (void ) +{ +while (vmx_get_test_stage () <3 ) +vmcall (); +} + +static void vmx_preemption_timer_zero_activate_preemption_timer (void ) +{ +vmcs_set_bits (PIN_CONTROLS ,PIN_PREEMPT ); +vmcs_write (PREEMPT_TIMER_VALUE ,0 ); +} + +static void vmx_preemption_timer_zero_advance_past_vmcall (void ) +{ +vmcs_clear_bits (PIN_CONTROLS ,PIN_PREEMPT ); +enter_guest (); +skip_exit_vmcall (); +} + +static void vmx_preemption_timer_zero_inject_db (bool intercept_db) +{ +vmx_preemption_timer_zero_activate_preemption_timer (); +vmcs_write (ENT_INTR_INFO ,INTR_INFO_VALID_MASK | +INTR_TYPE_HARD_EXCEPTION |DB_VECTOR ); +vmcs_write (EXC_BITMAP , intercept_db ?1 <<DB_VECTOR :0 ); +enter_guest (); +} + +static void vmx_preemption_timer_zero_set_pending_dbg (u32 exception_bitmap) +{ +vmx_preemption_timer_zero_activate_preemption_timer (); +vmcs_write (GUEST_PENDING_DEBUG ,BIT (12 ) |DR_TRAP1 ); +vmcs_write (EXC_BITMAP , exception_bitmap); +enter_guest (); +} + +static void vmx_preemption_timer_zero_expect_preempt_at_rip (u64 expected_rip) +{ +u32 reason = (u32 )vmcs_read (EXI_REASON ); +u64 guest_rip =vmcs_read (GUEST_RIP ); + +report (reason ==VMX_PREEMPT && guest_rip == expected_rip, +"Exit reason is 0x%x (expected 0x%x) and guest RIP is %lx (0x%lx expected)." , + reason,VMX_PREEMPT , guest_rip, expected_rip); +} + +/* +* This test ensures that when the VMX preemption timer is zero at +* VM-entry, a VM-exit occurs after any event injection and after any +* pending debug exceptions are raised, but before execution of any +* guest instructions. +*/ +static void vmx_preemption_timer_zero_test (void ) +{ +u64 db_fault_address = (u64 )get_idt_addr (&boot_idt[DB_VECTOR ]); +handler old_db; +u32 reason; + +if (!(ctrl_pin_rev.clr &PIN_PREEMPT )) { +report_skip ("'Activate VMX-preemption timer' not supported" ); +return ; + } + +/* +* Install a custom #DB handler that doesn't abort. +*/ + old_db =handle_exception (DB_VECTOR , + vmx_preemption_timer_zero_test_db_handler); + +test_set_guest (vmx_preemption_timer_zero_test_guest); + +/* +* VMX-preemption timer should fire after event injection. +*/ +vmx_set_test_stage (0 ); +vmx_preemption_timer_zero_inject_db (0 ); +vmx_preemption_timer_zero_expect_preempt_at_rip (db_fault_address); +vmx_preemption_timer_zero_advance_past_vmcall (); + +/* +* VMX-preemption timer should fire after event injection. +* Exception bitmap is irrelevant, since you can't intercept +* an event that you injected. +*/ +vmx_set_test_stage (1 ); +vmx_preemption_timer_zero_inject_db (1 <<DB_VECTOR ); +vmx_preemption_timer_zero_expect_preempt_at_rip (db_fault_address); +vmx_preemption_timer_zero_advance_past_vmcall (); + +/* +* VMX-preemption timer should fire after pending debug exceptions +* have delivered a #DB trap. +*/ +vmx_set_test_stage (2 ); +vmx_preemption_timer_zero_set_pending_dbg (0 ); +vmx_preemption_timer_zero_expect_preempt_at_rip (db_fault_address); +vmx_preemption_timer_zero_advance_past_vmcall (); + +/* +* VMX-preemption timer would fire after pending debug exceptions +* have delivered a #DB trap, but in this case, the #DB trap is +* intercepted. +*/ +vmx_set_test_stage (3 ); +vmx_preemption_timer_zero_set_pending_dbg (1 <<DB_VECTOR ); + reason = (u32 )vmcs_read (EXI_REASON ); +report (reason ==VMX_EXC_NMI ,"Exit reason is 0x%x (expected 0x%x)" , + reason,VMX_EXC_NMI ); + +vmcs_clear_bits (PIN_CONTROLS ,PIN_PREEMPT ); +enter_guest (); + +handle_exception (DB_VECTOR , old_db); +} +static void vmx_db_test_guest (void ) {/*
@@ -9623,6 +9742,7 @@ struct vmx_test vmx_tests[] = {TEST (vmx_pending_event_test),TEST (vmx_pending_event_hlt_test),TEST (vmx_store_tsc_test), +TEST (vmx_preemption_timer_zero_test),/* EPT access tests. */ TEST (ept_access_test_not_present), TEST(ept_access_test_read_only),
Signature · 1 line
2.26.0.110.g2183baf09c-goog
PATCH 2/2 x86: VMX: Add another corner-case VMX-preemption timer test
Ensure that the delivery of a "VMX-preemption timer expired" VM-exit doesn't disrupt single-stepping in the guest. Note that passing this test doesn't ensure correctness.
--- x86/vmx_tests.c | 104 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+)
x86/vmx_tests.c +104 -0
@@ -8438,6 +8438,109 @@ static void vmx_preemption_timer_zero_test(void)handle_exception (DB_VECTOR , old_db); } +static u64 vmx_preemption_timer_tf_test_prev_rip; + +static void vmx_preemption_timer_tf_test_db_handler (struct ex_regs *regs) +{ +extern char vmx_preemption_timer_tf_test_endloop; + +if (vmx_get_test_stage () ==2 ) { +/* +* Stage 2 means that we're done, one way or another. +* Arrange for the iret to drop us out of the wbinvd +* loop and stop single-stepping. +*/ + regs->rip = (u64)&vmx_preemption_timer_tf_test_endloop; + regs->rflags &= ~X86_EFLAGS_TF ; + }else if (regs->rip == vmx_preemption_timer_tf_test_prev_rip) { +/* +* The RIP should alternate between the wbinvd and the +* jmp instruction in the code below. If we ever see +* the same instruction twice in a row, that means a +* single-step trap has been dropped. Let the +* hypervisor know about the failure by executing a +* VMCALL. +*/ +vmcall (); + } + vmx_preemption_timer_tf_test_prev_rip = regs->rip; +} + +static void vmx_preemption_timer_tf_test_guest (void ) +{ +/* +* The hypervisor doesn't intercept WBINVD, so the loop below +* shouldn't be a problem--it's just two instructions +* executing in VMX non-root mode. However, when the +* hypervisor is running in a virtual environment, the parent +* hypervisor might intercept WBINVD and emulate it. If the +* parent hypervisor is broken, the single-step trap after the +* WBINVD might be lost. +*/ + asmvolatile ("vmcall\n\t" +"0: wbinvd\n\t" +"1: jmp 0b\n\t" +"vmx_preemption_timer_tf_test_endloop:" ); +} + +/* +* Ensure that the delivery of a "VMX-preemption timer expired" +* VM-exit doesn't disrupt single-stepping in the guest. Note that +* passing this test doesn't ensure correctness, because the test will +* only fail if the VMX-preemtion timer fires at the right time (or +* the wrong time, as it were). +*/ +static void vmx_preemption_timer_tf_test (void ) +{ +handler old_db; +u32 reason; +int i; + +if (!(ctrl_pin_rev.clr &PIN_PREEMPT )) { +report_skip ("'Activate VMX-preemption timer' not supported" ); +return ; + } + + old_db =handle_exception (DB_VECTOR , + vmx_preemption_timer_tf_test_db_handler); + +test_set_guest (vmx_preemption_timer_tf_test_guest); + +enter_guest (); +skip_exit_vmcall (); + +vmx_set_test_stage (1 ); +vmcs_set_bits (PIN_CONTROLS ,PIN_PREEMPT ); +vmcs_write (PREEMPT_TIMER_VALUE ,50000 ); +vmcs_write (GUEST_RFLAGS ,X86_EFLAGS_FIXED |X86_EFLAGS_TF ); + +/* +* The only exit we should see is "VMX-preemption timer +* expired." If we get a VMCALL exit, that means the #DB +* handler has detected a missing single-step trap. It doesn't +* matter where the guest RIP is when the VMX-preemption timer +* expires (whether it's in the WBINVD loop or in the #DB +* handler)--a single-step trap should never be discarded. +*/ +for (i =0 ; i <10000 ; i++) { +enter_guest (); + reason = (u32 )vmcs_read (EXI_REASON ); +if (reason ==VMX_PREEMPT ) +continue ; +TEST_ASSERT (reason ==VMX_VMCALL ); +skip_exit_insn (); +break ; + } + +report (reason ==VMX_PREEMPT ,"No single-step traps skipped" ); + +vmx_set_test_stage (2 ); +vmcs_clear_bits (PIN_CONTROLS ,PIN_PREEMPT ); +enter_guest (); + +handle_exception (DB_VECTOR , old_db); +} +static void vmx_db_test_guest (void ) {/*
@@ -9743,6 +9846,7 @@ struct vmx_test vmx_tests[] = {TEST (vmx_pending_event_hlt_test),TEST (vmx_store_tsc_test),TEST (vmx_preemption_timer_zero_test), +TEST (vmx_preemption_timer_tf_test),/* EPT access tests. */ TEST (ept_access_test_not_present), TEST(ept_access_test_read_only),
Signature · 1 line
2.26.0.110.g2183baf09c-goog
Re: [kvm-unit-tests PATCH 2/2] x86: VMX: Add another corner-case VMX-preemption timer test
Quoted 135 lines · Jim Mattson
Ensure that the delivery of a "VMX-preemption timer expired" VM-exit doesn't disrupt single-stepping in the guest. Note that passing this test doesn't ensure correctness.
Signed-off-by Jim Mattson <[email protected]>Reviewed-by Oliver Upton <[email protected]>Reviewed-by Peter Shier <[email protected]>--- x86/vmx_tests.c | 104 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+)x86/vmx_tests.c +104
-1@@ -8438,6 +8438,109 @@ static void vmx_preemption_timer_zero_test(void)handle_exception (DB_VECTOR , old_db); } +static u64 vmx_preemption_timer_tf_test_prev_rip; + +static void vmx_preemption_timer_tf_test_db_handler (struct ex_regs *regs) +{ +extern char vmx_preemption_timer_tf_test_endloop; + +if (vmx_get_test_stage () ==2 ) { +/* +* Stage 2 means that we're done, one way or another. +* Arrange for the iret to drop us out of the wbinvd +* loop and stop single-stepping. +*/ + regs->rip = (u64)&vmx_preemption_timer_tf_test_endloop; + regs->rflags &= ~X86_EFLAGS_TF ; + }else if (regs->rip == vmx_preemption_timer_tf_test_prev_rip) { +/* +* The RIP should alternate between the wbinvd and the +* jmp instruction in the code below. If we ever see +* the same instruction twice in a row, that means a +* single-step trap has been dropped. Let the +* hypervisor know about the failure by executing a +* VMCALL. +*/ +vmcall (); + } + vmx_preemption_timer_tf_test_prev_rip = regs->rip; +} + +static void vmx_preemption_timer_tf_test_guest (void ) +{ +/* +* The hypervisor doesn't intercept WBINVD, so the loop below +* shouldn't be a problem--it's just two instructions +* executing in VMX non-root mode. However, when the +* hypervisor is running in a virtual environment, the parent +* hypervisor might intercept WBINVD and emulate it. If the +* parent hypervisor is broken, the single-step trap after the +* WBINVD might be lost. +*/ + asmvolatile ("vmcall\n\t" +"0: wbinvd\n\t" +"1: jmp 0b\n\t" +"vmx_preemption_timer_tf_test_endloop:" ); +} + +/* +* Ensure that the delivery of a "VMX-preemption timer expired" +* VM-exit doesn't disrupt single-stepping in the guest. Note that +* passing this test doesn't ensure correctness, because the test will +* only fail if the VMX-preemtion timer fires at the right time (or +* the wrong time, as it were). +*/ +static void vmx_preemption_timer_tf_test (void ) +{ +handler old_db; +u32 reason; +int i; + +if (!(ctrl_pin_rev.clr &PIN_PREEMPT )) { +report_skip ("'Activate VMX-preemption timer' not supported" ); +return ; + } + + old_db =handle_exception (DB_VECTOR , + vmx_preemption_timer_tf_test_db_handler); + +test_set_guest (vmx_preemption_timer_tf_test_guest); + +enter_guest (); +skip_exit_vmcall (); + +vmx_set_test_stage (1 ); +vmcs_set_bits (PIN_CONTROLS ,PIN_PREEMPT ); +vmcs_write (PREEMPT_TIMER_VALUE ,50000 ); +vmcs_write (GUEST_RFLAGS ,X86_EFLAGS_FIXED |X86_EFLAGS_TF ); + +/* +* The only exit we should see is "VMX-preemption timer +* expired." If we get a VMCALL exit, that means the #DB +* handler has detected a missing single-step trap. It doesn't +* matter where the guest RIP is when the VMX-preemption timer +* expires (whether it's in the WBINVD loop or in the #DB +* handler)--a single-step trap should never be discarded. +*/ +for (i =0 ; i <10000 ; i++) { +enter_guest (); + reason = (u32 )vmcs_read (EXI_REASON ); +if (reason ==VMX_PREEMPT ) +continue ; +TEST_ASSERT (reason ==VMX_VMCALL ); +skip_exit_insn (); +break ; + } + +report (reason ==VMX_PREEMPT ,"No single-step traps skipped" ); + +vmx_set_test_stage (2 ); +vmcs_clear_bits (PIN_CONTROLS ,PIN_PREEMPT ); +enter_guest (); + +handle_exception (DB_VECTOR , old_db); +} +static void vmx_db_test_guest (void ) {/* @@ -9743,6 +9846,7 @@ struct vmx_test vmx_tests[] = {TEST (vmx_pending_event_hlt_test),TEST (vmx_store_tsc_test),TEST (vmx_preemption_timer_zero_test), +TEST (vmx_preemption_timer_tf_test),/* EPT access tests. */ TEST (ept_access_test_not_present), TEST(ept_access_test_read_only), --
Quoted 1 line
2.26.0.110.g2183baf09c-goog
Adding +Paolo Bonzini.
Quoted 151 lines · Jim Mattson
Verify that both injected events and debug traps that result from pending debug exceptions take precedence over a "VMX-preemption timer expired" VM-exit resulting from a zero-valued VMX-preemption timer.
Signed-off-by Jim Mattson <[email protected]>Reviewed-by Oliver Upton <[email protected]>Reviewed-by Peter Shier <[email protected]>--- x86/vmx_tests.c | 120 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+)x86/vmx_tests.c +120
-1@@ -8319,6 +8319,125 @@ static void vmx_store_tsc_test(void) msr_entry.value, low, high); } +static void vmx_preemption_timer_zero_test_db_handler (struct ex_regs *regs) +{ +} + +static void vmx_preemption_timer_zero_test_guest (void ) +{ +while (vmx_get_test_stage () <3 ) +vmcall (); +} + +static void vmx_preemption_timer_zero_activate_preemption_timer (void ) +{ +vmcs_set_bits (PIN_CONTROLS ,PIN_PREEMPT ); +vmcs_write (PREEMPT_TIMER_VALUE ,0 ); +} + +static void vmx_preemption_timer_zero_advance_past_vmcall (void ) +{ +vmcs_clear_bits (PIN_CONTROLS ,PIN_PREEMPT ); +enter_guest (); +skip_exit_vmcall (); +} + +static void vmx_preemption_timer_zero_inject_db (bool intercept_db) +{ +vmx_preemption_timer_zero_activate_preemption_timer (); +vmcs_write (ENT_INTR_INFO ,INTR_INFO_VALID_MASK | +INTR_TYPE_HARD_EXCEPTION |DB_VECTOR ); +vmcs_write (EXC_BITMAP , intercept_db ?1 <<DB_VECTOR :0 ); +enter_guest (); +} + +static void vmx_preemption_timer_zero_set_pending_dbg (u32 exception_bitmap) +{ +vmx_preemption_timer_zero_activate_preemption_timer (); +vmcs_write (GUEST_PENDING_DEBUG ,BIT (12 ) |DR_TRAP1 ); +vmcs_write (EXC_BITMAP , exception_bitmap); +enter_guest (); +} + +static void vmx_preemption_timer_zero_expect_preempt_at_rip (u64 expected_rip) +{ +u32 reason = (u32 )vmcs_read (EXI_REASON ); +u64 guest_rip =vmcs_read (GUEST_RIP ); + +report (reason ==VMX_PREEMPT && guest_rip == expected_rip, +"Exit reason is 0x%x (expected 0x%x) and guest RIP is %lx (0x%lx expected)." , + reason,VMX_PREEMPT , guest_rip, expected_rip); +} + +/* +* This test ensures that when the VMX preemption timer is zero at +* VM-entry, a VM-exit occurs after any event injection and after any +* pending debug exceptions are raised, but before execution of any +* guest instructions. +*/ +static void vmx_preemption_timer_zero_test (void ) +{ +u64 db_fault_address = (u64 )get_idt_addr (&boot_idt[DB_VECTOR ]); +handler old_db; +u32 reason; + +if (!(ctrl_pin_rev.clr &PIN_PREEMPT )) { +report_skip ("'Activate VMX-preemption timer' not supported" ); +return ; + } + +/* +* Install a custom #DB handler that doesn't abort. +*/ + old_db =handle_exception (DB_VECTOR , + vmx_preemption_timer_zero_test_db_handler); + +test_set_guest (vmx_preemption_timer_zero_test_guest); + +/* +* VMX-preemption timer should fire after event injection. +*/ +vmx_set_test_stage (0 ); +vmx_preemption_timer_zero_inject_db (0 ); +vmx_preemption_timer_zero_expect_preempt_at_rip (db_fault_address); +vmx_preemption_timer_zero_advance_past_vmcall (); + +/* +* VMX-preemption timer should fire after event injection. +* Exception bitmap is irrelevant, since you can't intercept +* an event that you injected. +*/ +vmx_set_test_stage (1 ); +vmx_preemption_timer_zero_inject_db (1 <<DB_VECTOR ); +vmx_preemption_timer_zero_expect_preempt_at_rip (db_fault_address); +vmx_preemption_timer_zero_advance_past_vmcall (); + +/* +* VMX-preemption timer should fire after pending debug exceptions +* have delivered a #DB trap. +*/ +vmx_set_test_stage (2 ); +vmx_preemption_timer_zero_set_pending_dbg (0 ); +vmx_preemption_timer_zero_expect_preempt_at_rip (db_fault_address); +vmx_preemption_timer_zero_advance_past_vmcall (); + +/* +* VMX-preemption timer would fire after pending debug exceptions +* have delivered a #DB trap, but in this case, the #DB trap is +* intercepted. +*/ +vmx_set_test_stage (3 ); +vmx_preemption_timer_zero_set_pending_dbg (1 <<DB_VECTOR ); + reason = (u32 )vmcs_read (EXI_REASON ); +report (reason ==VMX_EXC_NMI ,"Exit reason is 0x%x (expected 0x%x)" , + reason,VMX_EXC_NMI ); + +vmcs_clear_bits (PIN_CONTROLS ,PIN_PREEMPT ); +enter_guest (); + +handle_exception (DB_VECTOR , old_db); +} +static void vmx_db_test_guest (void ) {/* @@ -9623,6 +9742,7 @@ struct vmx_test vmx_tests[] = {TEST (vmx_pending_event_test),TEST (vmx_pending_event_hlt_test),TEST (vmx_store_tsc_test), +TEST (vmx_preemption_timer_zero_test),/* EPT access tests. */ TEST (ept_access_test_not_present), TEST(ept_access_test_read_only), --
Quoted 1 line
2.26.0.110.g2183baf09c-goog
Adding +Paolo Bonzini.