Allow ktr(4) tracing of all guest exceptions via the tunable

"hw.vmm.trace_guest_exceptions".  To enable this feature set the tunable
to "1" before loading vmm.ko.

Tracing the guest exceptions can be useful when debugging guest triple faults.

Note that there is a performance impact when exception tracing is enabled
since every exception will now trigger a VM-exit.

Also, handle machine check exceptions that happen during guest execution
by vectoring to the host's machine check handler via "int $18".

Discussed with:	grehan
MFC after:	2 weeks
This commit is contained in:
neel 2014-12-23 02:14:49 +00:00
parent 423cddba34
commit 4d52b44708
6 changed files with 177 additions and 17 deletions

View File

@ -358,6 +358,8 @@ void vm_copyin(struct vm *vm, int vcpuid, struct vm_copyinfo *copyinfo,
void *kaddr, size_t len);
void vm_copyout(struct vm *vm, int vcpuid, const void *kaddr,
struct vm_copyinfo *copyinfo, size_t len);
int vcpu_trace_exceptions(struct vm *vm, int vcpuid);
#endif /* KERNEL */
#define VM_MAXCPU 16 /* maximum virtual cpus */

View File

@ -46,6 +46,7 @@ __FBSDID("$FreeBSD$");
#include <machine/specialreg.h>
#include <machine/smp.h>
#include <machine/vmm.h>
#include <machine/vmm_dev.h>
#include <machine/vmm_instruction_emul.h>
#include "vmm_lapic.h"
@ -429,8 +430,24 @@ vmcb_init(struct svm_softc *sc, int vcpu, uint64_t iopm_base_pa,
svm_enable_intercept(sc, vcpu, VMCB_CR_INTCPT, mask);
}
/* Intercept Machine Check exceptions. */
svm_enable_intercept(sc, vcpu, VMCB_EXC_INTCPT, BIT(IDT_MC));
/*
* Intercept everything when tracing guest exceptions otherwise
* just intercept machine check exception.
*/
if (vcpu_trace_exceptions(sc->vm, vcpu)) {
for (n = 0; n < 32; n++) {
/*
* Skip unimplemented vectors in the exception bitmap.
*/
if (n == 2 || n == 9) {
continue;
}
svm_enable_intercept(sc, vcpu, VMCB_EXC_INTCPT, BIT(n));
}
} else {
svm_enable_intercept(sc, vcpu, VMCB_EXC_INTCPT, BIT(IDT_MC));
}
/* Intercept various events (for e.g. I/O, MSR and CPUID accesses) */
svm_enable_intercept(sc, vcpu, VMCB_CTRL1_INTCPT, VMCB_INTCPT_IO);
@ -1176,9 +1193,10 @@ svm_vmexit(struct svm_softc *svm_sc, int vcpu, struct vm_exit *vmexit)
struct vmcb_state *state;
struct vmcb_ctrl *ctrl;
struct svm_regctx *ctx;
struct vm_exception exception;
uint64_t code, info1, info2, val;
uint32_t eax, ecx, edx;
int handled;
int error, errcode_valid, handled, idtvec, reflect;
bool retu;
ctx = svm_get_guest_regctx(svm_sc, vcpu);
@ -1237,8 +1255,78 @@ svm_vmexit(struct svm_softc *svm_sc, int vcpu, struct vm_exit *vmexit)
case VMCB_EXIT_NMI: /* external NMI */
handled = 1;
break;
case VMCB_EXIT_MC: /* machine check */
case 0x40 ... 0x5F:
vmm_stat_incr(svm_sc->vm, vcpu, VMEXIT_EXCEPTION, 1);
reflect = 1;
idtvec = code - 0x40;
switch (idtvec) {
case IDT_MC:
/*
* Call the machine check handler by hand. Also don't
* reflect the machine check back into the guest.
*/
reflect = 0;
VCPU_CTR0(svm_sc->vm, vcpu, "Vectoring to MCE handler");
__asm __volatile("int $18");
break;
case IDT_PF:
error = svm_setreg(svm_sc, vcpu, VM_REG_GUEST_CR2,
info2);
KASSERT(error == 0, ("%s: error %d updating cr2",
__func__, error));
/* fallthru */
case IDT_NP:
case IDT_SS:
case IDT_GP:
case IDT_AC:
case IDT_TS:
errcode_valid = 1;
break;
case IDT_DF:
errcode_valid = 1;
info1 = 0;
break;
case IDT_BP:
case IDT_OF:
case IDT_BR:
/*
* The 'nrip' field is populated for INT3, INTO and
* BOUND exceptions and this also implies that
* 'inst_length' is non-zero.
*
* Reset 'inst_length' to zero so the guest %rip at
* event injection is identical to what it was when
* the exception originally happened.
*/
VCPU_CTR2(svm_sc->vm, vcpu, "Reset inst_length from %d "
"to zero before injecting exception %d",
vmexit->inst_length, idtvec);
vmexit->inst_length = 0;
/* fallthru */
default:
errcode_valid = 0;
break;
}
KASSERT(vmexit->inst_length == 0, ("invalid inst_length (%d) "
"when reflecting exception %d into guest",
vmexit->inst_length, idtvec));
if (reflect) {
/* Reflect the exception back into the guest */
exception.vector = idtvec;
exception.error_code_valid = errcode_valid;
exception.error_code = errcode_valid ? info1 : 0;
VCPU_CTR2(svm_sc->vm, vcpu, "Reflecting exception "
"%d/%#x into the guest", exception.vector,
exception.error_code);
error = vm_inject_exception(svm_sc->vm, vcpu,
&exception);
KASSERT(error == 0, ("%s: vm_inject_exception error %d",
__func__, error));
}
handled = 1;
break;
case VMCB_EXIT_MSR: /* MSR access. */
eax = state->rax;

