Virtualbox源码分析4:VMM虚拟化框架实现源码分析

Intel和AMD都有自己VMM框架, Intel的叫做VMX, AMD的叫做SVM,两个实现原理类似,只是CPU指令,VMCS结构体不同,所以虚拟化软件需要同时支持VMX和SVM两套VMM。
VMX实现代码在VMM\VMMR0\HMVMXR0.cpp
SVM实现代码在VMM\VMMR0\HMSVMR0.cpp
本文已VMX为举例讲解:

4.1 VMX原理

Intel手册Vol 3C第23章到第33章详细讲解了VMX的原理,有兴趣可以下载下来看,下面先简单介绍一些重要的概念。

4.1.1 VMX的状态转化:

VMX有两种状态: root状态和non-root状态
在这里插入图片描述
其中几个重要的指令:
VMXON: 进入VMX root模式
VMXOFF:退出VMX root模式
VMExit:non-root模式进入到root模式 。这个可以由Guest主动调用,也可以是一些异常导致CPU主动触发。
VMEntry: root模式进入non-roo模式,即CPU开始运行Guest OS代码。
如果大家熟悉windows系统调用可以吧root模式理解成操作系统内核,non-root模式理解成操作系统应用层,vmexit理解成syscall或者异常处理,VMEntry理解成sysexit。

4.1.2 VMCS

在虚拟化中,为了实现vCPU,既要模拟CPU的运行,又要记录vCPU的状态(包括对vCPU运行的控制信息), vCPU的状态,就保护到一个叫做VMCS到结构体里,具体描述可以参考Intel手册24.3 ORGANIZATION OF VMCS DATA
VMM给每个vCPU都分配了一个VMCS结构体,每个VMCS都有三个状态(Launched,clear和active状态),每个实体CPU上只能有一个VMCS出于active/launched状态
下面是Virtual box里的定义:
hm_vmx.h

/** VMCS launch state clear. */
#define VMX_V_VMCS_LAUNCH_STATE_CLEAR                           RT_BIT(0)
/** VMCS launch state active. */
#define VMX_V_VMCS_LAUNCH_STATE_ACTIVE                          RT_BIT(1)
/** VMCS launch state current. */
#define VMX_V_VMCS_LAUNCH_STATE_CURRENT                         RT_BIT(2)
/** VMCS launch state launched. */
#define VMX_V_VMCS_LAUNCH_STATE_LAUNCHED                        RT_BIT(3)

下图是3个状态之间的转换关系
在这里插入图片描述
VMClEAR:传入一个物理地址,CPU会把当前VMCS里的数据拷贝到传入的物理地址中,并吧当前CPU的VMCS状态设置成clear状态
VMPTRLD(ACTIVE): 传入一个物理地址, 把当前CPU的VMCS指针指向这个物理地址
VMLAUNCH: 当VMM设置好VMCS之后,就可以执行VMLAUNCH让CPU切换到GuestOS状态,运行GuestOS里的代码。
在下一章的VMXR0SetupVM这个函数的解析里,可以看到使用代码是如何设置一个VMCS内容的。

VirtualBox里对VMCS对定义代码子啊HMInternal.h里

