VT虚拟化技术笔记(part 1)

本文详细介绍了VT技术的概述,执行流程,以及如何检测CPU和BIOS对VT的支持。主要包括开锁、开柜门、拔电源等9步骤,涉及CR4寄存器、MSR寄存器设置和vmxon指令的使用。
摘要由CSDN通过智能技术生成

转:https://bbs.kanxue.com/thread-271854.htm
VT技术笔记(part 1)
最近在学习VT技术,想把学习过程中记录的笔记分享出来。技术不精有不对的地方还望指正。代码会发到https://github.com/smallzhong/myvt这个仓库中,目前还在施工中,还没写完。欢迎star,预计这个月内完工。

VT概述

在这里插入图片描述
VT技术的大致含义如上。一个CPU上有vm monitor和guest。在guest中感受不到自己跑在虚拟环境中。执行普通的操作时在guest中的操作和未开启vt时的操作并没有什么不同,但是在执行一些特殊的指令或者进行一些特殊的操作,如执行cpuid指令、切换cr3指令、读写msr寄存器指令、触发异常时,会将控制权交还给vm monitor,通过vm monitor确定应该如何让该指令或者操作得到执行。在云服务器的搭建中可以使用该技术让一台实体机上可以跑多台虚拟机。而该技术也可以做到无痕对一些指令进行高权限的hook。甚至可以通过ept技术进行对指定地址的无痕hook。

VT执行的大体流程

该流程的描述出自B站周壑的VT教学视频中,讲得非常通俗易懂。VT的开启与关闭大致分为开锁、开柜门、拔电源、选中机器、装机、开机、拔电源、关柜门、关锁9个步骤。这9个步骤的具体描述如下。此处为大体的描述,在后面会对每一个步骤的具体细节进行具体的说明。

在这里插入图片描述

开锁:检测是否支持VT
开柜门:vmxon。需要申请一块内存然后把这块内存的lowpart和highpart作为参数调用vmxon。注意这块内存的开头要填入一个特定的msr寄存器中的值。注意这里vmxon修改cr4寄存器之后,cr0寄存器的页保护和段保护位都不能再置位,如果试图修改cr0的页保护或者段保护位,会触发异常蓝屏。
拔电源:vmclear
选中机器:vmptrload,相当于一个指针。需要通过vmptrload来指向某个机器。如果当前有多个机器的话,在各个机器之间的切换操作也需要用到vmptrload。
装机:设置vmcs。也是要申请一块内存,然后在这块内存里面填入开启虚拟机时虚拟机使用的一些寄存器。要注意这里不能直接操作这块内存区域,要用vmwrite来操作。因为随着版本的更新,原来在这块内存区域的东西可能后来就不在了。vmwrite保证兼容性。
开机vmlaunch
拔电源vmclear
关柜门vmoff

检测是否支持VT(开锁)

在intel手册31.5章中详细描述了vmm的开启过程。
在这里插入图片描述
1通过cpuid指令查询CPU是否支持VT

2通过一些特定的MSR寄存器(在31.5.1中有介绍)来确认是否支持开启VT。

3申请一块非分页内存,用来存放vmxon区域。其大小在IA32_VMX_BASIC这个寄存器中指定。该内存要4kb对齐(后12位全为0)。实际申请时申请一个页的大小的非分页内存即可。

4初始化vmxon区域的版本编号(前4个字节)。版本编号通过IA32_VMX_BASIC这个寄存器的低4字节获取。实际实验时发现这个数为1,理论上把上一步申请到的内存前四字节置1即可,但为了兼容最好还是从寄存器中取值出来。

5给cr0和cr4寄存器的特定位置置位,使得其满足如下要求

IA32_VMX_CR0_FIXED0 寄存器中为1的位,在cr0中必须为1
IA32_VMX_CR0_FIXED1 寄存器中为0的位,在cr0中必须为0

IA32_VMX_CR4_FIXED0 寄存器中为1的位,在cr4中必须为1

IA32_VMX_CR4_FIXED1 寄存器中为0的位,在cr4中必须为0

6确认IA32_FEATURE_CONTROL寄存器被正确置位。该寄存器的具体细节可以通过《处理器虚拟化技术》2.3.2.1找到
在这里插入图片描述
也就是说,该寄存器的bit0是lock为,bit2是outside位。必须保证这两位均为1才可以开启VT。可以大致通过读取该寄存器后将其&5,判断是否两个位均为1。

7 执行vmxon指令,传进去的参数是一个指向存放vmxon区域的物理地址的指针。如果该指令成功,那么rflags.cf=0,否则rflags.cf=1。
由上可写出检测是否支持VT的代码如下:

//检测Bios是否开启VT
BOOLEAN VmxIsCheckSupportVTBIOS()
{
    ULONG64 value = __readmsr(IA32_FEATURE_CONTROL);
 
    return (value & 0x5) == 0x5;
}
 
 
//检测CPU是否支持VT
BOOLEAN VmxIsCheckSupportVTCPUID()
{
    int cpuidinfo[4];
    __cpuidex(cpuidinfo, 1, 0);
    //CPUID 是否支持VT ecx.vmx第6位 如果为1,支持VT,否则不支持
    return (cpuidinfo[2] >> 5) & 1;
}
 
 
//检测CR4VT是否开启,如果为1 代表已经开启过了,否则没有开启
BOOLEAN VmxIsCheckSupportVTCr4()
{
    ULONG64 mcr4 = __readcr4();
    //检测CR4 VT是否开启,cr4.vmxe如果第14位为1,那么VT已经被开启,否则可以开启
    return ((mcr4 >> 13) & 1) == 0;
}
 