View File

@ -332,7 +332,6 @@ vmcs_init(struct vmcs *vmcs)
int error, codesel, datasel, tsssel;
u_long cr0, cr4, efer;
uint64_t pat, fsbase, idtrbase;
uint32_t exc_bitmap;
codesel = vmm_get_host_codesel();
datasel = vmm_get_host_datasel();
@ -417,11 +416,6 @@ vmcs_init(struct vmcs *vmcs)
if ((error = vmwrite(VMCS_HOST_RIP, (u_long)vmx_exit_guest)) != 0)
goto done;
/* exception bitmap */
exc_bitmap = 1 << IDT_MC;
if ((error = vmwrite(VMCS_EXCEPTION_BITMAP, exc_bitmap)) != 0)
goto done;
/* link pointer */
if ((error = vmwrite(VMCS_LINK_POINTER, ~0)) != 0)
goto done;

View File

@ -321,7 +321,7 @@ vmcs_write(uint32_t encoding, uint64_t val)
#define EXIT_REASON_MTF 37
#define EXIT_REASON_MONITOR 39
#define EXIT_REASON_PAUSE 40
#define EXIT_REASON_MCE 41
#define EXIT_REASON_MCE_DURING_ENTRY 41
#define EXIT_REASON_TPR 43
#define EXIT_REASON_APIC_ACCESS 44
#define EXIT_REASON_VIRTUALIZED_EOI 45

View File

