]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
KVM: selftests: Verify VMX's GUEST_PENDING_DBG_EXCEPTIONS.BS Consistency Check
authorHou Wenlong <houwenlong.hwl@antgroup.com>
Fri, 15 May 2026 22:26:38 +0000 (15:26 -0700)
committerSean Christopherson <seanjc@google.com>
Thu, 21 May 2026 21:40:25 +0000 (14:40 -0700)
In x86's debug_regs test, add a test case to cover the scenario where a
single-step #DB occurs in an STI-shadow, in which case KVM needs to stuff
vmcs.GUEST_PENDING_DBG_EXCEPTIONS.BS in order to satisfy a flawed VM-Entry
Consistency Check.

Wire up an IRQ handler to gain a bit of bonus coverage, as the subsequent
IRET from the #DB sets RFLAGS.IF, but *without* STI-blocking, and so the
pending IRQ is expected on the instruction immediately following STI.

Signed-off-by: Hou Wenlong <houwenlong.hwl@antgroup.com>
[sean: expect the IRQ on the CLI, and explain why]
Link: https://patch.msgid.link/20260515222638.1949982-11-seanjc@google.com
Signed-off-by: Sean Christopherson <seanjc@google.com>
tools/testing/selftests/kvm/x86/debug_regs.c

index 13aaa7f3f8d945e8da2ddb5207b8176eda58e1df..2a2ef3e179ffd56d282b77eb819b99fd6c7a1a30 100644 (file)
 
 #define IRQ_VECTOR 0xAA
 
+#define  CAST_TO_RIP(v)  ((unsigned long long)&(v))
+
 /* For testing data access debug BP */
 u32 guest_value;
 
 extern unsigned char sw_bp, hw_bp, write_data, ss_start, bd_start;
-extern unsigned char fep_bd_start;
+extern unsigned char fep_bd_start, fep_sti_start, fep_sti_end;
+
+static int irqs_received;
+
+static void guest_db_handler(struct ex_regs *regs)
+{
+       static int count;
+       unsigned long target_rips[2] = {
+               CAST_TO_RIP(fep_sti_start),
+               CAST_TO_RIP(fep_sti_end),
+       };
+
+       __GUEST_ASSERT(regs->rip == target_rips[count],
+                      "STI[%u]: unexpected rip 0x%lx (should be 0x%lx)",
+                      count, regs->rip, target_rips[count]);
+       regs->rflags &= ~X86_EFLAGS_TF;
+       count++;
+}
+
+static void guest_irq_handler(struct ex_regs *regs)
+{
+       /*
+        * The pending IRQ should finally be take when KVM_GUESTDBG_BLOCKIRQ is
+        * cleared and IRQs are enabled.  Note, the IRQ is expected to arrive
+        * on the instruction immediately after STI, even though its in an STI
+        * shadow.  Because the next instruction has a coincident #DB, and #DBs
+        * are not subject to STI-blocking, the #DB will push RFLAGS.IF=1 on
+        * the stack, and the eventual IRET will unmask IRQs and obliterate the
+        * STI shadow in the process.
+        */
+       unsigned long target_rip = CAST_TO_RIP(fep_sti_start);
+
+       __GUEST_ASSERT(regs->rip == target_rip,
+                      "IRQ: unexpected rip 0x%lx (should be 0x%lx)",
+                      regs->rip, target_rip);
+
+       irqs_received++;
+       x2apic_write_reg(APIC_EOI, 0);
+}
 
 static void guest_code(void)
 {
@@ -66,14 +106,32 @@ static void guest_code(void)
        /* DR6.BD test */
        asm volatile("bd_start: mov %%dr0, %%rax" : : : "rax");
 
-       if (is_forced_emulation_enabled)
+       /*
+        * Note, the IRET from the #DB that occurs in the below STI-shadow will
+        * unmask IRQs, i.e. the pending interrupt will be delivered after #DB
+        * handling, on the CLI!
+        */
+       if (is_forced_emulation_enabled) {
                asm volatile(KVM_FEP "fep_bd_start: mov %%dr0, %%rax" : : : "rax");
 
+               /* pending debug exceptions for emulation */
+               asm volatile("pushf\n\t"
+                            "orq $" __stringify(X86_EFLAGS_TF) ", (%rsp)\n\t"
+                            "popf\n\t"
+                            "sti\n\t"
+                            "fep_sti_start:"
+                            "cli\n\t"
+                            "pushf\n\t"
+                            "orq $" __stringify(X86_EFLAGS_TF) ", (%rsp)\n\t"
+                            "popf\n\t"
+                            KVM_FEP "sti\n\t"
+                            "fep_sti_end:"
+                            "cli\n\t");
+               GUEST_ASSERT(irqs_received == 1);
+       }
        GUEST_DONE();
 }
 
-#define  CAST_TO_RIP(v)  ((unsigned long long)&(v))
-
 static void vcpu_skip_insn(struct kvm_vcpu *vcpu, int insn_len)
 {
        struct kvm_regs regs;
@@ -227,6 +285,9 @@ int main(void)
        memset(&debug, 0, sizeof(debug));
        vcpu_guest_debug_set(vcpu, &debug);
 
+       vm_install_exception_handler(vm, DB_VECTOR, guest_db_handler);
+       vm_install_exception_handler(vm, IRQ_VECTOR, guest_irq_handler);
+
        vcpu_run(vcpu);
        TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
        cmd = get_ucall(vcpu, &uc);