@@ -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
@@ -3,6 +3,9 @@
*
* Author : Arthur Chunqi Li <yzt356@gmail.com>
*/
+
+#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),