typedef struct VMXVMCSINFO
{
    /** @name Auxiliary information.
     * @{ */
    /** Ring-0 pointer to the hardware-assisted VMX execution function. */
    PFNHMVMXSTARTVM             pfnStartVM;
    /** Host-physical address of the EPTP. */
    RTHCPHYS                    HCPhysEPTP;                  这个地方保存了EPT Table的指针
    /** The VMCS launch state, see VMX_V_VMCS_LAUNCH_STATE_XXX. */
    uint32_t                    fVmcsState;                               保存上面介绍的vmcs状态
    /** The VMCS launch state of the shadow VMCS, see VMX_V_VMCS_LAUNCH_STATE_XXX. */
    uint32_t                    fShadowVmcsState;
    /** The host CPU for which its state has been exported to this VMCS. */
    RTCPUID                     idHostCpuState;              主机物理CPU的状态信息
    /** The host CPU on which we last executed this VMCS. */
    RTCPUID                     idHostCpuExec;
    /** Number of guest MSRs in the VM-entry MSR-load area. */
    uint32_t                    cEntryMsrLoad;
    /** Number of guest MSRs in the VM-exit MSR-store area. */
    uint32_t                    cExitMsrStore;
    /** Number of host MSRs in the VM-exit MSR-load area. */
    uint32_t                    cExitMsrLoad;
    /** @} */

    /** @name Cache of execution related VMCS fields.   这些区域是控制Guest OS行为
     *  @{ */
    /** Pin-based VM-execution controls. */
    uint32_t                    u32PinCtls;
    /** Processor-based VM-execution controls. */
    uint32_t                    u32ProcCtls;        设置VMExit事件
    /** Secondary processor-based VM-execution controls. */
    uint32_t                    u32ProcCtls2;      也是设置VMExit事件
    /** VM-entry controls. */
    uint32_t                    u32EntryCtls;       对VM Entry的行为进行控制
    /** VM-exit controls. */
    uint32_t                    u32ExitCtls;       这个地方可以设置VMExit事件
    /** Exception bitmap. */ 
    uint32_t                    u32XcptBitmap;     设置异常事件的bitmap
    /** Page-fault exception error-code mask. */
    uint32_t                    u32XcptPFMask;
    /** Page-fault exception error-code match. */
    uint32_t                    u32XcptPFMatch;
    /** Padding. */
    uint32_t                    u32Alignment0;
    /** TSC offset. */
    uint64_t                    u64TscOffset;
    /** VMCS link pointer. */
    uint64_t                    u64VmcsLinkPtr;
    /** CR0 guest/host mask. */
    uint64_t                    u64Cr0Mask;
    /** CR4 guest/host mask. */
    uint64_t                    u64Cr4Mask;
    /** @} */

    /** @name Host-virtual address of VMCS and related data structures.
     *  @{ */
    /** The VMCS. */
    R0PTRTYPE(void *)           pvVmcs;          当前VMCShost物理地址对应的虚拟地址,方便访问
    /** The shadow VMCS. */
    R0PTRTYPE(void *)           pvShadowVmcs;    用于支持嵌套虚拟化nested-guest shadow VMCS
    /** The virtual-APIC page. */
    R0PTRTYPE(uint8_t *)        pbVirtApic;
    /** The MSR bitmap. */
    R0PTRTYPE(void *)           pvMsrBitmap;    设置msr事件的bitmap
    /** The VM-entry MSR-load area. */
    R0PTRTYPE(void *)           pvGuestMsrLoad;
    /** The VM-exit MSR-store area. */
    R0PTRTYPE(void *)           pvGuestMsrStore;
    /** The VM-exit MSR-load area. */
    R0PTRTYPE(void *)           pvHostMsrLoad;
    /** @} */

    /** @name Real-mode emulation state.             实模式模拟的状态,多用于32位GuestOS在二进制翻译模式下系统启动时的模拟
     * @{ */
    /** Set if guest was executing in real mode (extra checks). */
    bool                        fWasInRealMode;
    /** Set if the guest switched to 64-bit mode on a 32-bit host. */
    bool                        fSwitchedTo64on32Obsolete;
    /** Padding. */
    bool                        afPadding0[6];
    struct
    {
        X86DESCATTR             AttrCS;
        X86DESCATTR             AttrDS;
        X86DESCATTR             AttrES;
        X86DESCATTR             AttrFS;
        X86DESCATTR             AttrGS;
        X86DESCATTR             AttrSS;
        X86EFLAGS               Eflags;
        bool                    fRealOnV86Active;
        bool                    afPadding1[3];
    } RealMode;
    /** @} */

    /** @name Host-physical address of VMCS and related data structures.
     *  @{ */
    /** The VMCS. */
    RTHCPHYS                    HCPhysVmcs;
    /** The shadow VMCS. */
    RTHCPHYS                    HCPhysShadowVmcs;
    /** The virtual APIC page. */
    RTHCPHYS                    HCPhysVirtApic;
    /** The MSR bitmap. */
    RTHCPHYS                    HCPhysMsrBitmap;
    /** The VM-entry MSR-load area. */
    RTHCPHYS                    HCPhysGuestMsrLoad;
    /** The VM-exit MSR-store area. */
    RTHCPHYS                    HCPhysGuestMsrStore;
    /** The VM-exit MSR-load area. */
    RTHCPHYS                    HCPhysHostMsrLoad;
    /** @} */

    /** @name R0-memory objects address for VMCS and related data structures.
     *  @{ */
    /** R0-memory object for VMCS and related data structures. */
    RTR0MEMOBJ                  hMemObj;
    /** @} */

    /** Padding. */
    uint64_t                    au64Padding[2];
} VMXVMCSINFO;

因为传入CPU的地址需要是物理地址,为了VMM代码方便访问这些物理地址,可以看到很多内容同时保存了物理地址和虚拟地址的指针。

4.1.3 VMExit:VMX异常

VMM调用VMEntry进入Guest OS之后,GuestOS需要与Host交互(比如GuestOS调用设备的IN/OUT指令),则需要通过VMExit退回到VMM里,让host处理相关内容之后再返回GuestOS。
什么样的时间需要处理器触发VMExit异常,则需要在GuestOS启动之前在VMCS里设置好,如上一章的VMXVMCSINIF结构体的里u32ProcCtls项等。
VMExit解释可以具体参考Intel指令手册的第24章

