Virtualbox源码分析6_VMM虚拟化实现源码分析3 HMVMXR0.cpp

Virtualbox源码分析6:VMM虚拟化实现源码分析3 HMVMXR0.cpp

前面两章介绍了VT-X的原理和主要函数,这章里继续介绍其他VT-X的其他函数,通过这一章的讲解,可以完整的了解写一个基于VT-X的虚拟化软件的大部分关键内容

6.1 Host和Guest的线程切换

进入GuestOS之前的PreRun代码,会顺序调用hmR0VmxPreRunGuest和hmR0VmxPreRunGuestCommitted做一些切换上下文的事情

static VBOXSTRICTRC hmR0VmxPreRunGuest(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient, bool fStepping)
{
   //检测状态,如果状态不对,直接返回
  hmR0VmxCheckForceFlags(pVCpu, pVmxTransient, fStepping);
  hmR0VmxMergeVmcsNested(pVCpu);
    
  //Setup the virtualized-APIC accesses.
  //在后面的APIC虚拟化里详细讲解
        
  //如果vmexit的handle里有pending的中断没有发送到GuestOS里,发生到GuestOS
  hmR0VmxInjectPendingEvent(pVCpu, pVmxTransient, fIntrState, fStepping);
  //导入GuestOS的上下文
  hmR0VmxExportGuestStateOptimal(pVCpu, pVmxTransient);
}
static VBOXSTRICTRC hmR0VmxExportGuestStateOptimal(PVMCPU pVCpu)
{
    //大部分VMExithandle里,其实都只需要修正GuestRip,所以这边先检测如果只需要changeGuestRip,就只调用hmR0VmxExportGuestRip,如果其他状态需要改变,则调用hmR0VmxExportGuestState
   if ((fCtxChanged & (HM_CHANGED_ALL_GUEST & ~HM_CHANGED_VMX_HOST_GUEST_SHARED_STATE)) == HM_CHANGED_GUEST_RIP)
   {
       rcStrict = hmR0VmxExportGuestRip(pVCpu);
   }
   else
   {
       rcStrict = hmR0VmxExportGuestState(pVCpu);
   }
}

//VMExit的时候,GuestOS的状态都保存在VMCS里,进入虚拟机的时候,CPU会自动从VMCS里加载GuestOS的状态
//所以这里只需要根据上一次VMExit的handle里设置的fCtxChanged里的标记位,把改变的寄存器值保存设置到VMCS里即可
static VBOXSTRICTRC hmR0VmxExportGuestState(PVMCPU pVCpu)
{
    //选择pfnStartVM ,即进入GuestOS的入口函数地址
    int rc = hmR0VmxSelectVMRunHandler(pVCpu);
    //根据pVCpu->hm.s.fCtxChanged里的flags,修改VMCS里的值
    rc = hmR0VmxExportGuestEntryCtls(pVCpu);
     rc = hmR0VmxExportGuestExitCtls(pVCpu);
    rc = hmR0VmxExportGuestCR0(pVCpu);
    hmR0VmxExportGuestCR3AndCR4(pVCpu); 
    rc = hmR0VmxExportGuestSegmentRegs(pVCpu);
    rc = hmR0VmxExportGuestMsrs(pVCpu);
    rc = hmR0VmxExportGuestApicTpr(pVCpu);
    rc = hmR0VmxExportGuestXcptIntercepts(pVCpu);
    rc  = hmR0VmxExportGuestRip(pVCpu);
    rc |= hmR0VmxExportGuestRsp(pVCpu);
    rc |= hmR0VmxExportGuestRflags(pVCpu);
}
static void hmR0VmxPreRunGuestCommitted(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
  //根据pVCpu里的信息,执行相关操作
  if (   !pVCpu->hm.s.vmx.fUpdatedHostMsrs && pVCpu->hm.s.vmx.cMsrs > 0)
        hmR0VmxUpdateAutoLoadStoreHostMsrs(pVCpu);
  //保存host状态
  if (pVCpu->hm.s.fCtxChanged & HM_CHANGED_HOST_CONTEXT)
  {
      int rc = hmR0VmxExportHostState(pVCpu);
  }
  //保存FPU,debug lazy MSRs信息
  if (pVCpu->hm.s.fCtxChanged & HM_CHANGED_VMX_HOST_GUEST_SHARED_STATE)
      hmR0VmxExportSharedState(pVCpu);
  //保存HostMsr
  if (   !pVCpu->hm.s.vmx.fUpdatedHostMsrs && pVCpu->hm.s.vmx.cMsrs > 0)
        hmR0VmxUpdateAutoLoadStoreHostMsrs(pVCpu);
  
  //更新定时器相关的信息
  if (   pVmxTransient->fUpdateTscOffsettingAndPreemptTimer || idCurrentCpu != pVCpu->hm.s.idLastCpu)
  {
       hmR0VmxUpdateTscOffsettingAndPreemptTimer(pVCpu);
       pVmxTransient->fUpdateTscOffsettingAndPreemptTimer = false;
  }
  //刷新TLB
  hmR0VmxFlushTaggedTlb(pHostCpu, pVCpu, pVmcsInfo); 
  //告诉Timeout Manager马上要开始执行GuestOS
  TMNotifyStartOfExecution(pVM, pVCpu);
    
  if ((pVmcsInfo->u32ProcCtls2 & VMX_PROC_CTLS2_RDTSCP) && !fIsRdtscIntercepted)
  {
    	hmR0VmxImportGuestState(pVCpu, pVmcsInfo, CPUMCTX_EXTRN_TSC_AUX);
        int rc = hmR0VmxAddAutoLoadStoreMsr(pVCpu, pVmxTransient, MSR_K8_TSC_AUX, CPUMGetGuestTscAux(pVCpu),
                                            true /* fSetReadWrite */, true /* fUpdateHostMsr */);
  }
}

当CPU开始运行GuestOS代码的时候,会吧Host的状态信息写坏,所以在运行Guest代码之前,需要保存Host的状态到VMCS里,方便退出的时候恢复,类似于系统调用的上下文保存

VMCS里提供部分区域可以保存Host的运行态数据,具体可以参考Intel手册的 24.5 HOST-STATE AREA

//每个字函数里调用VMXWriteVmcs32保存host的寄存器信息
//部分信息也会同时保存到pVCpu->hm.s.vmx.RestoreHost里,具体定义见下面的结构体
typedef struct VMXRESTOREHOST
{
    RTSEL       uHostSelDS;     /* 0x00 */
    RTSEL       uHostSelES;     /* 0x02 */
    RTSEL       uHostSelFS;     /* 0x04 */
    RTSEL       uHostSelGS;     /* 0x06 */
    RTSEL       uHostSelTR;     /* 0x08 */
    uint8_t     abPadding0[4];
    X86XDTR64   HostGdtr;       /**< 0x0e - should be aligned by it's 64-bit member. */
    uint8_t     abPadding1[6];
    X86XDTR64   HostGdtrRw;     /**< 0x1e - should be aligned by it's 64-bit member. */
    uint8_t     abPadding2[6];
    X86XDTR64   HostIdtr;       /**< 0x2e - should be aligned by it's 64-bit member. */
    uint64_t    uHostFSBase;    /* 0x38 */
    uint64_t    uHostGSBase;    /* 0x40 */
} VMXRESTOREHOST;

