[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [Xen-devel] [V10 PATCH 23/23] PVH xen: introduce vmexit handler for PVH
On Wed, Jul 24, 2013 at 2:59 AM, Mukesh Rathor <mukesh.rathor@xxxxxxxxxx> wrote: > This patch contains vmx exit handler for PVH guest. Note it contains > a macro dbgp1 to print vmexit reasons and a lot of other data > to go with it. It can be enabled by setting pvhdbg to 1. This can be > very useful debugging for the first few months of testing, after which > it can be removed at the maintainer's discretion. > > Changes in V2: > - Move non VMX generic code to arch/x86/hvm/pvh.c > - Remove get_gpr_ptr() and use existing decode_register() instead. > - Defer call to pvh vmx exit handler until interrupts are enabled. So the > caller vmx_pvh_vmexit_handler() handles the NMI/EXT-INT/TRIPLE_FAULT now. > - Fix the CPUID (wrongly) clearing bit 24. No need to do this now, set > the correct feature bits in CR4 during vmcs creation. > - Fix few hard tabs. > > Changes in V3: > - Lot of cleanup and rework in PVH vm exit handler. > - add parameter to emulate_forced_invalid_op(). > > Changes in V5: > - Move pvh.c and emulate_forced_invalid_op related changes to another patch. > - Formatting. > - Remove vmx_pvh_read_descriptor(). > - Use SS DPL instead of CS.RPL for CPL. > - Remove pvh_user_cpuid() and call pv_cpuid for user mode also. > > Changes in V6: > - Replace domain_crash_synchronous() with domain_crash(). > > Changes in V7: > - Don't read all selectors on every vmexit. Do that only for the > IO instruction vmexit. > - Add couple checks and set guest_cr[4] in access_cr4(). > - Add period after all comments in case that's an issue. > - Move making pv_cpuid and emulate_privileged_op public here. > > Changes in V8: > - Mainly, don't read selectors on vmexit. The macros now come to VMCS > to read selectors on demand. Overall I have the same comment here as I had for the VMCS patch: the code looks 98% identical. Substantial differences seem to be: - emulation of privileged ops - cpuid - cr4 handling It seems like it would be much better to share the codepath and just put "is_pvh_domain()" in the places where it needs to be different. More comments / questions inline. > > Signed-off-by: Mukesh Rathor <mukesh.rathor@xxxxxxxxxx> > --- > xen/arch/x86/hvm/vmx/pvh.c | 451 > +++++++++++++++++++++++++++++++++++++++++++- > 1 files changed, 450 insertions(+), 1 deletions(-) > > diff --git a/xen/arch/x86/hvm/vmx/pvh.c b/xen/arch/x86/hvm/vmx/pvh.c > index 8e61d23..ba11967 100644 > --- a/xen/arch/x86/hvm/vmx/pvh.c > +++ b/xen/arch/x86/hvm/vmx/pvh.c > @@ -20,9 +20,458 @@ > #include <asm/hvm/nestedhvm.h> > #include <asm/xstate.h> > > -/* Implemented in the next patch */ > +#ifndef NDEBUG > +static int pvhdbg = 0; > +#define dbgp1(...) do { (pvhdbg == 1) ? printk(__VA_ARGS__) : 0; } while ( 0 > ) > +#else > +#define dbgp1(...) ((void)0) > +#endif > + > +/* Returns : 0 == msr read successfully. */ > +static int vmxit_msr_read(struct cpu_user_regs *regs) > +{ > + u64 msr_content = 0; > + > + switch ( regs->ecx ) > + { > + case MSR_IA32_MISC_ENABLE: > + rdmsrl(MSR_IA32_MISC_ENABLE, msr_content); > + msr_content |= MSR_IA32_MISC_ENABLE_BTS_UNAVAIL | > + MSR_IA32_MISC_ENABLE_PEBS_UNAVAIL; > + break; > + > + default: > + /* PVH fixme: see hvm_msr_read_intercept(). */ > + rdmsrl(regs->ecx, msr_content); > + break; So at the moment you basically pass through all MSR reads (adding BTS_UNAVAIL and PEBS_UNAVAIL to MISC_ENABLE), but send MSR writes through the hvm code? That sounds like it's asking for trouble... > + } > + regs->eax = (uint32_t)msr_content; > + regs->edx = (uint32_t)(msr_content >> 32); > + vmx_update_guest_eip(); > + > + dbgp1("msr read c:%lx a:%lx d:%lx RIP:%lx RSP:%lx\n", regs->ecx, > regs->eax, > + regs->edx, regs->rip, regs->rsp); > + > + return 0; > +} > + > +/* Returns : 0 == msr written successfully. */ > +static int vmxit_msr_write(struct cpu_user_regs *regs) > +{ > + uint64_t msr_content = (uint32_t)regs->eax | ((uint64_t)regs->edx << 32); > + > + dbgp1("PVH: msr write:0x%lx. eax:0x%lx edx:0x%lx\n", regs->ecx, > + regs->eax, regs->edx); > + > + if ( hvm_msr_write_intercept(regs->ecx, msr_content) == X86EMUL_OKAY ) > + { > + vmx_update_guest_eip(); > + return 0; > + } > + return 1; > +} > + > +static int vmxit_debug(struct cpu_user_regs *regs) > +{ > + struct vcpu *vp = current; > + unsigned long exit_qualification = __vmread(EXIT_QUALIFICATION); > + > + write_debugreg(6, exit_qualification | 0xffff0ff0); > + > + /* gdbsx or another debugger. Never pause dom0. */ > + if ( vp->domain->domain_id != 0 && vp->domain->debugger_attached ) > + domain_pause_for_debugger(); > + else > + hvm_inject_hw_exception(TRAP_debug, HVM_DELIVER_NO_ERROR_CODE); Hmm, strangely enough, the HVM handler for this doesn't seem to deliver this exception -- or if it does, I can't quite figure out where. What you have here seems like the correct thing to do, but I would be interested in knowing the reason for the HVM behavior. > + > + return 0; > +} > + > +/* Returns: rc == 0: handled the MTF vmexit. */ > +static int vmxit_mtf(struct cpu_user_regs *regs) > +{ > + struct vcpu *vp = current; > + int rc = -EINVAL, ss = vp->arch.hvm_vcpu.single_step; > + > + vp->arch.hvm_vmx.exec_control &= ~CPU_BASED_MONITOR_TRAP_FLAG; > + __vmwrite(CPU_BASED_VM_EXEC_CONTROL, vp->arch.hvm_vmx.exec_control); > + vp->arch.hvm_vcpu.single_step = 0; > + > + if ( vp->domain->debugger_attached && ss ) > + { > + domain_pause_for_debugger(); > + rc = 0; > + } > + return rc; > +} > + > +static int vmxit_int3(struct cpu_user_regs *regs) > +{ > + int ilen = vmx_get_instruction_length(); > + struct vcpu *vp = current; > + struct hvm_trap trap_info = { > + .vector = TRAP_int3, > + .type = X86_EVENTTYPE_SW_EXCEPTION, > + .error_code = HVM_DELIVER_NO_ERROR_CODE, > + .insn_len = ilen > + }; > + > + /* gdbsx or another debugger. Never pause dom0. */ > + if ( vp->domain->domain_id != 0 && vp->domain->debugger_attached ) > + { > + regs->eip += ilen; > + dbgp1("[%d]PVH: domain pause for debugger\n", smp_processor_id()); > + current->arch.gdbsx_vcpu_event = TRAP_int3; > + domain_pause_for_debugger(); > + return 0; > + } > + hvm_inject_trap(&trap_info); > + > + return 0; > +} > + > +/* Just like HVM, PVH should be using "cpuid" from the kernel mode. */ > +static int vmxit_invalid_op(struct cpu_user_regs *regs) > +{ > + if ( guest_kernel_mode(current, regs) || > !emulate_forced_invalid_op(regs) ) > + hvm_inject_hw_exception(TRAP_invalid_op, HVM_DELIVER_NO_ERROR_CODE); > + > + return 0; > +} > + > +/* Returns: rc == 0: handled the exception. */ > +static int vmxit_exception(struct cpu_user_regs *regs) > +{ > + int vector = (__vmread(VM_EXIT_INTR_INFO)) & INTR_INFO_VECTOR_MASK; > + int rc = -ENOSYS; The vmx code here has some handler for faults that happen during a guest IRET -- is that an issue for PVH? > + > + dbgp1(" EXCPT: vec:%d cs:%lx r.IP:%lx\n", vector, > + __vmread(GUEST_CS_SELECTOR), regs->eip); > + > + switch ( vector ) > + { > + case TRAP_debug: > + rc = vmxit_debug(regs); > + break; > + > + case TRAP_int3: > + rc = vmxit_int3(regs); > + break; > + > + case TRAP_invalid_op: > + rc = vmxit_invalid_op(regs); > + break; > + > + case TRAP_no_device: > + hvm_funcs.fpu_dirty_intercept(); > + rc = 0; > + break; > + > + default: > + printk(XENLOG_G_WARNING > + "PVH: Unhandled trap:%d. IP:%lx\n", vector, regs->eip); > + } > + return rc; > +} > + > +static int vmxit_vmcall(struct cpu_user_regs *regs) > +{ > + if ( hvm_do_hypercall(regs) != HVM_HCALL_preempted ) > + vmx_update_guest_eip(); > + return 0; > +} > + > +/* Returns: rc == 0: success. */ > +static int access_cr0(struct cpu_user_regs *regs, uint acc_typ, uint64_t > *regp) > +{ > + struct vcpu *vp = current; > + > + if ( acc_typ == VMX_CONTROL_REG_ACCESS_TYPE_MOV_TO_CR ) > + { > + unsigned long new_cr0 = *regp; > + unsigned long old_cr0 = __vmread(GUEST_CR0); > + > + dbgp1("PVH:writing to CR0. RIP:%lx val:0x%lx\n", regs->rip, *regp); > + if ( (u32)new_cr0 != new_cr0 ) > + { > + printk(XENLOG_G_WARNING > + "Guest setting upper 32 bits in CR0: %lx", new_cr0); > + return -EPERM; > + } > + > + new_cr0 &= ~HVM_CR0_GUEST_RESERVED_BITS; > + /* ET is reserved and should always be 1. */ > + new_cr0 |= X86_CR0_ET; > + > + /* A pvh is not expected to change to real mode. */ > + if ( (new_cr0 & (X86_CR0_PE | X86_CR0_PG)) != > + (X86_CR0_PG | X86_CR0_PE) ) > + { > + printk(XENLOG_G_WARNING > + "PVH attempting to turn off PE/PG. CR0:%lx\n", new_cr0); > + return -EPERM; > + } > + /* TS going from 1 to 0 */ > + if ( (old_cr0 & X86_CR0_TS) && ((new_cr0 & X86_CR0_TS) == 0) ) > + vmx_fpu_enter(vp); > + > + vp->arch.hvm_vcpu.hw_cr[0] = vp->arch.hvm_vcpu.guest_cr[0] = new_cr0; > + __vmwrite(GUEST_CR0, new_cr0); > + __vmwrite(CR0_READ_SHADOW, new_cr0); > + } > + else > + *regp = __vmread(GUEST_CR0); The HVM code here just uses hvm_vcpu.guest_cr[] -- is there any reason not to do the same here? And in any case, shouldn't it be CR0_READ_SHADOW? > + > + return 0; > +} > + > +/* Returns: rc == 0: success. */ > +static int access_cr4(struct cpu_user_regs *regs, uint acc_typ, uint64_t > *regp) > +{ > + if ( acc_typ == VMX_CONTROL_REG_ACCESS_TYPE_MOV_TO_CR ) > + { > + struct vcpu *vp = current; > + u64 old_val = __vmread(GUEST_CR4); > + u64 new = *regp; > + > + if ( new & HVM_CR4_GUEST_RESERVED_BITS(vp) ) > + { > + printk(XENLOG_G_WARNING > + "PVH guest attempts to set reserved bit in CR4: %lx", > new); > + hvm_inject_hw_exception(TRAP_gp_fault, 0); > + return 0; > + } > + > + if ( !(new & X86_CR4_PAE) && hvm_long_mode_enabled(vp) ) > + { > + printk(XENLOG_G_WARNING "Guest cleared CR4.PAE while " > + "EFER.LMA is set"); > + hvm_inject_hw_exception(TRAP_gp_fault, 0); > + return 0; > + } > + > + vp->arch.hvm_vcpu.guest_cr[4] = new; > + > + if ( (old_val ^ new) & (X86_CR4_PSE | X86_CR4_PGE | X86_CR4_PAE) ) > + vpid_sync_all(); Is it actually allowed for a PVH guest to change modes like this? I realize that at the moment you're only supporting HAP, but that may not always be true; would it make sense to call paging_update_paging_modes() here instead? > + > + __vmwrite(CR4_READ_SHADOW, new); > + > + new &= ~X86_CR4_PAE; /* PVH always runs with hap enabled. */ > + new |= X86_CR4_VMXE | X86_CR4_MCE; > + __vmwrite(GUEST_CR4, new); Should you be updating hvm_vcpu.hw_cr[4] to this value? > + } > + else > + *regp = __vmread(CR4_READ_SHADOW); Same as above re guest_cr[] > + > + return 0; > +} > + > +/* Returns: rc == 0: success, else -errno. */ > +static int vmxit_cr_access(struct cpu_user_regs *regs) > +{ > + unsigned long exit_qualification = __vmread(EXIT_QUALIFICATION); > + uint acc_typ = VMX_CONTROL_REG_ACCESS_TYPE(exit_qualification); > + int cr, rc = -EINVAL; > + > + switch ( acc_typ ) > + { > + case VMX_CONTROL_REG_ACCESS_TYPE_MOV_TO_CR: > + case VMX_CONTROL_REG_ACCESS_TYPE_MOV_FROM_CR: > + { > + uint gpr = VMX_CONTROL_REG_ACCESS_GPR(exit_qualification); > + uint64_t *regp = decode_register(gpr, regs, 0); > + cr = VMX_CONTROL_REG_ACCESS_NUM(exit_qualification); > + > + if ( regp == NULL ) > + break; > + > + switch ( cr ) > + { > + case 0: > + rc = access_cr0(regs, acc_typ, regp); > + break; > + > + case 3: > + printk(XENLOG_G_ERR "PVH: unexpected cr3 vmexit. rip:%lx\n", > + regs->rip); > + domain_crash(current->domain); > + break; > + > + case 4: > + rc = access_cr4(regs, acc_typ, regp); > + break; > + } > + if ( rc == 0 ) > + vmx_update_guest_eip(); > + break; > + } > + > + case VMX_CONTROL_REG_ACCESS_TYPE_CLTS: > + { > + struct vcpu *vp = current; > + unsigned long cr0 = vp->arch.hvm_vcpu.guest_cr[0] & ~X86_CR0_TS; > + vp->arch.hvm_vcpu.hw_cr[0] = vp->arch.hvm_vcpu.guest_cr[0] = cr0; > + > + vmx_fpu_enter(vp); > + __vmwrite(GUEST_CR0, cr0); > + __vmwrite(CR0_READ_SHADOW, cr0); > + vmx_update_guest_eip(); > + rc = 0; > + } > + } > + return rc; > +} > + > +/* > + * Note: A PVH guest sets IOPL natively by setting bits in the eflags, and > not > + * via hypercalls used by a PV. > + */ > +static int vmxit_io_instr(struct cpu_user_regs *regs) > +{ > + struct segment_register seg; > + int requested = (regs->rflags & X86_EFLAGS_IOPL) >> 12; > + int curr_lvl = (regs->rflags & X86_EFLAGS_VM) ? 3 : 0; > + > + if ( curr_lvl == 0 ) > + { > + hvm_get_segment_register(current, x86_seg_ss, &seg); > + curr_lvl = seg.attr.fields.dpl; > + } > + if ( requested >= curr_lvl && emulate_privileged_op(regs) ) > + return 0; > + > + hvm_inject_hw_exception(TRAP_gp_fault, regs->error_code); > + return 0; > +} > + > +static int pvh_ept_handle_violation(unsigned long qualification, > + paddr_t gpa, struct cpu_user_regs *regs) > +{ > + unsigned long gla, gfn = gpa >> PAGE_SHIFT; > + p2m_type_t p2mt; > + mfn_t mfn = get_gfn_query_unlocked(current->domain, gfn, &p2mt); > + > + printk(XENLOG_G_ERR "EPT violation %#lx (%c%c%c/%c%c%c), " > + "gpa %#"PRIpaddr", mfn %#lx, type %i. IP:0x%lx RSP:0x%lx\n", > + qualification, > + (qualification & EPT_READ_VIOLATION) ? 'r' : '-', > + (qualification & EPT_WRITE_VIOLATION) ? 'w' : '-', > + (qualification & EPT_EXEC_VIOLATION) ? 'x' : '-', > + (qualification & EPT_EFFECTIVE_READ) ? 'r' : '-', > + (qualification & EPT_EFFECTIVE_WRITE) ? 'w' : '-', > + (qualification & EPT_EFFECTIVE_EXEC) ? 'x' : '-', > + gpa, mfn_x(mfn), p2mt, regs->rip, regs->rsp); > + > + ept_walk_table(current->domain, gfn); > + > + if ( qualification & EPT_GLA_VALID ) > + { > + gla = __vmread(GUEST_LINEAR_ADDRESS); > + printk(XENLOG_G_ERR " --- GLA %#lx\n", gla); > + } > + hvm_inject_hw_exception(TRAP_gp_fault, 0); > + return 0; > +} > + > +/* > + * Main vm exit handler for PVH . Called from vmx_vmexit_handler(). > + * Note: vmx_asm_vmexit_handler updates rip/rsp/eflags in regs{} struct. > + */ > void vmx_pvh_vmexit_handler(struct cpu_user_regs *regs) > { > + unsigned long exit_qualification; > + unsigned int exit_reason = __vmread(VM_EXIT_REASON); > + int rc=0, ccpu = smp_processor_id(); > + struct vcpu *v = current; > + > + dbgp1("PVH:[%d]left VMCS exitreas:%d RIP:%lx RSP:%lx EFLAGS:%lx > CR0:%lx\n", > + ccpu, exit_reason, regs->rip, regs->rsp, regs->rflags, > + __vmread(GUEST_CR0)); > + > + switch ( (uint16_t)exit_reason ) > + { > + /* NMI and machine_check are handled by the caller, we handle rest here > */ > + case EXIT_REASON_EXCEPTION_NMI: /* 0 */ > + rc = vmxit_exception(regs); > + break; > + > + case EXIT_REASON_EXTERNAL_INTERRUPT: /* 1 */ > + break; /* handled in vmx_vmexit_handler() */ This seems to be a weird way to do things, but I see this is what they do in vmx_vmexit_handler() as well, so I guess it makes sense to follow suit. What about EXIT_REASON_TRIPLE_FAULT? [End of in-line comments] > + > + case EXIT_REASON_PENDING_VIRT_INTR: /* 7 */ > + /* Disable the interrupt window. */ > + v->arch.hvm_vmx.exec_control &= ~CPU_BASED_VIRTUAL_INTR_PENDING; > + __vmwrite(CPU_BASED_VM_EXEC_CONTROL, v->arch.hvm_vmx.exec_control); > + break; > + > + case EXIT_REASON_CPUID: /* 10 */ > + pv_cpuid(regs); > + vmx_update_guest_eip(); > + break; > + > + case EXIT_REASON_HLT: /* 12 */ > + vmx_update_guest_eip(); > + hvm_hlt(regs->eflags); > + break; > + > + case EXIT_REASON_VMCALL: /* 18 */ > + rc = vmxit_vmcall(regs); > + break; > + > + case EXIT_REASON_CR_ACCESS: /* 28 */ > + rc = vmxit_cr_access(regs); > + break; > + > + case EXIT_REASON_DR_ACCESS: /* 29 */ > + exit_qualification = __vmread(EXIT_QUALIFICATION); > + vmx_dr_access(exit_qualification, regs); > + break; > + > + case EXIT_REASON_IO_INSTRUCTION: /* 30 */ > + vmxit_io_instr(regs); > + break; > + > + case EXIT_REASON_MSR_READ: /* 31 */ > + rc = vmxit_msr_read(regs); > + break; > + > + case EXIT_REASON_MSR_WRITE: /* 32 */ > + rc = vmxit_msr_write(regs); > + break; > + > + case EXIT_REASON_MONITOR_TRAP_FLAG: /* 37 */ > + rc = vmxit_mtf(regs); > + break; > + > + case EXIT_REASON_MCE_DURING_VMENTRY: /* 41 */ > + break; /* handled in vmx_vmexit_handler() */ > + > + case EXIT_REASON_EPT_VIOLATION: /* 48 */ > + { > + paddr_t gpa = __vmread(GUEST_PHYSICAL_ADDRESS); > + exit_qualification = __vmread(EXIT_QUALIFICATION); > + rc = pvh_ept_handle_violation(exit_qualification, gpa, regs); > + break; > + } > + > + default: > + rc = 1; > + printk(XENLOG_G_ERR > + "PVH: Unexpected exit reason:%#x\n", exit_reason); > + } > + > + if ( rc ) > + { > + exit_qualification = __vmread(EXIT_QUALIFICATION); > + printk(XENLOG_G_WARNING > + "PVH: [%d] exit_reas:%d %#x qual:%ld 0x%lx cr0:0x%016lx\n", > + ccpu, exit_reason, exit_reason, exit_qualification, > + exit_qualification, __vmread(GUEST_CR0)); > + printk(XENLOG_G_WARNING "PVH: RIP:%lx RSP:%lx EFLAGS:%lx CR3:%lx\n", > + regs->rip, regs->rsp, regs->rflags, __vmread(GUEST_CR3)); > + domain_crash(v->domain); > + } > } > > /* > -- > 1.7.2.3 > > > _______________________________________________ > Xen-devel mailing list > Xen-devel@xxxxxxxxxxxxx > http://lists.xen.org/xen-devel _______________________________________________ Xen-devel mailing list Xen-devel@xxxxxxxxxxxxx http://lists.xen.org/xen-devel
|
Lists.xenproject.org is hosted with RackSpace, monitoring our |