文章目录
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个部分:
-
保存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的结果,不同的处理方法:
如果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);
}
}