static int hmR0VmxExportHostState(PVMCPU pVCpu)
{
    //保存Cr0/Cr3/Cr4
    hmR0VmxExportHostControlRegs();
    //保存GDTR,IDTR,TR,GS,FS段
    hmR0VmxExportHostSegmentRegs(pVCpu);
    //保存host msr的相关信息,SYSENTER_ESP/SYSENTER_EIP/SYSENTER_CS/HOST_EFER_FULL
    hmR0VmxExportHostMsrs(pVCpu);
}

退出GuestOS之后的PostRun代码

static void hmR0VmxPostRunGuest(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient, int rcVMRun)
{
    //保存Guest的所有状态
    hmR0VmxImportGuestState(pVCpu, HMVMX_CPUMCTX_EXTRN_ALL);
}
//从VMCS里保存GuestOS的状态, 可以参考Intel手册的第24.4 GUEST-STATE AREA章
static int hmR0VmxImportGuestState(PVMCPU pVCpu, uint64_t fWhat)
{
  	fWhat &= pCtx->fExtrn;
  	//根据API传入的fWhat值和pCtx里的fExtn值,恢复对应的寄存器
  	//进入GuestOS的时候,API参数传入的fWhat值是
    //先关闭中断
    fEFlags = ASMIntDisableFlags();
    //恢复 Guest ip
    if (fWhat & CPUMCTX_EXTRN_RIP)
        hmR0VmxImportGuestRip(pVCpu);
  	//恢复Guest rflags
    if (fWhat & CPUMCTX_EXTRN_RFLAGS)
    	hmR0VmxImportGuestRFlags(pVCpu);
  	//读取guest 的interrupt状态,如果有中断标志位,则先处理中断?
    if (fWhat & CPUMCTX_EXTRN_HM_VMX_INT_STATE)
    	hmR0VmxImportGuestIntrState(pVCpu);
    //恢复Guest RSP
    if (fWhat & CPUMCTX_EXTRN_RSP)
    	VMXReadVmcsGstN(VMX_VMCS_GUEST_RSP, &u64Val);
    //如果是实模式代码模拟,需要恢复几个段寄存器
    //cs 同时恢复rip
    if (fWhat & CPUMCTX_EXTRN_CS) 
    	hmR0VmxImportGuestSegReg(pVCpu, X86_SREG_CS);
    	hmR0VmxImportGuestRip(pVCpu);
    //ss
    if (fWhat & CPUMCTX_EXTRN_SS)
   		hmR0VmxImportGuestSegReg(pVCpu, X86_SREG_SS);
    //ds
    if (fWhat & CPUMCTX_EXTRN_DS)
    	hmR0VmxImportGuestSegReg(pVCpu, X86_SREG_DS);
    //es
    if (fWhat & CPUMCTX_EXTRN_ES)
    	hmR0VmxImportGuestSegReg(pVCpu, X86_SREG_ES);
    //fs
    if (fWhat & CPUMCTX_EXTRN_FS)
    	hmR0VmxImportGuestSegReg(pVCpu, X86_SREG_FS);
    //gs
    if (fWhat & CPUMCTX_EXTRN_GS)
   		hmR0VmxImportGuestSegReg(pVCpu, X86_SREG_GS);
  	//ldtr
    if (fWhat & CPUMCTX_EXTRN_LDTR)
  		hmR0VmxImportGuestLdtr(pVCpu);
  	//gdtr
    if (fWhat & CPUMCTX_EXTRN_GDTR)
        VMXReadVmcsNw(VMX_VMCS_GUEST_GDTR_BASE,    &pCtx->gdtr.pGdt);  
        VMXReadVmcs32(VMX_VMCS32_GUEST_GDTR_LIMIT, &u32Val);  
    	pCtx->gdtr.pGdt  = u64Val;
         pCtx->gdtr.cbGdt = u32Val;
    //idtr
    if (fWhat & CPUMCTX_EXTRN_IDTR)
        VMXReadVmcsNw(VMX_VMCS_GUEST_IDTR_BASE,    &pCtx->idtr.pIdt); 
        VMXReadVmcs32(VMX_VMCS32_GUEST_IDTR_LIMIT, &u32Val);      
        pCtx->idtr.pIdt  = u64Val;
        pCtx->idtr.cbIdt = u32Val;
  	//tr
    if (fWhat & CPUMCTX_EXTRN_TR)
  		hmR0VmxImportGuestTr(pVCpu);
    //syscall相关的msr寄存器
    if (fWhat & CPUMCTX_EXTRN_SYSENTER_MSRS)
        VMXReadVmcsNw(VMX_VMCS_GUEST_SYSENTER_EIP,  &pCtx->SysEnter.eip); 
        VMXReadVmcsNw(VMX_VMCS_GUEST_SYSENTER_ESP,  &pCtx->SysEnter.esp); 
        VMXReadVmcs32(VMX_VMCS32_GUEST_SYSENTER_CS, &u32Val); 
    //下面的几个寄存器直接从真机寄存器中读取
    if (fWhat & CPUMCTX_EXTRN_KERNEL_GS_BASE)
        pCtx->msrKERNELGSBASE = ASMRdMsr(MSR_K8_KERNEL_GS_BASE);
   	if (fWhat & CPUMCTX_EXTRN_SYSCALL_MSRS)
        pCtx->msrLSTAR  = ASMRdMsr(MSR_K8_LSTAR);
        pCtx->msrSTAR   = ASMRdMsr(MSR_K6_STAR);
        pCtx->msrSFMASK = ASMRdMsr(MSR_K8_SF_MASK);
        
    if (fWhat & CPUMCTX_EXTRN_DR7)
    {
        //保存DR7,调试寄存器
        rc = VMXReadVmcs32(VMX_VMCS_GUEST_DR7, &u32Val);
    }
    
    //保存CR0和CR4
    if (fWhat & CPUMCTX_EXTRN_CR0)
    {
        rc  = VMXReadVmcs32(VMX_VMCS_GUEST_CR0,            &u32Val);
        rc |= VMXReadVmcs32(VMX_VMCS_CTRL_CR0_READ_SHADOW, &u32Shadow);
        CPUMSetGuestCR0(pVCpu, u32Val);
    }
    if (fWhat & CPUMCTX_EXTRN_CR4)
    {
        rc  = VMXReadVmcs32(VMX_VMCS_GUEST_CR4,            &u32Val);
        rc |= VMXReadVmcs32(VMX_VMCS_CTRL_CR4_READ_SHADOW, &u32Shadow);
        CPUMSetGuestCR4(pVCpu, u32Val);
    }

     if (fWhat & CPUMCTX_EXTRN_CR3)
     {
         //保存CR3
         rc = VMXReadVmcsGstN(VMX_VMCS_GUEST_CR3, &u64Val);
         CPUMSetGuestCR3(pVCpu, u64Val);
          //当secondary processor-based 里enableEPT设置上且Guest是32位PAE模式的时候,需要保存内部使用的4个PDPTE值
         if (CPUMIsGuestInPAEModeEx(pCtx))
         {
             rc  = VMXReadVmcs64(VMX_VMCS64_GUEST_PDPTE0_FULL, &pVCpu->hm.s.aPdpes[0].u);
             rc |= VMXReadVmcs64(VMX_VMCS64_GUEST_PDPTE1_FULL, &pVCpu->hm.s.aPdpes[1].u);
             rc |= VMXReadVmcs64(VMX_VMCS64_GUEST_PDPTE2_FULL, &pVCpu->hm.s.aPdpes[2].u);
             rc |= VMXReadVmcs64(VMX_VMCS64_GUEST_PDPTE3_FULL, &pVCpu->hm.s.aPdpes[3].u);
         }
     }
   
  	//保存完毕之后开启中断
    ASMSetFlags(fEFlags); 
}