void checkVT()
{
    if (VmxIsCheckSupportVTCPUID())
    {
        DbgPrintEx(77, 0, "[db]:VmxIsCheckSupportVTCPUID  number = %d\r\n", KeGetCurrentProcessorNumber());
    }
 
    if (VmxIsCheckSupportVTBIOS())
    {
        DbgPrintEx(77, 0, "[db]:VmxIsCheckSupportVTBIOS  number = %d\r\n", KeGetCurrentProcessorNumber());
    }
 
    if (VmxIsCheckSupportVTCr4())
    {
        DbgPrintEx(77, 0, "[db]:VmxIsCheckSupportVTCr4  number = %d\r\n", KeGetCurrentProcessorNumber());
    }
}

vmxon(开柜门)
申请一块4KB对齐的非分页内存,作为vmxon的参数

PHYSICAL_ADDRESS lowphys,heiPhy;
lowphys.QuadPart = 0;
heiPhy.QuadPart = -1;
pVcpu->VmxOnAddr = MmAllocateContiguousMemorySpecifyCache(PAGE_SIZE, lowphys, heiPhy, lowphys, MmCached);

设置vmxon区域。这一块的说明在intel白皮书24.11.5中。这一块内存是操作系统使用的。在申请区域之后要rtlzeromemory一下,防止没有挂页。其他位都置零即可,之后操作系统进行虚拟机的管理的时候会使用到这块内存。我们需要设置的只有头四个字节。要把msr中 IA32_VMX_BASIC 这个寄存器的低4字节填入vmxon的头4字节。否则vmxon的执行会出错。基本上这个寄存器的低4字节会是1。之后随着版本的更新可能会有变化。在这里插入图片描述


1
2
ULONG64 vmxBasic = __readmsr(IA32_VMX_BASIC);
*(PULONG)pVcpu->VmxOnAddr = (ULONG)vmxBasic;

在这里插入图片描述
具体解释在附录A中。在这里插入图片描述
简单解释就是在进行vmxon之前, IA32_VMX_CR0_FIXED0 寄存器中为1的值,在cr0中必须为1、 IA32_VMX_CR0_FIXED1 寄存器中为0的值,在cr0中必须为0。cr4同理。那么可以通过 cr0 = cr0 | IA32_VMX_CR0_FIXED0 & IA32_VMX_CR0_FIXED1 操作将cr0和cr4进行置位。具体代码如下。

ULONG64 vcr00 = __readmsr(IA32_VMX_CR0_FIXED0);
ULONG64 vcr01 = __readmsr(IA32_VMX_CR0_FIXED1);
ULONG64 vcr04 = __readmsr(IA32_VMX_CR4_FIXED0);
ULONG64 vcr14 = __readmsr(IA32_VMX_CR4_FIXED1);
 
ULONG64 mcr4 = __readcr4();
ULONG64 mcr0 = __readcr0();
 
mcr4 |= vcr04;
mcr4 &= vcr14;
mcr0 |= vcr00;
mcr0 &= vcr01;
 
__writecr0(mcr0);
__writecr4(mcr4);

在进行以上步骤之后,可以通过vmxon打开柜门。

int error = __vmx_on(&pVcpu->VmxOnAddrPhys.QuadPart);

申请vmcs内存并vmclear(拔电源)

首先申请vmcs内存。和vmxon内存类似,这一块内存的头4个字节也要填入 IA32_VMX_BASIC 的低4个字节。vmcs中保存的是虚拟机的各种寄存器和控制区域、vmexit控制区域等。在装机过程中要通过vmwrite进行vmcs的设置。

申请vmcs内存并设置头4字节为 IA32_VMX_BASIC 的低4字节之后,通过vmclear指令进行初始化。传入的参数与vmxon的参数类似,是指向申请的内存的物理地址的一个指针。vmclear的用法如下

__vmx_vmclear(&pVcpu->VmxcsAddrPhys.QuadPart);

vmptrload(选中机器)

vmptrload 指令和vmxon、vmclear类似,传入的是指向vmcs结构的物理地址的一个指针。只有执行了这行指令之后才能通过 vmwrite 进行vmcs结构的填写。代码如下

	
__vmx_vmptrld(&pVcpu->VmxcsAddrPhys.QuadPart);

设置vmcs(装机)

在这里插入图片描述

24章中描述了需要设置的VMCS字段。大体包括如下几部分

Guest state fields
Host state fields
vm-control fields
vm execution control
vm exit control
vm entry control
​ 除这5个区域之外,vmcs还有一个区域是vm exit信息区域。这个区域是只读的,存储的是vmx指令失败后失败代码的编号。

VMCS字段的设置是VT中最为繁琐的一步。对于VMCS中一些具体字段应该如何进行设置,会在下一篇文章中进行说明。
参考文献
intel白皮书
邓志《处理器虚拟化技术》
B站周壑VT教学视频

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值