VMCS设置好了VMExit handlle之后,调用

VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, uProcCtls);

就完成了VMExit时间的设定,当GuestOS运行到指定的CPU指令的时候,CPU就会触发VMExit中断。

除了少数不可屏蔽VMExit中断,比如VMX_EXIT_CPUID之外,其他VMExit中断都是可以在GuestOS运行的时候动态控制的。
比如在某个时间,当CPU执行CR8写入指令的时候发现VMExit:

//设置fVal值
fVal |= VMX_PROC_CTLS_CR8_STORE_EXIT;
//commit到VMCS里
VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, fVal);

从commit到VMCS之后,当CPU执行到CR8写入指令的时候,就会触发VMExit异常,进入VMM里。
同意,如果不想当CPU执行CR8写入指令的时候发生异常, 修改VMCS即可。

//设置fVal值
fVal &= ~VMX_PROC_CTLS_CR8_STORE_EXIT;
//commit到VMCS里
VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, fVal);

从commit到VMCS之后,当CPU执行到CR8写入指令的时候,都不会触发VMExit异常。

注释:
VMExit数量和虚拟机性能的关系:
VMExit这条指令会让CPU从non-root模式切换到root模式,这个过程会保留GuestOS的运行上下文,然后切换到HostOS运行上下文,同样当vmexit处理完成从root模式切换到non-root模式的时候,也会触发上下文切换,所以如果GuestOS运行的时候,发生的vmexit过多,会导致GuestOS运行效率大大下降(因为大量的CPU时间片都用来处理上下文切换),所以VMExit的设置需要避免设置一些调用频繁的时间,比如gs切换,rdmsr等。
但如果真的需要监控rdmsr指令怎么办?比如VT模式可以通过rdmsr C0000082(syscall入口)过PG时,需要当PatchGuard代码执行rdmsr C0000082这条指令的时候发生vmexit中断。
Intel非常贴心的设置了各种细粒度控制的办法,比如对msr的访问,可以设置msr bitmap来控制哪些msr寄存器的访问需要产生vmexit中断。

void hmR0VmxSetMsrPermission(PVMCPU pVCpu, uint32_t uMsr, VMXMSREXITREAD enmRead, VMXMSREXITWRITE enmWrite)
 {
 	 uint8_t *pbMsrBitmap = (uint8_t *)pVCpu->hm.s.vmx.pvMsrBitmap;
 	 /*
     * MSR Layout:
     *   Byte index            MSR range            Interpreted as
     * 0x000 - 0x3ff    0x00000000 - 0x00001fff    Low MSR read bits.
     * 0x400 - 0x7ff    0xc0000000 - 0xc0001fff    High MSR read bits.
     * 0x800 - 0xbff    0x00000000 - 0x00001fff    Low MSR write bits.
     * 0xc00 - 0xfff    0xc0000000 - 0xc0001fff    High MSR write bits.
     */
    //0x00000000 - 0x00001fff id
    if (uMsr <= 0x00001fff)
        iBit = uMsr;
    //0xc0000000 - 0xc0001fff 是设置在pbMsrBitmap的0x400~0x800
    else if (uMsr - UINT32_C(0xc0000000) <= UINT32_C(0x00001fff))
    {
        iBit = uMsr - UINT32_C(0xc0000000);
        pbMsrBitmap += 0x400;
    }
    //msr读取拦截是设置在0 ~ 0x800里
	if (enmRead == VMXMSREXIT_INTERCEPT_READ)
        ASMBitSet(pbMsrBitmap, iBit);
    else
        ASMBitClear(pbMsrBitmap, iBit);
	//msr写入拦截是设置在0x800 ~ 0xfff里
    if (enmWrite == VMXMSREXIT_INTERCEPT_WRITE)
        ASMBitSet(pbMsrBitmap + 0x800, iBit);
    else
        ASMBitClear(pbMsrBitmap + 0x800, iBit);
 	}

当需要拦截c0000082这个msr读取的时候,只需要设置MSR_K8_LSTAR写拦截,即可拦截到读取这个msr

hmR0VmxSetMsrPermission(pVCpu, MSR_K8_LSTAR, VMXMSREXIT_INTERCEPT_READ, VMXMSREXIT_INTERCEPT_WRITE);

参考资料:
https://software.intel.com/sites/default/files/managed/39/c5/325462-sdm-vol-1-2abcd-3abcd.pdf

Logo

鸿蒙生态一站式服务平台。

更多推荐