调整GuestOS RIP, 把RIP指向下一条指令

static int hmR0VmxAdvanceGuestRip(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
    hmR0VmxImportGuestState(pVCpu, CPUMCTX_EXTRN_RIP | CPUMCTX_EXTRN_RFLAGS);
    hmR0VmxAdvanceGuestRipBy(pVCpu, pVmxTransient->cbInstr);
}
DECLINLINE(void) hmR0VmxAdvanceGuestRipBy(PVMCPU pVCpu, uint32_t cbInstr)
{
    /* Advance the RIP. */
    pVCpu->cpum.GstCtx.rip += cbInstr;
    //设置标记RIP被host改变
    ASMAtomicUoOrU64(&pVCpu->hm.s.fCtxChanged, HM_CHANGED_GUEST_RIP);
}

6.2 具体设置VMExit事件

在启动虚拟机之前,设置好VMCS里相关项,就可以在虚拟机运行的时候收到VMExit事件了,其中又有以下几种类型的VMExit事件设置

  • Pin-Based VM-Execution Controls 基于处理器Pin接口的控制(INTR和NMI)
static int hmR0VmxSetupPinCtls(PVMCPU pVCpu)
{
     //这个两个是一定要设置的异常,因为Host不可能让CPU完全被Guest占满,需要一段时间后能够发生vmexit退回到Host
     fVal |= VMX_PIN_CTLS_EXT_INT_EXIT                       
          |  VMX_PIN_CTLS_NMI_EXIT;         
    //当启用VMX的定时功能后,设置这个异常,当定时的时间片完成的时候,发生vmexit
    if (pVM->hm.s.vmx.fUsePreemptTimer)
    {
        fVal |= VMX_PIN_CTLS_PREEMPT_TIMER;
    }
    int rc = VMXWriteVmcs32(VMX_VMCS32_CTRL_PIN_EXEC, fVal);
}
  • Processor-Based VM-Execution Controls, 设置基于处理器成面上的控制,主要是当CPU执行到某条特殊指令的时候发生VMExit,比如CPUID等

    其中又有两个字段

    processor-based VM-execution controls

    secondary processor-based VM-execution

static int hmR0VmxSetupProcCtls(PVMCPU pVCpu)
{
    fVal |= VMX_PROC_CTLS_HLT_EXIT                                    /* HLT causes a VM-exit. */
         |  VMX_PROC_CTLS_USE_TSC_OFFSETTING                          /* Use TSC-offsetting. */
         |  VMX_PROC_CTLS_MOV_DR_EXIT                                 /* MOV DRx causes a VM-exit. */
         |  VMX_PROC_CTLS_UNCOND_IO_EXIT                              /* All IO instructions cause a VM-exit. */
         |  VMX_PROC_CTLS_RDPMC_EXIT                                  /* RDPMC causes a VM-exit. */
         |  VMX_PROC_CTLS_MONITOR_EXIT                                /* MONITOR causes a VM-exit. */
         |  VMX_PROC_CTLS_MWAIT_EXIT;                                 /* MWAIT causes a VM-exit. */
   	//开启嵌套,需要注册页表切换和tlb相关的vmexit
    if (!pVM->hm.s.fNestedPaging)
    {
        fVal |= VMX_PROC_CTLS_INVLPG_EXIT
             |  VMX_PROC_CTLS_CR3_LOAD_EXIT
             |  VMX_PROC_CTLS_CR3_STORE_EXIT;
    }
    
    //msr寄存器的读写
    if (pVM->hm.s.vmx.Msrs.ProcCtls.n.allowed1 & VMX_PROC_CTLS_USE_MSR_BITMAPS)
    {
        VMXWriteVmcs64(VMX_VMCS64_CTRL_MSR_BITMAP_FULL, pVCpu->hm.s.vmx.HCPhysMsrBitmap);
        
        hmR0VmxSetMsrPermission(pVCpu, MSR_IA32_SYSENTER_CS,  VMXMSREXIT_PASSTHRU_READ, VMXMSREXIT_PASSTHRU_WRITE);
        hmR0VmxSetMsrPermission(pVCpu, MSR_IA32_SYSENTER_ESP, VMXMSREXIT_PASSTHRU_READ, VMXMSREXIT_PASSTHRU_WRITE);
        hmR0VmxSetMsrPermission(pVCpu, MSR_IA32_SYSENTER_EIP, VMXMSREXIT_PASSTHRU_READ, VMXMSREXIT_PASSTHRU_WRITE);
        hmR0VmxSetMsrPermission(pVCpu, MSR_K8_GS_BASE,        VMXMSREXIT_PASSTHRU_READ, VMXMSREXIT_PASSTHRU_WRITE);
        hmR0VmxSetMsrPermission(pVCpu, MSR_K8_FS_BASE,        VMXMSREXIT_PASSTHRU_READ, VMXMSREXIT_PASSTHRU_WRITE);
        
       //64位host
        hmR0VmxSetMsrPermission(pVCpu, MSR_K8_LSTAR, VMXMSREXIT_PASSTHRU_READ, VMXMSREXIT_PASSTHRU_WRITE);
            hmR0VmxSetMsrPermission(pVCpu, MSR_K6_STAR,           VMXMSREXIT_PASSTHRU_READ, VMXMSREXIT_PASSTHRU_WRITE);
            hmR0VmxSetMsrPermission(pVCpu, MSR_K8_SF_MASK,        VMXMSREXIT_PASSTHRU_READ, VMXMSREXIT_PASSTHRU_WRITE);
            hmR0VmxSetMsrPermission(pVCpu, MSR_K8_KERNEL_GS_BASE, VMXMSREXIT_PASSTHRU_READ, VMXMSREXIT_PASSTHRU_WRITE);
        
        //是否使用第二个ProcCtls,enable了,secondary processor-based VM-execution controls才会生效
        if (pVM->hm.s.vmx.Msrs.ProcCtls.n.allowed1 & VMX_PROC_CTLS_USE_SECONDARY_CTLS)
        	fVal |= VMX_PROC_CTLS_USE_SECONDARY_CTLS;
        
        //设置好了之后,写入VMCS即可
        VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, fVal);       
    }
    
}
static int hmR0VmxSetupProcCtls2(PVMCPU pVCpu)
{
     /* WBINVD causes a VM-exit. */
    if (pVM->hm.s.vmx.Msrs.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_WBINVD_EXIT)
        fVal |= VMX_PROC_CTLS2_WBINVD_EXIT;
    //开启EPT
    if (pVM->hm.s.fNestedPaging)
        fVal |= VMX_PROC_CTLS2_EPT;
 	//如果没有设置这项,当Guest执行指令的时候会发生UD
    if (   (pVM->hm.s.vmx.Msrs.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_INVPCID)
        && pVM->cpum.ro.GuestFeatures.fInvpcid)
        fVal |= VMX_PROC_CTLS2_INVPCID;
    //开启VIRTUAL PROCESSOR IDENTIFIERS, 开启VPID,每个TLB可以关联一个VPID,这样虚拟机切换到时候可以避免刷新TLB导致性能损耗
    if (pVM->hm.s.vmx.fVpid)
        fVal |= VMX_PROC_CTLS2_VPID;
	//Guest可以运行在没有分页的保护模式或者实模式下
    if (pVM->hm.s.vmx.fUnrestrictedGuest)
        fVal |= VMX_PROC_CTLS2_UNRESTRICTED_GUEST;    
    //rdtscp 如果GuestOS是Windows10 以上系统,需要注册这个vmexit,否则会蓝屏
    if (pVM->hm.s.vmx.Msrs.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_RDTSCP)
        fVal |= VMX_PROC_CTLS2_RDTSCP;
	//防止GuestOS进入PAUSE-loop状态
    if (   pVM->hm.s.vmx.Msrs.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_PAUSE_LOOP_EXIT
        && pVM->hm.s.vmx.cPleGapTicks
        && pVM->hm.s.vmx.cPleWindowTicks)
    {
        fVal |= VMX_PROC_CTLS2_PAUSE_LOOP_EXIT;
        //设置两个时间上线
        int rc  = VMXWriteVmcs32(VMX_VMCS32_CTRL_PLE_GAP, pVM->hm.s.vmx.cPleGapTicks);
        rc |= VMXWriteVmcs32(VMX_VMCS32_CTRL_PLE_WINDOW, pVM->hm.s.vmx.cPleWindowTicks);
    }
    //设置完成之后,写入VMCS对应项即可
    VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC2, fVal);
}
  • 其他分类