@ -283,8 +283,8 @@ exit_reason_to_str(int reason)
return "monitor";
case EXIT_REASON_PAUSE:
return "pause";
case EXIT_REASON_MCE:
return "mce";
case EXIT_REASON_MCE_DURING_ENTRY:
return "mce-during-entry";
case EXIT_REASON_TPR:
return "tpr";
case EXIT_REASON_APIC_ACCESS:
@ -821,6 +821,7 @@ vmx_vminit(struct vm *vm, pmap_t pmap)
int i, error;
struct vmx *vmx;
struct vmcs *vmcs;
uint32_t exc_bitmap;
vmx = malloc(sizeof(struct vmx), M_VMX, M_WAITOK | M_ZERO);
if ((uintptr_t)vmx & PAGE_MASK) {
@ -911,6 +912,14 @@ vmx_vminit(struct vm *vm, pmap_t pmap)
error += vmwrite(VMCS_ENTRY_CTLS, entry_ctls);
error += vmwrite(VMCS_MSR_BITMAP, vtophys(vmx->msr_bitmap));
error += vmwrite(VMCS_VPID, vpid[i]);
/* exception bitmap */
if (vcpu_trace_exceptions(vm, i))
exc_bitmap = 0xffffffff;
else
exc_bitmap = 1 << IDT_MC;
error += vmwrite(VMCS_EXCEPTION_BITMAP, exc_bitmap);
if (virtual_interrupt_delivery) {
error += vmwrite(VMCS_APIC_ACCESS, APIC_ACCESS_ADDRESS);
error += vmwrite(VMCS_VIRTUAL_APIC,
@ -2056,8 +2065,9 @@ vmx_exit_process(struct vmx *vmx, int vcpu, struct vm_exit *vmexit)
struct vlapic *vlapic;
struct vm_inout_str *vis;
struct vm_task_switch *ts;
struct vm_exception vmexc;
uint32_t eax, ecx, edx, idtvec_info, idtvec_err, intr_info, inst_info;
uint32_t intr_type, reason;
uint32_t intr_type, intr_vec, reason;
uint64_t exitintinfo, qual, gpa;
bool retu;
@ -2073,6 +2083,18 @@ vmx_exit_process(struct vmx *vmx, int vcpu, struct vm_exit *vmexit)
vmm_stat_incr(vmx->vm, vcpu, VMEXIT_COUNT, 1);
/*
* VM-entry failures during or after loading guest state.
*
* These VM-exits are uncommon but must be handled specially
* as most VM-exit fields are not populated as usual.
*/
if (__predict_false(reason == EXIT_REASON_MCE_DURING_ENTRY)) {
VCPU_CTR0(vmx->vm, vcpu, "Handling MCE during VM-entry");
__asm __volatile("int $18");
return (1);
}
/*
* VM exits that can be triggered during event delivery need to
* be handled specially by re-injecting the event if the IDT
@ -2305,6 +2327,9 @@ vmx_exit_process(struct vmx *vmx, int vcpu, struct vm_exit *vmexit)
KASSERT((intr_info & VMCS_INTR_VALID) != 0,
("VM exit interruption info invalid: %#x", intr_info));
intr_vec = intr_info & 0xff;
intr_type = intr_info & VMCS_INTR_T_MASK;
/*
* If Virtual NMIs control is 1 and the VM-exit is due to a
* fault encountered during the execution of IRET then we must
@ -2315,16 +2340,55 @@ vmx_exit_process(struct vmx *vmx, int vcpu, struct vm_exit *vmexit)
* See "Information for VM Exits Due to Vectored Events".
*/
if ((idtvec_info & VMCS_IDT_VEC_VALID) == 0 &&
(intr_info & 0xff) != IDT_DF &&
(intr_vec != IDT_DF) &&
(intr_info & EXIT_QUAL_NMIUDTI) != 0)
vmx_restore_nmi_blocking(vmx, vcpu);
/*
* The NMI has already been handled in vmx_exit_handle_nmi().
*/
if ((intr_info & VMCS_INTR_T_MASK) == VMCS_INTR_T_NMI)
if (intr_type == VMCS_INTR_T_NMI)
return (1);
break;
/*
* Call the machine check handler by hand. Also don't reflect
* the machine check back into the guest.
*/
if (intr_vec == IDT_MC) {
VCPU_CTR0(vmx->vm, vcpu, "Vectoring to MCE handler");
__asm __volatile("int $18");
return (1);
}
if (intr_vec == IDT_PF) {
error = vmxctx_setreg(vmxctx, VM_REG_GUEST_CR2, qual);
KASSERT(error == 0, ("%s: vmxctx_setreg(cr2) error %d",
__func__, error));
}
/*
* Software exceptions exhibit trap-like behavior. This in
* turn requires populating the VM-entry instruction length
* so that the %rip in the trap frame is past the INT3/INTO
* instruction.
*/
if (intr_type == VMCS_INTR_T_SWEXCEPTION)
vmcs_write(VMCS_ENTRY_INST_LENGTH, vmexit->inst_length);
/* Reflect all other exceptions back into the guest */
bzero(&vmexc, sizeof(struct vm_exception));
vmexc.vector = intr_vec;
if (intr_info & VMCS_INTR_DEL_ERRCODE) {
vmexc.error_code_valid = 1;
vmexc.error_code = vmcs_read(VMCS_EXIT_INTR_ERRCODE);
}
VCPU_CTR2(vmx->vm, vcpu, "Reflecting exception %d/%#x into "
"the guest", vmexc.vector, vmexc.error_code);
error = vm_inject_exception(vmx->vm, vcpu, &vmexc);
KASSERT(error == 0, ("%s: vm_inject_exception error %d",
__func__, error));
return (1);
case EXIT_REASON_EPT_FAULT:
/*
* If 'gpa' lies within the address space allocated to

View File

@ -207,6 +207,11 @@ static int vmm_ipinum;
SYSCTL_INT(_hw_vmm, OID_AUTO, ipinum, CTLFLAG_RD, &vmm_ipinum, 0,
"IPI vector used for vcpu notifications");
static int trace_guest_exceptions;
SYSCTL_INT(_hw_vmm, OID_AUTO, trace_guest_exceptions, CTLFLAG_RDTUN,
&trace_guest_exceptions, 0,
"Trap into hypervisor on all guest exceptions and reflect them back");
static void
vcpu_cleanup(struct vm *vm, int i, bool destroy)
{
@ -250,6 +255,13 @@ vcpu_init(struct vm *vm, int vcpu_id, bool create)
vmm_stat_init(vcpu->stats);
}
int
vcpu_trace_exceptions(struct vm *vm, int vcpuid)
{
return (trace_guest_exceptions);
}
struct vm_exit *
vm_exitinfo(struct vm *vm, int cpuid)
{