KVM: selftests: Verify VMX's GUEST_PENDING_DBG_EXCEPTIONS.BS Consistency Check

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>
This commit is contained in:
Hou Wenlong 2026-05-15 15:26:38 -07:00 committed by Sean Christopherson
parent 063a22451f
commit b8284279b7

View File

@ -15,11 +15,51 @@
#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);