Intel VT学习笔记(二)—— VMXE&VMXON
VT生命周期
描述:VT生命周期可以参考Intel开发手册第3卷图23-1。
周壑大大将VT生命周期总结为以下示意图:
并将这个流程比喻为:
- 开锁(将Cr4的VMXE位置1)
- 开柜门(VMXON)
- 拔电源(VMCLEAR,相当于初始化)
- 选中机器(VMPTRLOAD,选择需要处理的guest机)
- 装机(设置VMCS,通过VMWRITE)
- 开机(VMLAUNCH)
- 拔电源(依然是VMCLEAR)
- 关柜门(VMXOFF)
- 关锁(将Cr4的VMXE位置0)
VMXE
描述:在进入VMX模式前,需要将Cr4的VMXE位置1,告知他人系统已进入VMX模式,否则会失败。
typedef union
{
struct{
unsigned VME:1;
unsigned PVI:1;
unsigned TSD:1;
unsigned DE:1;
unsigned PSE:1;
unsigned PAE:1;
unsigned MCE:1;
unsigned PGE:1;
unsigned PCE:1;
unsigned OSFXSR:1;
unsigned PSXMMEXCPT:1;
unsigned UNKONOWN_1:1; //These are zero
unsigned UNKONOWN_2:1; //These are zero
unsigned VMXE:1; //It's zero in normal
unsigned Reserved:18; //These are zero
//unsigned Reserved_64:32;
};
}_CR4;
VMXON
描述:汇编指令,用于进入VMX操作模式。
准备工作
进入VMX模式前的准备工作可参考可参考Intel开发手册第3章31.5小节。
简单来说,进入VMX模式前需要做的事情有:
- 使用CPUID指令查看系统是否支持
- 查看相关的MSR寄存器确定VMX支持能力
- 创建一个4KB对齐的内存,大小由IA32_VMX_BASIC MSR指定(称作VMXON region)。
- 清除申请内存的前四个字节,并设置版本标识符(IA32_VMX_BASIC MSR低4字节)
- 确保Cr0的PE位、PG位为1
- 确保Cr4的VMXE位为1
- 确保IA32_FEATURE_CONTROL MSR的Lock位为1(BIOS设置)
- 以申请内存的物理地址作为操作数,并在执行后检查CF位是否为0
其中,第1、5、6、7点已经学习过,第2点目前来说不重要,先重点关注3、4、8这三点。
VMXON region
描述:在执行VMXON前,需要申请一块内存,用于实现VMX相关功能,例如存储VMM相关信息,由CPU负责维护,这块内存被称为VMXON region。
具体可参考Intel开发手册第3章附录A.1
这里是说,VMXON region具体大小可从IA32_VMX_BASIC MSR中读取(偏移为480H),第44:32位形成的整数即所需大小。
申请完内存后,需要将前四个字节设置为版本标识符(第30:0位)
在进入VMX模式时,需要将这块内存的物理地址作为VMXON的参数,并在执行后检查CF位是否为0。
代码实现
//vtasm.h
#ifndef VTASM_H
#define VTASM_H
typedef union
{
struct
{
unsigned SSE3 : 1;
unsigned PCLMULQDQ : 1;
unsigned DTES64 : 1;
unsigned MONITOR : 1;
unsigned DS_CPL : 1;
unsigned VMX : 1;
unsigned SMX : 1;
unsigned EIST : 1;
unsigned TM2 : 1;
unsigned SSSE3 : 1;
unsigned Reserved : 22;
};
}_CPUID_ECX;
typedef struct _IA32_FEATURE_CONTROL_MSR
{
unsigned Lock : 1; // Bit 0 is the lock bit - cannot be modified once lock is set
unsigned Reserved1 : 1; // Undefined
unsigned EnableVmxon : 1; // Bit 2. If this bit is clear, VMXON causes a general protection exception
unsigned Reserved2 : 29; // Undefined
unsigned Reserved3 : 32; // Undefined
} IA32_FEATURE_CONTROL_MSR;
typedef struct _VMX_BASIC_MSR
{
unsigned RevId : 32;
unsigned szVmxOnRegion : 12;
unsigned ClearBit : 1;
unsigned Reserved : 3;
unsigned PhysicalWidth : 1;
unsigned DualMonitor : 1;
unsigned MemoryType : 4;
unsigned VmExitInformation : 1;
unsigned Reserved2 : 9;
} VMX_BASIC_MSR, * PVMX_BASIC_MSR;
typedef union
{
struct
{
unsigned PE : 1;
unsigned MP : 1;
unsigned EM : 1;
unsigned TS : 1;
unsigned ET : 1;
unsigned NE : 1;
unsigned Reserved_1 : 10;
unsigned WP : 1;
unsigned Reserved_2 : 1;
unsigned AM : 1;
unsigned Reserved_3 : 10;
unsigned NW : 1;
unsigned CD : 1;
unsigned PG : 1;
//unsigned Reserved_64:32;
};
}_CR0;
typedef union
{
struct {
unsigned VME : 1;
unsigned PVI : 1;
unsigned TSD : 1;
unsigned DE : 1;
unsigned PSE : 1;
unsigned PAE : 1;
unsigned MCE : 1;
unsigned PGE : 1;
unsigned PCE : 1;
unsigned OSFXSR : 1;
unsigned PSXMMEXCPT : 1;
unsigned UNKONOWN_1 : 1; //These are zero
unsigned UNKONOWN_2 : 1; //These are zero
unsigned VMXE : 1; //It's zero in normal
unsigned Reserved : 18; //These are zero
//unsigned Reserved_64:32;
};
}_CR4;
typedef union
{
struct
{
unsigned CF : 1;
unsigned Unknown_1 : 1; //Always 1
unsigned PF : 1;
unsigned Unknown_2 : 1; //Always 0
unsigned AF : 1;
unsigned Unknown_3 : 1; //Always 0
unsigned ZF : 1;
unsigned SF : 1;
unsigned TF : 1;
unsigned IF : 1;
unsigned DF : 1;
unsigned OF : 1;
unsigned TOPL : 2;
unsigned NT : 1;
unsigned Unknown_4 : 1;
unsigned RF : 1;
unsigned VM : 1;
unsigned AC : 1;
unsigned VIF : 1;
unsigned VIP : 1;
unsigned ID : 1;
unsigned Reserved : 10; //Always 0
//unsigned Reserved_64:32; //Always 0
};
}_EFLAGS;
void Asm_CPUID(ULONG uFn, PULONG uRet_EAX, PULONG uRet_EBX, PULONG uRet_ECX, PULONG uRet_EDX);
ULONG64 Asm_ReadMsr(ULONG uIndex);
ULONG Asm_GetEflags();
ULONG Asm_GetCr0();
ULONG Asm_GetCr4();
void Asm_SetCr4(ULONG uNewCr4);
void Vmx_VmxOn(ULONG LowPart, ULONG HighPart);
void Vmx_VmxOff();
#endif
//vtasm.asm
.686p
.model flat, stdcall
option casemap:none
.data
.code
Asm_CPUID Proc uses ebx esi edi fn:dword, ret_eax:dword, ret_ebx:dword, ret_ecx:dword, ret_edx:dword
mov eax, fn
cpuid
mov esi, ret_eax
mov dword ptr [esi], eax
mov esi, ret_ebx
mov dword ptr [esi], ebx
mov esi, ret_ecx
mov dword ptr [esi], ecx
mov esi, ret_edx
mov dword ptr [esi], edx
ret
Asm_CPUID Endp
Asm_ReadMsr Proc Index:dword
mov ecx,Index
rdmsr
ret
Asm_ReadMsr Endp
Asm_GetCr0 Proc
mov eax, cr0
ret
Asm_GetCr0 Endp
Asm_GetCr4 Proc
mov eax, cr4
ret
Asm_GetCr4 Endp
Asm_SetCr4 Proc NewCr4:dword
mov eax,NewCr4
mov cr4, eax
ret
Asm_SetCr4 Endp
Vmx_VmxOn Proc LowPart:dword,HighPart:dword
push HighPart
push LowPart
Vmxon qword ptr [esp]
add esp,8
ret
Vmx_VmxOn Endp
Vmx_VmxOff Proc
Vmxoff
ret
Vmx_VmxOff Endp
Asm_GetEflags PROC
pushfd
pop eax
ret
Asm_GetEflags ENDP
END
//vtsystem.h
#ifndef VTSYSTEM_H
#define VTSYSTEM_H
#include <ntddk.h>
/*MSR definition*/
#define MSR_IA32_FEATURE_CONTROL 0x03a
#define MSR_IA32_VMX_BASIC 0x480
typedef struct _VMX_CPU
{
PVOID pVMXONRegion;
PHYSICAL_ADDRESS pVMXONRegion_PA;
PVOID pVMCSRegion;
PHYSICAL_ADDRESS pVMCSRegion_PA;
PVOID pStack;
BOOLEAN bVTStartSuccess;
}VMX_CPU, * PVMX_CPU;
//检查当前处理器是否支持VT
BOOLEAN IsVTEnabled();
//开启VT
NTSTATUS StartVirtualTechnology();
//关闭VT
NTSTATUS StopVirtualTechnology();
#define Log(message,value) {{KdPrint(("[MinVT] %-40s [%p]\n",message,value));}}
#endif
//vtsystem.c
#include "vtsystem.h"
#include "vtasm.h"
VMX_CPU g_VMXCPU;
BOOLEAN IsVTEnabled()
{
ULONG uRet_EAX, uRet_ECX, uRet_EDX, uRet_EBX;
_CPUID_ECX uCPUID;
_CR0 uCr0;
_CR4 uCr4;
IA32_FEATURE_CONTROL_MSR msr;
//1. CPUID
Asm_CPUID(1, &uRet_EAX, &uRet_EBX, &uRet_ECX, &uRet_EDX);
*((PULONG)&uCPUID) = uRet_ECX;
if (uCPUID.VMX != 1)
{
Log("ERROR: 这个CPU不支持VT!", 0);
return FALSE;
}
// 2. MSR
*((PULONG)&msr) = (ULONG)Asm_ReadMsr(MSR_IA32_FEATURE_CONTROL);
if (msr.Lock != 1)
{
Log("ERROR:VT指令未被锁定!", 0);
return FALSE;
}
// 3. CR0 CR4
*((PULONG)&uCr0) = Asm_GetCr0();
*((PULONG)&uCr4) = Asm_GetCr4();
if (uCr0.PE != 1 || uCr0.PG != 1 || uCr0.NE != 1)
{
Log("ERROR:这个CPU没有开启VT!", 0);
return FALSE;
}
if (uCr4.VMXE == 1)
{
Log("ERROR:这个CPU已经开启了VT!", 0);
Log("可能是别的驱动已经占用了VT,你必须关闭它后才能开启。", 0);
return FALSE;
}
Log("SUCCESS:这个CPU支持VT!", 0);
return TRUE;
}
NTSTATUS StartVirtualTechnology()
{
PVOID pVMXONRegion;
VMX_BASIC_MSR Msr;
ULONG uRevId;
_CR4 uCr4;
_EFLAGS uEflags;
if (!IsVTEnabled())
return STATUS_NOT_SUPPORTED;
*((PULONG)&uCr4) = Asm_GetCr4(); //设置Cr4的VMXE位
uCr4.VMXE = 1;
Asm_SetCr4(*((PULONG)&uCr4));
pVMXONRegion = ExAllocatePoolWithTag(NonPagedPool, 0x1000, 'vmon'); //申请一块4KB内存,给VMM用
if (!pVMXONRegion)
{
Log("ERROR:申请VMXON内存区域失败!", 0);
return STATUS_MEMORY_NOT_ALLOCATED;
}
RtlZeroMemory(pVMXONRegion, 0x1000);
g_VMXCPU.pVMXONRegion = pVMXONRegion;
g_VMXCPU.pVMXONRegion_PA = MmGetPhysicalAddress(pVMXONRegion); //获得这块内存的物理地址
*((PULONG)&Msr) = (ULONG)Asm_ReadMsr(MSR_IA32_VMX_BASIC);
uRevId = Msr.RevId;
Log("TIP:VMX版本号信息", uRevId);
*((PULONG)g_VMXCPU.pVMXONRegion) = uRevId; //设置版本标识符
//将64位物理地址作为VMXON参数,执行
Vmx_VmxOn(g_VMXCPU.pVMXONRegion_PA.LowPart, g_VMXCPU.pVMXONRegion_PA.HighPart);
*((PULONG)&uEflags) = Asm_GetEflags();
if (uEflags.CF != 0) //通过CF位判断是否调用成功
{
Log("ERROR:VMXON指令调用失败!", 0);
return STATUS_UNSUCCESSFUL;
}
Log("SUCCESS:VMXON指令调用成功!", 0);
return STATUS_SUCCESS;
}
NTSTATUS StopVirtualTechnology()
{
_CR4 uCr4;
Vmx_VmxOff(); //退出VMX模式
*((PULONG)&uCr4) = Asm_GetCr4(); //设置VMXE为0
uCr4.VMXE = 0;
Asm_SetCr4(*((PULONG)&uCr4));
ExFreePool(g_VMXCPU.pVMXONRegion); //释放内存
Log("SUCCESS:VMXOFF指令调用成功!", 0);
return STATUS_SUCCESS;
}
//driver.c
#include <ntddk.h>
#include "vtasm.h"
#include "vtsystem.h"
VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
StopVirtualTechnology();
DbgPrint("Driver unload. \r\n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING reg_path)
{
DbgPrint("Driver load. \r\n");
StartVirtualTechnology();
pDriver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
执行结果:
参考资料
- VT虚拟化架构编写视频教程①~⑥课
- 周鹤《VT技术入门》系列视频教程
- github项目:VT_Learn
- github项目: HyperPlatform
- Intel开发手册 卷3:Chapter 23 ~ Chapter 33
- x86内部函数列表