Skip to content

Commit f10c729

Browse files
jsmattsonjrbonzini
authored andcommitted
kvm: vmx: Defer setting of DR6 until #DB delivery
When exception payloads are enabled by userspace (which is not yet possible) and a #DB is raised in L2, defer the setting of DR6 until later. Under VMX, this allows the L1 hypervisor to intercept the fault before DR6 is modified. Under SVM, DR6 is modified before L1 can intercept the fault (as has always been the case with DR7). Note that the payload associated with a #DB exception includes only the "new DR6 bits." When the payload is delievered, DR6.B0-B3 will be cleared and DR6.RTM will be set prior to merging in the new DR6 bits. Also note that bit 16 in the "new DR6 bits" is set to indicate that a debug exception (#DB) or a breakpoint exception (#BP) occurred inside an RTM region while advanced debugging of RTM transactional regions was enabled. Though the reverse of DR6.RTM, this makes the #DB payload field compatible with both the pending debug exceptions field under VMX and the exit qualification for #DB exceptions under VMX. Reported-by: Jim Mattson <jmattson@google.com> Suggested-by: Paolo Bonzini <pbonzini@redhat.com> Signed-off-by: Jim Mattson <jmattson@google.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
1 parent da998b4 commit f10c729

File tree

2 files changed

+62
-31
lines changed

2 files changed

+62
-31
lines changed

