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