static int hmR0VmxSetupMiscCtls(PVMCPU pVCpu)
{
    //设置保存guest msr load/store信息的物理地址指针
    rc  = VMXWriteVmcs64(VMX_VMCS64_CTRL_ENTRY_MSR_LOAD_FULL, pVCpu->hm.s.vmx.HCPhysGuestMsr);
    rc |= VMXWriteVmcs64(VMX_VMCS64_CTRL_EXIT_MSR_STORE_FULL, pVCpu->hm.s.vmx.HCPhysGuestMsr);
    //设置保存host信息的物理地址指针
    VMXWriteVmcs64(VMX_VMCS64_CTRL_EXIT_MSR_LOAD_FULL,  pVCpu->hm.s.vmx.HCPhysHostMsr);
    // VMCS link pointer 保留指针,现在必须设置成-1
    VMXWriteVmcs64(VMX_VMCS64_GUEST_VMCS_LINK_PTR_FULL, UINT64_C(0xffffffffffffffff));
}
  • Exception bitmap, 注册哪些异常需要触发VMExit
static int hmR0VmxInitXcptBitmap(PVMCPU pVCpu)
{
    //ac和db的异常必须注册
    uXcptBitmap = RT_BIT_32(X86_XCPT_AC);
    uXcptBitmap |= RT_BIT_32(X86_XCPT_DB);
    //如果没有开启VT嵌套,需要注册PF去更新EPT表
     if (!pVCpu->CTX_SUFF(pVM)->hm.s.fNestedPaging)
        uXcptBitmap |= RT_BIT(X86_XCPT_PF);
    VMXWriteVmcs32(VMX_VMCS32_CTRL_EXCEPTION_BITMAP, uXcptBitmap);
}

6.3 VM-exit handlers

上一章设置好了VMExit事件之后,当发生这些事件的时候,就会调用到对应的VMExit handle里

VMExit Handler的代码都很类似,主要分成3个部分:

  1. 保存GuestOS的状态(从VMCS到CPUMCTX),获取VMExit的参数

    hmR0VmxImportGuestState((a_pVCpu), (a_fWhat))
    hmR0VmxReadExitInstrLenVmcs(pVmxTransient);
    

    CPU触发VMExit的时候,会吧相关的信息保存在VMCS的部分字段里,可以通过vmread指令读取

    相关内容可以参考Intel手册27.2.1Basic VM-Exit Information 类似于操作系统中的异常上下文

    主要有以下信息

    • 基本信息: Exit reason / Exit qualification(详细信息,每个事件都不同) / 发生异常的Guest IP地址

    • 直接中断异常: interruption information/ error code ,GuestOS 异常导致的VMExit

    • 间接中断异常:GuestOS异常在分发的时候出错导致的VMExit

    • 指令类信息字段: VM-exit instruction length / VM-exit instruction information / I/O RCX, I/O RSI, I/O RDI, I/O RIP (SMM开启时的SMM VM-exit)

    • 指令失败: VM-instruction error

  2. 根据指令不同,处理这条指令

  3. 根据2的结果,不同的处理方法:

    如果2返回成功,则调整相关寄存器,让GuestOS继续执行下一条指令。

    如果2返回失败,根据不同的指令不同处理,有的是需要给GuestOS注入异常,有的是需要退出VMX返回R3处理。

    如果状态检查发现错误,通知VMX退出。

下面是具体的VMExit函数实现,部分调用到其他Manage函数的代码,在后面的章节里介绍