arch/x86/kvm/vmx.c

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3305,21 +3305,17 @@ static int nested_vmx_check_exception(struct kvm_vcpu *vcpu, unsigned long *exit
33053305
*exit_qual = has_payload ? payload : vcpu->arch.cr2;
33063306
return 1;
33073307
}
3308-
} else {
3309-
/*
3310-
* FIXME: we must not write DR6 when L1 intercepts an
3311-
* L2 #DB exception.
3312-
*/
3313-
if (vmcs12->exception_bitmap & (1u << nr)) {
3314-
if (nr == DB_VECTOR) {
3315-
*exit_qual = vcpu->arch.dr6;
3316-
*exit_qual &= ~(DR6_FIXED_1 | DR6_BT);
3317-
*exit_qual ^= DR6_RTM;
3318-
} else {
3319-
*exit_qual = 0;
3308+
} else if (vmcs12->exception_bitmap & (1u << nr)) {
3309+
if (nr == DB_VECTOR) {
3310+
if (!has_payload) {
3311+
payload = vcpu->arch.dr6;
3312+
payload &= ~(DR6_FIXED_1 | DR6_BT);
3313+
payload ^= DR6_RTM;
33203314
}
3321-
return 1;
3322-
}
3315+
*exit_qual = payload;
3316+
} else
3317+
*exit_qual = 0;
3318+
return 1;
33233319
}
33243320

33253321
return 0;

arch/x86/kvm/x86.c

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,28 @@ void kvm_deliver_exception_payload(struct kvm_vcpu *vcpu)
410410
return;
411411

412412
switch (nr) {
413+
case DB_VECTOR:
414+
/*
415+
* "Certain debug exceptions may clear bit 0-3. The
416+
* remaining contents of the DR6 register are never
417+
* cleared by the processor".
418+
*/
419+
vcpu->arch.dr6 &= ~DR_TRAP_BITS;
420+
/*
421+
* DR6.RTM is set by all #DB exceptions that don't clear it.
422+
*/
423+
vcpu->arch.dr6 |= DR6_RTM;
424+
vcpu->arch.dr6 |= payload;
425+
/*
426+
* Bit 16 should be set in the payload whenever the #DB
427+
* exception should clear DR6.RTM. This makes the payload
428+
* compatible with the pending debug exceptions under VMX.
429+
* Though not currently documented in the SDM, this also
430+
* makes the payload compatible with the exit qualification
431+
* for #DB exceptions under VMX.
432+
*/
433+
vcpu->arch.dr6 ^= payload & DR6_RTM;
434+
break;
413435
case PF_VECTOR:
414436
vcpu->arch.cr2 = payload;
415437
break;
@@ -464,11 +486,13 @@ static void kvm_multiple_exception(struct kvm_vcpu *vcpu,
464486
/*
465487
* In guest mode, payload delivery should be deferred,
466488
* so that the L1 hypervisor can intercept #PF before
467-
* CR2 is modified. However, for ABI compatibility
468-
* with KVM_GET_VCPU_EVENTS and KVM_SET_VCPU_EVENTS,
469-
* we can't delay payload delivery unless userspace
470-
* has enabled this functionality via the per-VM
471-
* capability, KVM_CAP_EXCEPTION_PAYLOAD.
489+
* CR2 is modified (or intercept #DB before DR6 is
490+
* modified under nVMX). However, for ABI
491+
* compatibility with KVM_GET_VCPU_EVENTS and
492+
* KVM_SET_VCPU_EVENTS, we can't delay payload
493+
* delivery unless userspace has enabled this
494+
* functionality via the per-VM capability,
495+
* KVM_CAP_EXCEPTION_PAYLOAD.
472496
*/
473497
if (!vcpu->kvm->arch.exception_payload_enabled ||
474498
!is_guest_mode(vcpu))
@@ -518,6 +542,12 @@ void kvm_requeue_exception(struct kvm_vcpu *vcpu, unsigned nr)
518542
}
519543
EXPORT_SYMBOL_GPL(kvm_requeue_exception);
520544

545+
static void kvm_queue_exception_p(struct kvm_vcpu *vcpu, unsigned nr,
546+
unsigned long payload)
547+
{
548+
kvm_multiple_exception(vcpu, nr, false, 0, true, payload, false);
549+
}
550+
521551
static void kvm_queue_exception_e_p(struct kvm_vcpu *vcpu, unsigned nr,
522552
u32 error_code, unsigned long payload)
523553
{
@@ -6156,14 +6186,7 @@ static void kvm_vcpu_do_singlestep(struct kvm_vcpu *vcpu, int *r)
61566186
kvm_run->exit_reason = KVM_EXIT_DEBUG;
61576187
*r = EMULATE_USER_EXIT;
61586188
} else {
6159-
/*
6160-
* "Certain debug exceptions may clear bit 0-3. The
6161-
* remaining contents of the DR6 register are never
6162-
* cleared by the processor".
6163-
*/
6164-
vcpu->arch.dr6 &= ~15;
6165-
vcpu->arch.dr6 |= DR6_BS | DR6_RTM;
6166-
kvm_queue_exception(vcpu, DB_VECTOR);
6189+
kvm_queue_exception_p(vcpu, DB_VECTOR, DR6_BS);
61676190
}
61686191
}
61696192

@@ -7102,10 +7125,22 @@ static int inject_pending_event(struct kvm_vcpu *vcpu, bool req_int_win)
71027125
__kvm_set_rflags(vcpu, kvm_get_rflags(vcpu) |
71037126
X86_EFLAGS_RF);
71047127

7105-
if (vcpu->arch.exception.nr == DB_VECTOR &&
7106-
(vcpu->arch.dr7 & DR7_GD)) {
7107-
vcpu->arch.dr7 &= ~DR7_GD;
7108-
kvm_update_dr7(vcpu);
7128+
if (vcpu->arch.exception.nr == DB_VECTOR) {
7129+
/*
7130+
* This code assumes that nSVM doesn't use
7131+
* check_nested_events(). If it does, the
7132+
* DR6/DR7 changes should happen before L1
7133+
* gets a #VMEXIT for an intercepted #DB in
7134+
* L2. (Under VMX, on the other hand, the
7135+
* DR6/DR7 changes should not happen in the
7136+
* event of a VM-exit to L1 for an intercepted
7137+
* #DB in L2.)
7138+
*/
7139+
kvm_deliver_exception_payload(vcpu);
7140+
if (vcpu->arch.dr7 & DR7_GD) {
7141+
vcpu->arch.dr7 &= ~DR7_GD;
7142+
kvm_update_dr7(vcpu);
7143+
}
71097144
}
71107145

71117146
kvm_x86_ops->queue_exception(vcpu);

0 commit comments

Comments
 (0)