[kvm-unit-tests PATCH 6/7] x86/debug: Add single-step #DB + STI/MOVSS blocking tests

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Add a variety of test cases to verify single-step #DB interaction with
STI and MOVSS blocking.  Of particular note are STI blocking and MOVSS
blocking with DR7.GD=1, both of which require manual intervention from
the hypervisor to set vmcs.GUEST_PENDING_DBG_EXCEPTION.BS when
re-injecting an intercepted #DB with STI/MOVSS blocking active.

Cc: David Woodhouse <dwmw2@xxxxxxxxxxxxx>
Cc: Alexander Graf <graf@xxxxxxxxx>
Signed-off-by: Sean Christopherson <seanjc@xxxxxxxxxx>
---
 x86/debug.c | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 176 insertions(+)

diff --git a/x86/debug.c b/x86/debug.c
index 5835a064..0165dc68 100644
--- a/x86/debug.c
+++ b/x86/debug.c
@@ -51,6 +51,11 @@ static inline bool is_single_step_db(unsigned long dr6_val)
 	return dr6_val == (DR6_ACTIVE_LOW | DR6_BS);
 }
 
+static inline bool is_general_detect_db(unsigned long dr6_val)
+{
+	return dr6_val == (DR6_ACTIVE_LOW | DR6_BD);
+}
+
 static inline bool is_icebp_db(unsigned long dr6_val)
 {
 	return dr6_val == DR6_ACTIVE_LOW;
@@ -79,6 +84,8 @@ static void handle_ud(struct ex_regs *regs)
 typedef unsigned long (*db_test_fn)(void);
 typedef void (*db_report_fn)(unsigned long, const char *);
 
+static unsigned long singlestep_with_movss_blocking_and_dr7_gd(void);
+
 static void __run_single_step_db_test(db_test_fn test, db_report_fn report_fn)
 {
 	unsigned long start;
@@ -90,8 +97,13 @@ static void __run_single_step_db_test(db_test_fn test, db_report_fn report_fn)
 	start = test();
 	report_fn(start, "");
 
+	/* MOV DR #GPs at CPL>0, don't try to run the DR7.GD test in usermode. */
+	if (test == singlestep_with_movss_blocking_and_dr7_gd)
+		return;
+
 	n = 0;
 	write_dr6(0);
+
 	/*
 	 * Run the test in usermode.  Use the expected start RIP from the first
 	 * run, the usermode framework doesn't make it easy to get the expected
@@ -178,6 +190,166 @@ static unsigned long singlestep_emulated_instructions(void)
 	return start;
 }
 
+static void report_singlestep_with_sti_blocking(unsigned long start,
+						const char *usermode)
+{
+	report(n == 4 &&
+	       is_single_step_db(dr6[0]) && db_addr[0] == start &&
+	       is_single_step_db(dr6[1]) && db_addr[1] == start + 6 &&
+	       is_single_step_db(dr6[2]) && db_addr[2] == start + 6 + 1 &&
+	       is_single_step_db(dr6[3]) && db_addr[3] == start + 6 + 1 + 1,
+	       "%sSingle-step #DB w/ STI blocking", usermode);
+}
+
+
+static unsigned long singlestep_with_sti_blocking(void)
+{
+	unsigned long start_rip;
+
+	/*
+	 * STI blocking doesn't suppress #DBs, thus the first single-step #DB
+	 * should arrive after the standard one instruction delay.
+	 */
+	asm volatile(
+		"cli\n\t"
+		"pushf\n\t"
+		"pop %%rax\n\t"
+		"or $(1<<8),%%rax\n\t"
+		"push %%rax\n\t"
+		"popf\n\t"
+		"sti\n\t"
+		"1:and $~(1<<8),%%rax\n\t"
+		"push %%rax\n\t"
+		"popf\n\t"
+		"lea 1b,%0\n\t"
+		: "=r" (start_rip) : : "rax"
+	);
+	return start_rip;
+}
+
+static void report_singlestep_with_movss_blocking(unsigned long start,
+						  const char *usermode)
+{
+	report(n == 3 &&
+	       is_single_step_db(dr6[0]) && db_addr[0] == start &&
+	       is_single_step_db(dr6[1]) && db_addr[1] == start + 1 &&
+	       is_single_step_db(dr6[2]) && db_addr[2] == start + 1 + 1,
+	       "%sSingle-step #DB w/ MOVSS blocking", usermode);
+}
+
+static unsigned long singlestep_with_movss_blocking(void)
+{
+	unsigned long start_rip;
+
+	/*
+	 * MOVSS blocking suppresses single-step #DBs (and select other #DBs),
+	 * thus the first single-step #DB should occur after MOVSS blocking
+	 * expires, i.e. two instructions after #DBs are enabled in this case.
+	 */ 
+	asm volatile(
+		"pushf\n\t"
+		"pop %%rax\n\t"
+		"or $(1<<8),%%rax\n\t"
+		"push %%rax\n\t"
+		"mov %%ss, %%ax\n\t"
+		"popf\n\t"
+		"mov %%ax, %%ss\n\t"
+		"and $~(1<<8),%%rax\n\t"
+		"1: push %%rax\n\t"
+		"popf\n\t"
+		"lea 1b,%0\n\t"
+		: "=r" (start_rip) : : "rax"
+	);
+	return start_rip;
+}
+
+
+static void report_singlestep_with_movss_blocking_and_icebp(unsigned long start,
+							    const char *usermode)
+{
+	report(n == 4 &&
+	       is_icebp_db(dr6[0]) && db_addr[0] == start &&
+	       is_single_step_db(dr6[1]) && db_addr[1] == start + 6 &&
+	       is_single_step_db(dr6[2]) && db_addr[2] == start + 6 + 1 &&
+	       is_single_step_db(dr6[3]) && db_addr[3] == start + 6 + 1 + 1,
+	       "%sSingle-Step + ICEBP #DB w/ MOVSS blocking", usermode);
+}
+
+static unsigned long singlestep_with_movss_blocking_and_icebp(void)
+{
+	unsigned long start;
+
+	/*
+	 * ICEBP, a.k.a. INT1 or int1icebrk, is an oddball.  It generates a
+	 * trap-like #DB, is intercepted if #DBs are intercepted, and manifests
+	 * as a #DB VM-Exit, but the VM-Exit occurs on the ICEBP itself, i.e.
+	 * it's treated as an instruction intercept.  Verify that ICEBP is
+	 * correctly emulated as a trap-like #DB when intercepted, and that
+	 * MOVSS blocking is handled correctly with respect to single-step
+	 * breakpoints being enabled.
+	 */
+	asm volatile(
+		"pushf\n\t"
+		"pop %%rax\n\t"
+		"or $(1<<8),%%rax\n\t"
+		"push %%rax\n\t"
+		"mov %%ss, %%ax\n\t"
+		"popf\n\t"
+		"mov %%ax, %%ss\n\t"
+		".byte 0xf1;"
+		"1:and $~(1<<8),%%rax\n\t"
+		"push %%rax\n\t"
+		"popf\n\t"
+		"lea 1b,%0\n\t"
+		: "=r" (start) : : "rax"
+	);
+	return start;
+}
+
+static void report_singlestep_with_movss_blocking_and_dr7_gd(unsigned long start,
+							     const char *ign)
+{
+	report(n == 5 &&
+	       is_general_detect_db(dr6[0]) && db_addr[0] == start &&
+	       is_single_step_db(dr6[1]) && db_addr[1] == start + 3 &&
+	       is_single_step_db(dr6[2]) && db_addr[2] == start + 3 + 6 &&
+	       is_single_step_db(dr6[3]) && db_addr[3] == start + 3 + 6 + 1 &&
+	       is_single_step_db(dr6[4]) && db_addr[4] == start + 3 + 6 + 1 + 1,
+	       "Single-step #DB w/ MOVSS blocking and DR7.GD=1");
+}
+
+static unsigned long singlestep_with_movss_blocking_and_dr7_gd(void)
+{
+	unsigned long start_rip;
+
+	write_dr7(DR7_GD);
+
+	/*
+	 * MOVSS blocking does NOT suppress General Detect #DBs, which have
+	 * fault-like behavior.  Note, DR7.GD is cleared by the CPU upon
+	 * successful delivery of the #DB.  DR6.BD is NOT cleared by the CPU,
+	 * but the MOV DR6 below will be re-executed after handling the
+	 * General Detect #DB.
+	 */
+	asm volatile(
+		"xor %0, %0\n\t"
+		"pushf\n\t"
+		"pop %%rax\n\t"
+		"or $(1<<8),%%rax\n\t"
+		"push %%rax\n\t"
+		"mov %%ss, %%ax\n\t"
+		"popf\n\t"
+		"mov %%ax, %%ss\n\t"
+		"1: mov %0, %%dr6\n\t"
+		"and $~(1<<8),%%rax\n\t"
+		"push %%rax\n\t"
+		"popf\n\t"
+		"lea 1b,%0\n\t"
+		: "=r" (start_rip) : : "rax"
+	);
+	return start_rip;
+}
+
 int main(int ac, char **av)
 {
 	unsigned long cr4;
@@ -238,6 +410,10 @@ int main(int ac, char **av)
 
 	run_ss_db_test(singlestep_basic);
 	run_ss_db_test(singlestep_emulated_instructions);
+	run_ss_db_test(singlestep_with_sti_blocking);
+	run_ss_db_test(singlestep_with_movss_blocking);
+	run_ss_db_test(singlestep_with_movss_blocking_and_icebp);
+	run_ss_db_test(singlestep_with_movss_blocking_and_dr7_gd);
 
 	n = 0;
 	write_dr1((void *)&value);
-- 
2.34.1.703.g22d0c6ccf7-goog




[Index of Archives]     [KVM ARM]     [KVM ia64]     [KVM ppc]     [Virtualization Tools]     [Spice Development]     [Libvirt]     [Libvirt Users]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite Questions]     [Linux Kernel]     [Linux SCSI]     [XFree86]

  Powered by Linux