Test a single-step trap delivered by hardware to set expectations, and then test a single-step trap synthesized by L0 to see if those expectations are met. As a bonus, test a single-step trap in a transactional region to set expectations for that unusual case as well. Signed-off-by: Jim Mattson <jmattson@xxxxxxxxxx> Reviewed-by: Peter Shier <pshier@xxxxxxxxxx> --- x86/unittests.cfg | 6 ++ x86/vmx_tests.c | 157 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+) diff --git a/x86/unittests.cfg b/x86/unittests.cfg index 2d04806..e455d96 100644 --- a/x86/unittests.cfg +++ b/x86/unittests.cfg @@ -566,6 +566,12 @@ extra_params = -cpu host,+vmx -m 2560 -append vmx_nm_test arch = x86_64 groups = vmx +[vmx_db_test] +file = vmx.flat +extra_params = -cpu host,+vmx -m 2560 -append vmx_db_test +arch = x86_64 +groups = vmx + [vmx_eoi_bitmap_ioapic_scan] file = vmx.flat smp = 2 diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c index 1574970..524b82d 100644 --- a/x86/vmx_tests.c +++ b/x86/vmx_tests.c @@ -3,6 +3,9 @@ * * Author : Arthur Chunqi Li <yzt356@xxxxxxxxx> */ + +#include <asm/debugreg.h> + #include "vmx.h" #include "msr.h" #include "processor.h" @@ -4684,6 +4687,159 @@ static void vmx_nm_test(void) enter_guest(); } +static void vmx_db_test_guest(void) +{ + /* + * For a hardware generated single-step #DB. + */ + asm volatile("vmcall;" + "nop;" + ".Lpost_nop:"); + /* + * For an L0 synthesized single-step #DB. (L0 intercepts WBINVD and + * emulates it in software.) + */ + asm volatile("vmcall;" + "wbinvd;" + ".Lpost_wbinvd:"); + /* + * For a hardware generated single-step #DB in a transactional region. + */ + asm volatile("vmcall;" + ".Lxbegin: xbegin .Lpost_xbegin;" + ".Lpost_xbegin:"); +} + +/* + * Clear the pending debug exceptions and RFLAGS.TF and re-enter + * L2. No #DB is delivered and L2 continues to the next point of + * interest. + */ +static void dismiss_db(void) +{ + vmcs_write(GUEST_PENDING_DEBUG, 0); + vmcs_write(GUEST_RFLAGS, X86_EFLAGS_FIXED); + enter_guest(); +} + +/* + * Check a variety of VMCS fields relevant to an intercepted #DB exception. + * Then throw away the #DB exception and resume L2. + */ +static void check_db_exit(bool xfail, void *expected_rip, + u64 expected_exit_qual, u64 expected_dr6) +{ + u32 reason = vmcs_read(EXI_REASON); + u32 intr_info = vmcs_read(EXI_INTR_INFO); + u64 exit_qual = vmcs_read(EXI_QUALIFICATION); + u64 guest_rip = vmcs_read(GUEST_RIP); + u64 guest_pending_dbg = vmcs_read(GUEST_PENDING_DEBUG); + u64 dr6 = read_dr6(); + const u32 expected_intr_info = INTR_INFO_VALID_MASK | + INTR_TYPE_HARD_EXCEPTION | DB_VECTOR; + + report("Expected #DB VM-exit", + reason == VMX_EXC_NMI && intr_info == expected_intr_info); + report("Expected RIP %p (actual %lx)", (u64)expected_rip == guest_rip, + expected_rip, guest_rip); + report("Expected pending debug exceptions 0 (actual %lx)", + 0 == guest_pending_dbg, guest_pending_dbg); + report_xfail("Expected exit qualification %lx (actual %lx)", xfail, + expected_exit_qual == exit_qual, + expected_exit_qual, exit_qual); + report_xfail("Expected DR6 %lx (actual %lx)", xfail, + expected_dr6 == dr6, expected_dr6, dr6); + dismiss_db(); +} + +/* + * Assuming the guest has just exited on a VMCALL instruction, skip + * over the vmcall, set the guest's RFLAGS.TF in the VMCS, and resume + * L2. + */ +static void single_step_guest(u64 starting_dr6) +{ + skip_exit_vmcall(); + write_dr6(starting_dr6); + vmcs_write(GUEST_RFLAGS, X86_EFLAGS_FIXED | X86_EFLAGS_TF); + enter_guest(); +} + +/* + * If L1 intercepts #DB, verify that a single-step trap clears pending + * debug exceptions, populates the exit qualification field properly, + * and that DR6 is not prematurely clobbered. + */ +static void vmx_db_test(void) +{ + /* + * We are going to set a few arbitrary bits in DR6 to verify that + * (a) DR6 is not modified by an intercepted #DB, and + * (b) stale bits in DR6 don't leak into the exit qualification + * field for a subsequent #DB exception. + */ + const u64 starting_dr6 = DR6_RESERVED | DR_SWITCH | 0xa; + extern char post_nop asm(".Lpost_nop"); + extern char post_wbinvd asm(".Lpost_wbinvd"); + extern char xbegin asm(".Lxbegin"); + extern char post_xbegin asm(".Lpost_xbegin"); + + /* + * L1 wants to intercept #DB exceptions encountered in L2. + */ + vmcs_write(EXC_BITMAP, BIT(DB_VECTOR)); + + /* + * Start L2 and run it up to the first point of interest. + */ + test_set_guest(vmx_db_test_guest); + enter_guest(); + + /* + * Hardware-delivered #DB trap for single-step sets the standard. + */ + single_step_guest(starting_dr6); + printf("\nHardware delivered single-step\n"); + check_db_exit(false, &post_nop, DR_STEP, starting_dr6); + + /* + * L0 synthesized #DB trap for single-step is buggy, because + * kvm (a) clobbers DR6 too early, and (b) tries its best to + * reconstitute the exit qualification from the prematurely + * modified DR6, but fails miserably. + */ + single_step_guest(starting_dr6); + printf("\nSoftware synthesized single-step\n"); + check_db_exit(true, &post_wbinvd, DR_STEP, starting_dr6); + + /* + * Optional RTM test for hardware that supports RTM, to + * demonstrate that the current volume 3 of the SDM + * (325384-067US), table 27-1 is incorrect. Bit 16 of the exit + * qualification for debug exceptions is not reserved. It is + * set to 1 if a debug exception (#DB) or a breakpoint + * exception (#BP) occurs inside an RTM region while advanced + * debugging of RTM transactional regions is enabled. + */ + if (cpuid(7).b & BIT(11)) { + vmcs_write(ENT_CONTROLS, + vmcs_read(ENT_CONTROLS) | ENT_LOAD_DBGCTLS); + /* + * Set DR7.RTM[bit 11] and IA32_DEBUGCTL.RTM[bit 15] + * in the guest to enable advanced debugging of RTM + * transactional regions. + */ + vmcs_write(GUEST_DR7, BIT(11)); + vmcs_write(GUEST_DEBUGCTL, BIT(15)); + single_step_guest(starting_dr6); + printf("\nHardware delivered single-step in transactional region\n"); + check_db_exit(false, &xbegin, BIT(16), starting_dr6); + } else { + vmcs_write(GUEST_RIP, (u64)&post_xbegin); + enter_guest(); + } +} + static bool cpu_has_apicv(void) { return ((ctrl_cpu_rev[1].clr & CPU_APIC_REG_VIRT) && @@ -5245,6 +5401,7 @@ struct vmx_test vmx_tests[] = { /* Regression tests */ TEST(vmx_cr_load_test), TEST(vmx_nm_test), + TEST(vmx_db_test), /* EPT access tests. */ TEST(ept_access_test_not_present), TEST(ept_access_test_read_only), -- 2.19.0.444.g18242da7ef-goog