//VMX_EXIT_EXT_INT
HMVMX_EXIT_DECL hmR0VmxExitExtInt(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
    if (VMMR0ThreadCtxHookIsEnabled(pVCpu))
        return VINF_SUCCESS;
    return VINF_EM_RAW_INTERRUPT;
}
//VMX_EXIT_XCPT_OR_NMI host收到NMI和中断
HMVMX_EXIT_DECL hmR0VmxExitXcptOrNmi(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
  switch (uExitIntType)
  {
      case VMX_EXIT_INT_INFO_TYPE_NMI:
      	hmR0VmxExitHostNmi(pVCpu, pVmcsInfo);
      case VMX_EXIT_INT_INFO_TYPE_HW_XCPT:
      	hmR0VmxExitXcpt(pVCpu, pVmxTransient);
  }
}
static int hmR0VmxExitHostNmi(PVMCPUCC pVCpu, PCVMXVMCSINFO pVmcsInfo)
{
  idCpu = pVmcsInfo->idHostCpuExec;
  //对目标CPU调用int 2,发送给host
  VMXDispatchHostNmi();
}
/*其他异常的分发
其实主要是做了3个做法:
	DB/BP 这类调试异常看是否host要处理
    某些异常Guest无法执行,比如实模式的PF异常,都需要先尝试模拟执行
    其他异常,注入给Guest
*/
static VBOXSTRICTRC hmR0VmxExitXcpt(PVMCPUCC pVCpu, PVMXTRANSIENT pVmxTransient)
{
    case X86_XCPT_PF: return hmR0VmxExitXcptPF(pVCpu, pVmxTransient);
    case X86_XCPT_GP: return hmR0VmxExitXcptGP(pVCpu, pVmxTransient);
    case X86_XCPT_MF: return hmR0VmxExitXcptMF(pVCpu, pVmxTransient);
    case X86_XCPT_DB: return hmR0VmxExitXcptDB(pVCpu, pVmxTransient);
    case X86_XCPT_BP: return hmR0VmxExitXcptBP(pVCpu, pVmxTransient);
    case X86_XCPT_AC: return hmR0VmxExitXcptAC(pVCpu, pVmxTransient);
    default:
    	//非上面6个中断,都是直接注入到GuestOS里
    	return hmR0VmxExitXcptOthers(pVCpu, pVmxTransient);
}
//#AC (Alignment-check exception). 给GuestOS处理
static VBOXSTRICTRC hmR0VmxExitXcptAC(PVMCPUCC pVCpu, PVMXTRANSIENT pVmxTransient)
{
    //读取相关异常信息
    int rc = hmR0VmxReadExitIntErrorCodeVmcs(pVmxTransient);
    rc    |= hmR0VmxReadExitInstrLenVmcs(pVmxTransient);
    hmR0VmxSetPendingEvent(pVCpu, VMX_ENTRY_INT_INFO_FROM_EXIT_INT_INFO(pVmxTransient->uExitIntInfo),
                           pVmxTransient->cbExitInstr, pVmxTransient->uExitIntErrorCode, 0 /* GCPtrFaultAddress */);
}
//#BP (Breakpoint exception).
static int hmR0VmxExitXcptBP(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
    HMVMX_CPUMCTX_IMPORT_STATE(pVCpu, HMVMX_CPUMCTX_EXTRN_ALL);
    //先个Host处理
    rc = DBGFRZTrap03Handler(pVCpu->CTX_SUFF(pVM), pVCpu, CPUMCTX2CORE(pCtx));
    //如果需要交给Guest处理,读取相关信息,注入一个异常
    if (rc == VINF_EM_RAW_GUEST_TRAP)
    {
        rc  = hmR0VmxReadExitIntInfoVmcs(pVmxTransient);
        rc |= hmR0VmxReadExitInstrLenVmcs(pVmxTransient);
        rc |= hmR0VmxReadExitIntErrorCodeVmcs(pVmxTransient);
        hmR0VmxSetPendingEvent(pVCpu, VMX_ENTRY_INT_INFO_FROM_EXIT_INT_INFO(pVmxTransient->uExitIntInfo), pVmxTransient->cbInstr,
                               pVmxTransient->uExitIntErrorCode, 0 /* GCPtrFaultAddress */);
    }
}
//#DB (Debug exception).
static int hmR0VmxExitXcptDB(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
    //同样这种调试类型中断先看host是否处理
    DBGFRZTrap01Handler(pVCpu->CTX_SUFF(pVM), pVCpu, CPUMCTX2CORE(pCtx), uDR6, pVCpu->hm.s.fSingleInstruction);
    if (rc == VINF_EM_RAW_GUEST_TRAP)
    {
        //需要给Guest处理,写入GuestOS的DR7寄存器
        pCtx->dr[7] &= ~X86_DR7_GD;
        pCtx->dr[7] &= ~X86_DR7_RAZ_MASK;
        pCtx->dr[7] |= X86_DR7_RA1_MASK;
        rc = VMXWriteVmcs32(VMX_VMCS_GUEST_DR7, (uint32_t)pCtx->dr[7]);
        //发送DB异常到Guest
        rc  = hmR0VmxReadExitIntInfoVmcs(pVmxTransient);
        rc |= hmR0VmxReadExitInstrLenVmcs(pVmxTransient);
        rc |= hmR0VmxReadExitIntErrorCodeVmcs(pVmxTransient);
        hmR0VmxSetPendingEvent(pVCpu, VMX_ENTRY_INT_INFO_FROM_EXIT_INT_INFO(pVmxTransient->uExitIntInfo), pVmxTransient->cbInstr,
                               pVmxTransient->uExitIntErrorCode, 0 /* GCPtrFaultAddress */);
	}
}
//#GP (General-protection exception).
static int hmR0VmxExitXcptGP(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
    //实模式
    if (pVCpu->hm.s.vmx.RealMode.fRealOnV86Active)
    {
        //模拟执行
        VBOXSTRICTRC rcStrict = IEMExecOne(pVCpu);
         if (rcStrict == VINF_SUCCESS)
         {
             //模拟执行完成之后,需要检查CPU是否已经不在实模式下
             if (!CPUMIsGuestInRealModeEx(pCtx))
             {
                 if (HMVmxCanExecuteGuest(pVCpu, pCtx))
                 {
                     //设置状态不在实模式下
                     pVCpu->hm.s.vmx.RealMode.fRealOnV86Active = false;
				}
			}
		}
       
	}
    //非实模式
    else
    {
         //直接注入到GuestOS里
         hmR0VmxSetPendingEvent(pVCpu, VMX_ENTRY_INT_INFO_FROM_EXIT_INT_INFO(pVmxTransient->uExitIntInfo),
                                   pVmxTransient->cbInstr, pVmxTransient->uExitIntErrorCode, 0 /* GCPtrFaultAddress */);
	}
}
static int hmR0VmxExitXcptPF(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
    //开启EPT
    if (!pVM->hm.s.fNestedPaging)
    {
        //根据VMExit Info里的字段,决定是发生PF还是DF(double fault)到GuestOS
         if (RT_LIKELY(!pVmxTransient->fVectoringDoublePF))
         {
             hmR0VmxSetPendingEvent(pVCpu, VMX_ENTRY_INT_INFO_FROM_EXIT_INT_INFO(pVmxTransient->uExitIntInfo), 0 /* cbInstr */,
                                   pVmxTransient->uExitIntErrorCode, pVmxTransient->uExitQual);
		}
        else
        {
            rc = hmR0VmxSetPendingXcptDF(pVCpu);
	    }
	}
    else
    {
        //调用Page Manager and Monitor处理
        rc = PGMTrap0eHandler(pVCpu, pVmxTransient->uExitIntErrorCode, CPUMCTX2CORE(pCtx), (RTGCPTR)pVmxTransient->uExitQual);
         //如果异常需要注入到GuestOS里
        if (rc == VINF_EM_RAW_GUEST_TRAP)
        {
            //同上根据VMExit的信息,注入PF或者DF异常
        }
        
	}
}
//VMX_EXIT_INT_WINDOW
HMVMX_EXIT_NSRC_DECL hmR0VmxExitIntWindow(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
  //VMCS设置里关闭这个异常
  hmR0VmxClearIntWindowExitVmcs(pVmcsInfo);
}
//VMX_EXIT_GETSEC 这个是不可屏蔽异常
HMVMX_EXIT_DECL hmR0VmxExitGetsec(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
  //这个异常需要让Host处理
  //保存cr4的Guest信息
  hmR0VmxImportGuestState(pVCpu, pVmcsInfo, CPUMCTX_EXTRN_CR4);
  //如果没有设置了CR4.SMXE但发生了VMX_EXIT_GETSEC,VMX异常退出
  if (pVCpu->cpum.GstCtx.cr4 & X86_CR4_SMXE)
    	 //返回Host 处理
        return VINF_EM_RAW_EMULATE_INSTR;
  HMVMX_UNEXPECTED_EXIT_RET(pVCpu, pVmxTransient->uExitReason);
}
HMVMX_EXIT_DECL hmR0VmxExitRdtsc(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
    //调用模拟器模拟执行这条指令,将在EM这一章里介绍这个函数
    IEMExecDecodedRdtsc(pVCpu, pVmxTransient->cbInstr);
    if (RT_LIKELY(rcStrict == VINF_SUCCESS))
    {
        //模拟执行成功,如果设置了VMX_PROC_CTLS_USE_TSC_OFFSETTING,需要在GuestOS重新进入的时候
        //重置Tsc offset
        if (pVCpu->hm.s.vmx.u32ProcCtls & VMX_PROC_CTLS_USE_TSC_OFFSETTING)
            pVmxTransient->fUpdateTscOffsettingAndPreemptTimer = true;
        //标记需要修改Guest RIP和Guest rflags
        ASMAtomicUoOrU64(&pVCpu->hm.s.fCtxChanged, HM_CHANGED_GUEST_RIP | HM_CHANGED_GUEST_RFLAGS);
    }
    else if (rcStrict == VINF_IEM_RAISED_XCPT)
    {
        //如果模拟执行失败,需要向GuestOS注入一个异常
        ASMAtomicUoOrU64(&pVCpu->hm.s.fCtxChanged, HM_CHANGED_RAISED_XCPT_MASK);
    }
}
//VMX_EXIT_RDTSCP 和VMX_EXIT_RDTSC的处理过程一样
HMVMX_EXIT_DECL hmR0VmxExitRdtscp(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
    IEMExecDecodedRdtscp(pVCpu, pVmxTransient->cbInstr);
    ....和hmR0VmxExitRdtsc一样
}
//rdpmc :读取性能监视计数器
HMVMX_EXIT_DECL hmR0VmxExitRdpmc(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
    //调用模拟器模拟执行这条指令,相关信息从Host获取,将在EM这一章里介绍这个函数
    EMInterpretRdpmc(pVM, pVCpu, CPUMCTX2CORE(pCtx));
    if (RT_LIKELY(rc == VINF_SUCCESS))
    {
        //模拟执行成功,修改Rip到下一条指令
        rc = hmR0VmxAdvanceGuestRip(pVCpu, pVmxTransient);
    }
    else
    {
       //失败,退出VMX
        rc = VERR_EM_INTERPRETER;
    }
}
HMVMX_EXIT_DECL hmR0VmxExitVmcall(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
    //调用GIM(Guest Interface Manager)执行这条指令,将在GIM这一章里介绍这个函数
    rcStrict = GIMHypercall(pVCpu, &pVCpu->cpum.GstCtx);
    if (rcStrict == VINF_SUCCESS)
    {
        //模拟执行成功,修改Rip到下一条指令
        rc = hmR0VmxAdvanceGuestRip(pVCpu, pVmxTransient);
    }
    else
        Assert(   rcStrict == VINF_GIM_R3_HYPERCALL
               || rcStrict == VINF_GIM_HYPERCALL_CONTINUING
               || RT_FAILURE(rcStrict));
    //如果执行失败,则向GuestOS注入#UD异常
     if (RT_FAILURE(rcStrict))
    {
        hmR0VmxSetPendingXcptUD(pVCpu);
    }
}
//VMX_EXIT_INVLPG
HMVMX_EXIT_DECL hmR0VmxExitInvlpg(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
    //同样也是调用模拟器模拟执行这条指令
    IEMExecDecodedInvlpg(pVCpu, pVmxTransient->cbInstr, pVmxTransient->uExitQual);
}
//MONITOR (VMX_EXIT_MONITOR) 设置监视器地址
HMVMX_EXIT_DECL hmR0VmxExitMonitor(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
    //同样也是调用模拟器模拟执行这条指令
    EMInterpretMonitor(pVM, pVCpu, CPUMCTX2CORE(pCtx));
}
//MWAIT - 监视器等待
HMVMX_EXIT_DECL hmR0VmxExitMwait(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
    //模拟执行
    EMInterpretMWait(pVM, pVCpu, CPUMCTX2CORE(pCtx));
    if (RT_LIKELY(   rc == VINF_SUCCESS
                  || rc == VINF_EM_HALT))
    {
        //GuestOS RIP到下一条指令
        int rc3 = hmR0VmxAdvanceGuestRip(pVCpu, pVmxTransient);
        //如果返回VINF_EM_HALT,根据GuestOS的状态决定是否要退出VMX
        if (   rc == VINF_EM_HALT
            && EMMonitorWaitShouldContinue(pVCpu, pCtx))
            rc = VINF_SUCCESS;
    }
    else
    {
        //返回错误码
        rc = VERR_EM_INTERPRETER;
    }
}
//HLT (VMX_EXIT_HLT)
HMVMX_EXIT_DECL hmR0VmxExitHlt(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
    //GuestRIP调整
    int rc = hmR0VmxAdvanceGuestRip(pVCpu, pVmxTransient);
    //根据GuestOS的状态决定是否要退出VMX
    if (EMShouldContinueAfterHalt(pVCpu, &pVCpu->cpum.GstCtx))    /* Requires eflags. */
        rc = VINF_SUCCESS;
    else
        rc = VINF_EM_HALT;
}
//VMX preemption timer 时间片到
HMVMX_EXIT_DECL hmR0VmxExitPreemptTimer(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
    pVmxTransient->fUpdateTscOffsettingAndPreemptTimer = true;
    //调用TM的TMTimerPollBool,如果有timer相关的event没有被处理,则返回错误到Ring-3处理
    bool fTimersPending = TMTimerPollBool(pVM, pVCpu);
    return fTimersPending ? VINF_EM_RAW_TIMER_PENDING : VINF_SUCCESS;
}
//XSETBV (VMX_EXIT_XSETBV). Set Extended Control Register 这条指令是不可屏蔽异常
HMVMX_EXIT_DECL hmR0VmxExitXsetbv(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
    IEMExecDecodedXsetbv(pVCpu, pVmxTransient->cbInstr);
}
//LGDT, SGDT, LIDT, SIDT, LLDT, LTR, SLDT, STR 指令
//这里可以拦截下来修改SIDT指令的返回值欺骗GuestOS过PatchGuard
HMVMX_EXIT_DECL hmR0VmxExitXdtrAccess(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
    //设置了VMX_PROC_CTLS2_DESC_TABLE_EXIT,模拟执行
    if (pVCpu->hm.s.vmx.u32ProcCtls2 & VMX_PROC_CTLS2_DESC_TABLE_EXIT)
        return VERR_EM_INTERPRETER;
    //什么都没做
}
//RDMSR (VMX_EXIT_RDMSR)
HMVMX_EXIT_DECL hmR0VmxExitRdmsr(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
  //保存相关的寄存器
  hmR0VmxImportGuestState(pVCpu, pVmcsInfo, fImport);
  //模拟执行这条指令,可以在host里修改GuestOS的返回值
  IEMExecDecodedRdmsr(pVCpu, pVmxTransient->cbExitInstr);
}
//VMX_EXIT_WRMSR 模拟执行,这边可以修改返回值欺骗GuestOS
HMVMX_EXIT_DECL hmR0VmxExitWrmsr(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
 	//模拟执行这条指令
  IEMExecVmxVmexitInstr(pVCpu, pVmxTransient->uExitReason, pVmxTransient->cbExitInstr);
}
//CRX寄存器的访问,也是模拟执行,这边可以修改返回值欺骗GuestOS
HMVMX_EXIT_DECL hmR0VmxExitMovCRx(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
  IEMExecDecodedMovCRxWrite(pVCpu, cbInstr, iCrReg, iGReg);
}
//处理IO/OUT 指令,虚拟设备实现的基础
HMVMX_EXIT_DECL hmR0VmxExitIoInstr(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
    //INS/OUTS指令, 调用Interpreted Execution Manager模拟执行这条指令
    if (fIOWrite)
        rcStrict = IEMExecStringIoWrite(pVCpu, cbValue, enmAddrMode, fRep, cbInstr,
                                                    pVmxTransient->ExitInstrInfo.StrIo.iSegReg, true /*fIoChecked*/);
    else
    {
        rcStrict = IEMExecStringIoRead(pVCpu, cbValue, enmAddrMode, fRep, cbInstr, true /*fIoChecked*/);
    }
    //IN/OUT
    if (fIOWrite)  //OUT 
    {
        //调用Input / Output Monitor 执行
        rcStrict = IOMIOPortWrite(pVM, pVCpu, uIOPort, pCtx->eax & uAndVal, cbValue);
		if (rcStrict == VINF_IOM_R3_IOPORT_WRITE)
         {
             //需要返回R3处理的,设置相关数据,退出VMX交给R3处理,虚拟设备模拟的IN/OUThandle就是这里分发出去的
             rcStrict = EMRZSetPendingIoPortWrite(pVCpu, uIOPort, cbInstr, cbValue, pCtx->eax & uAndVal);
		}
	}
    else  //IN ,处理同OUT
    {
        rcStrict = IOMIOPortRead(pVM, pVCpu, uIOPort, &u32Result, cbValue);
        if (IOM_SUCCESS(rcStrict))
        {
            //模拟执行成功,保存返回值
            pCtx->eax = (pCtx->eax & ~uAndVal) | (u32Result & uAndVal);
        }
        if (rcStrict == VINF_IOM_R3_IOPORT_READ)
        {
            rcStrict = EMRZSetPendingIoPortRead(pVCpu, uIOPort, cbInstr, cbValue);
        }
    }
    return rcStrict;
}
HMVMX_EXIT_DECL hmR0VmxExitTaskSwitch(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
HMVMX_EXIT_DECL hmR0VmxExitMtf(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
  //注销这个异常
    pVmcsInfo->u32ProcCtls &= ~VMX_PROC_CTLS_MONITOR_TRAP_FLAG;
    VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, pVmcsInfo->u32ProcCtls);
  	return VINF_EM_DBG_STEPPED;
}
HMVMX_EXIT_DECL hmR0VmxExitApicAccess(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
HMVMX_EXIT_DECL hmR0VmxExitMovDRx(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
//EPT表发生异常,比如Guest访问某个Guest物理地址,但是没有EPT table里没有对应的Host物理地址,就会发生这个异常
HMVMX_EXIT_DECL hmR0VmxExitEptMisconfig(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
  //调用PGM: Page Manager and Monitor, 去修复EPT table
  PGMR0Trap0eHandlerNPMisconfig(pVM, pVCpu, PGMMODE_EPT, CPUMCTX2CORE(pCtx), GCPhys, UINT32_MAX);
}
//EPT表里有3个bit可以分别设置R/W/X的权限,如果权限发生错误的时候,会触发这个中断
//比如某个EPTTable设置了某个物理地址不可写,当这个物理地址被写入的时候,会发生这个VMExit
HMVMX_EXIT_DECL hmR0VmxExitEptViolation(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
{
    if (pVmxTransient->uExitQual & VMX_EXIT_QUAL_EPT_INSTR_FETCH)
        uErrorCode |= X86_TRAP_PF_ID;
    if (pVmxTransient->uExitQual & VMX_EXIT_QUAL_EPT_DATA_WRITE)
        uErrorCode |= X86_TRAP_PF_RW;
    if (pVmxTransient->uExitQual & VMX_EXIT_QUAL_EPT_ENTRY_PRESENT)
        uErrorCode |= X86_TRAP_PF_P;
    //注入一个PF的异常
    TRPMAssertXcptPF(pVCpu, GCPhys, uErrorCode);
}

为了支持虚拟化嵌套,拦截VMX命令, 基本都是调用IEM - Interpreted Execution Manager里的函数,在后面介绍IEM的时候介绍

6.4. 向虚拟机中注入异常:

Host可以向通过VMCS里的设置向虚拟机里注入异常,比如在VMExit里,希望向GuestOS里注入一个GP的异常,可以调用hmR0VmxInjectEventVmcs,这样当GuestOS运行的时候,就会收到一个GP的中断

//注入GP异常
DECLINLINE(VBOXSTRICTRC) hmR0VmxInjectXcptGP(PVMCPU pVCpu, bool fErrorCodeValid, uint32_t u32ErrorCode, bool fStepping,
                                             uint32_t *pfIntrState)
{
    uint32_t const u32IntInfo = X86_XCPT_GP | VMX_EXIT_INT_INFO_VALID
                              | (VMX_EXIT_INT_INFO_TYPE_HW_XCPT << VMX_EXIT_INT_INFO_TYPE_SHIFT)
                              | (fErrorCodeValid ? VMX_EXIT_INT_INFO_ERROR_CODE_VALID : 0);
    return hmR0VmxInjectEventVmcs(pVCpu, u32IntInfo, 0 /* cbInstr */, u32ErrorCode, 0 /* GCPtrFaultAddress */, fStepping,
                                  pfIntrState);
}

在向GuestOS发异常的时候,需要同时发送:

INTERRUPTION_INFO: 什么异常(GP,PF)等

EXCEPTION_ERRCODE: 有的异常需要传入具体的errorcode,比如PF的异常需要知道是读异常还是写异常等等。

写入CR2: 异常发生的地址,比如PF里表示访问CR2里对应地址发生了PF异常

static VBOXSTRICTRC hmR0VmxInjectEventVmcs(PVMCPU pVCpu, uint64_t u64IntInfo, uint32_t cbInstr, uint32_t u32ErrCode,
                                           RTGCUINTREG GCPtrFaultAddress, bool fStepping, uint32_t *pfIntrState)
{
  	if (CPUMIsGuestInRealModeEx(pCtx))  
    {
      //Guest运行在实模式
      //如果在GuestOS的IDT表里找不到对应的处理函数
      if (uVector * cbIdtEntry + (cbIdtEntry - 1) > pCtx->idtr.cbIdt)
      {
        //return a triple-fault
        if (uVector == X86_XCPT_DF)
          	return VINF_EM_RESET;
        //如果实模式发生GP,则注入double fault给Guest
        if (uVector == X86_XCPT_GP)
        {
          XcptDfInfo = RT_BF_MAKE(VMX_BF_ENTRY_INT_INFO_VECTOR,         X86_XCPT_DF)
          ...
          return hmR0VmxInjectEventVmcs(pVCpu, pVmxTransient, &EventXcptDf, fStepping, pfIntrState);
        }
        //到这个分支都是无效IDT,inject GP到GuestOS
        uXcptGpInfo = RT_BF_MAKE(VMX_BF_ENTRY_INT_INFO_VECTOR,         X86_XCPT_GP)
         ....
           return hmR0VmxInjectEventVmcs(pVCpu, pVmxTransient, &EventXcptDf, fStepping, pfIntrState);
			}
      	//找到了Intercept handle函数
      	//构造异常stack
        rcStrict = hmR0VmxRealModeGuestStackPush(pVCpu, pCtx->eflags.u32);
        rcStrict = hmR0VmxRealModeGuestStackPush(pVCpu, pCtx->cs.Sel);
        rcStrict = hmR0VmxRealModeGuestStackPush(pVCpu, uGuestIp);
        
      	//设置GuestOS相关的寄存器,具体内容见Intel手册20.1.4节
        pCtx->eflags.u32 &= ~(X86_EFL_IF | X86_EFL_TF | X86_EFL_RF | X86_EFL_AC);
        pCtx->rip         = IdtEntry.offSel;
        pCtx->cs.Sel      = IdtEntry.uSel;
        pCtx->cs.ValidSel = IdtEntry.uSel;
        pCtx->cs.u64Base  = IdtEntry.uSel << cbIdtEntry;
        if (   uIntType == VMX_ENTRY_INT_INFO_TYPE_HW_XCPT
            && uVector  == X86_XCPT_PF)
          pCtx->cr2 = GCPtrFault;
				 //设置fCtxChanged 在进入GuestOS之前设置这些寄存器
         ASMAtomicUoOrU64(&pVCpu->hm.s.fCtxChanged, HM_CHANGED_GUEST_CS  | HM_CHANGED_GUEST_CR2
                                                         | HM_CHANGED_GUEST_RIP | HM_CHANGED_GUEST_RFLAGS
                                                         | HM_CHANGED_GUEST_RSP);
      
		}
    //GuestOS运行在保护模式
    //写入VMCS的相关项
    int rc = VMXWriteVmcs32(VMX_VMCS32_CTRL_ENTRY_INTERRUPTION_INFO, u32IntInfo);
    if (VMX_ENTRY_INT_INFO_IS_ERROR_CODE_VALID(u32IntInfo))
        rc |= VMXWriteVmcs32(VMX_VMCS32_CTRL_ENTRY_EXCEPTION_ERRCODE, u32ErrCode);
    rc |= VMXWriteVmcs32(VMX_VMCS32_CTRL_ENTRY_INSTR_LENGTH, cbInstr);
    
    //更新CR2的值,
    if (   VMX_ENTRY_INT_INFO_TYPE(u32IntInfo) == VMX_EXIT_INT_INFO_TYPE_HW_XCPT
        && uVector == X86_XCPT_PF)
        pCtx->cr2 = GCPtrFaultAddress;
}

在VMExit的处理函数中,也可以调用hmR0VmxSetPendingEvent 插入异常,当hmR0VmxPreRunGuest准备进入GuestOS的时候,会调用hmR0VmxInjectPendingEvent把这些异常注入到GuestOS里。

DECLINLINE(void) hmR0VmxSetPendingEvent(PVMCPU pVCpu, uint32_t u32IntInfo, uint32_t cbInstr, uint32_t u32ErrCode,
                                        RTGCUINTPTR GCPtrFaultAddress)
{
    //在pVCpu里保存异常相关的信息
    pVCpu->hm.s.Event.fPending          = true;
    pVCpu->hm.s.Event.u64IntInfo        = u32IntInfo;
    pVCpu->hm.s.Event.u32ErrCode        = u32ErrCode;
    pVCpu->hm.s.Event.cbInstr           = cbInstr;
    pVCpu->hm.s.Event.GCPtrFaultAddress = GCPtrFaultAddress;
}

static VBOXSTRICTRC hmR0VmxInjectPendingEvent(PVMCPU pVCpu, uint32_t fIntrState, bool fStepping)
{
    //判断有pending的event
    if (pVCpu->hm.s.Event.fPending)
    {
        //调用hmR0VmxInjectEventVmcs注入异常
        hmR0VmxInjectEventVmcs(pVCpu, pVCpu->hm.s.Event.u64IntInfo, pVCpu->hm.s.Event.cbInstr,
                               pVCpu->hm.s.Event.u32ErrCode, pVCpu->hm.s.Event.GCPtrFaultAddress, fStepping,
                               &fIntrState);
        
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值