AMD-V学习笔记(一)
参考资料:intel SDM vol3
AMD64 Architecture Programmer’s Manual Volume 2(System Programming)
https://github.com/tandasat/SimpleSvm.git
源码:
实验代码:https://github.com/wutiaojian000/AFVM
调试环境:是内核项目的源码 https://github.com/wutiaojian000/AFKernel.git
本文地址:https://www.cnblogs.com/angel-fish/p/18903300
先看看AMD-V,再看KVM的原理。
因为我的电脑是amd的,所以这里看amd的文档了。
1. 概述
VMM的生命周期:

VMXON指令开启VMM->
VM entry指令可以进入一个客户机系统,VM exit退出到VMM->
VMEXIT退出VMM。
VMCS结构用于管理虚拟机,VMCS指针会指向这个结构。有专门的指令读取和保存VMCS指针,VMPTRST、VMPTRLD。也有专门的配置指令VMREAD、VMWRITE和VMCLEAR。每一个虚拟机都会有一个VMCS结构,准确来说是一个虚机的一个逻辑核对应一个VMCS结构。
1.1 查看处理器的VMX支持
进入VMX操作之前,需要通过CPUID确定CPU对VMX的支持。
#include <stdio.h>
void main()
{
unsigned int a = 0;
__asm("movl $0x80000001, %%eax;cpuid;movl %%ecx, %0":"=r"(a)::"eax", "ebx", "ecx", "edx");
printf("%x\n", a);
}
然后检查ecx的bit5是否为1,为1就是支持VMX。

看文档里说cpuid Fn4000_0000h-4000_00ffh是保留给软件用途的,可以通过将信息在hypervisor和guest中传递。
还有CPUID Fn0000_0001_ECX[31]如果是1,代表系统跑在虚拟机里。
#include <stdio.h>
void main()
{
unsigned int a = 0;
__asm("movl $0x00000001, %%eax;cpuid;movl %%ecx, %0":"=r"(a)::"eax", "ebx", "ecx", "edx");
printf("%x\n", a);
}

1.2 使能SVM
当EFER.SVME位(AMD的一个控制寄存器位,控制SVM的开启)为1时,VMRUN、VMLOAD、VMSAVE、CLGI、VMMCALL、INVLPGA这些指令可以用,否则UD(未定义指令);EFER.SVME位为1或CPUID Fn8000_0001_ECX[SKINIT]为1时,SKINIT和STGI指令可以用,否则UD(未定义指令)。
使能svm之前,按以下步骤进行检查,
if(CPUID Fn8000_0001_ECX[SVM] == 0)
return SVM_NOT_AVAIL;
if(VM_CR.SVMDIS == 0) //虚拟机控制寄存器VM_CR的SVM DISABLED位,表示SVM是否被禁用
return SVM_ALLOWED;
if(CPUID Fn8000_000A_EDX[SVML] == 0)
return SVM_DISABLED_AT_BIOS_NOT_UNLOCKABLE;
else return SVM_DISABLED_WITH_KEY;
VM_CR看到15.30 SVM related MSRs,(MSR代表型号特定寄存器,用于某些特定功能,比如这里是专用于VM的)

SVMDIS在bit4,为1时,对EFER.SVME的写入为MBZ(must be zero),意思是无法使能;为0时EFER.SVME正常写入。MSR寄存器需要通过RDMSR和WRMSR指令读写(3.2),且只能跑在内核态,所以这里只能系统调用。
CPUID Fn8000_000A_EDX[SVML]在15.31 SVM-Lock,为1时代表支持SVM-Lock,此时即使EFER.SVME=0也可以执行SKINIT和STGI。
下面先打一下实验环境。
在bochs中搭建了一个跑例程的环境:https://github.com/wutiaojian000/AFKernel.git,本来想在KVM上跑的,但暂时还不会调试,先搭bochs的环境跑先。
升级了一下bochs到3.0,configure查看bochs的官方文档(https://bochs.sourceforge.io/doc/docbook/user/compiling.html#AEN934),启动的时候需要加上-debugger。
bin/bochs -help cpu查看支持的CPU,需要确定好支持SVM的版本,

./configure
--prefix=/home/zcm/bochs3.0
--enable-debugger
--enable-disasm
--enable-iodebug
--enable-x86-debugger
--with-x
--with-x11
--enable-cpu-level=6
--enable-x86-64
--enable-svm
试了半天,总算试出来了,phenom_8650_toliman平台下可以做实验。在bochs虚拟机配置文件中添加一行
cpu: model=phenom_8650_toliman, count=1, ips=1000000

可以看到这里ecx的bit2是1,支持SVM。检测SVM是否能开启的代码在src/arch/AMD-V/sys.h下check_SVM_support函数。
接下来继续。
1.3 VMRUN
AMD使用VMCB结构体是一个4KB对齐的页,记录guest系统中需要拦截的指令或事件、定义guest系统执行环境的控制位或指定guest运行前的特殊操作、处理器状态等。VMRUN使用VMCB做为它的参数。
VMRUN指令需要跑在CPL0上,CPU在保护态且EFER.SVME需要为1,否则会报未定义指令异常。主机状态通过VM_HSAVE_PA的msr寄存器保存,运行guest系统时从对应的VMCB中获取信息,检查guest状态。一切就绪后就开始跑guest系统,直到有需要拦截的事件发生,调用VMEXIT指令返回到host系统。返回时只会记录下用于恢复虚拟机状态的信息,如果需要保存其他信息,使用VMLOAD、VMSAVE指令。
(1)保存主机状态
为了确保能正确回复主机状态,需要保存以下信息:(以32位为例)
CS.SELECTOR,EIP;
EFLAGS,EAX(EAX中存VMCB地址);
SS.SELECTOR,ESP;
CR0,CR3,CR4,EFER;
IDTR,GDTR;
ES.SELECTOR,DS.SELECTOR.
(2)加载客户机状态
保存主机状态之后,从VMCB中加载客户机状态。
CS,EIP;
EFLAGS,EAX(EAX中存VMCB地址);
SS.SELECTOR,ESP;
CR0,CR2,CR3,CR4,EFER;
IDTR,GDTR;
ES,DS;
INTERRUPT_SHADOW(用于虚拟机执行期间暂时屏蔽中断);
IDTR,GDTR;
DR6,DR7(断点相关);
V_TPR(虚拟机任务优先级寄存器);
V_IRQ(中断相关);
CPL(guest在实模式时CPL是0,在x86模式下CPL为3,其他情况看VMCB中保存的CPL,x86模式是保护模式的一种扩展模式).
加载guest状态时会检查一致性,检查不通过会调用VMEXIT。看看479页,
VMRUN检查guest状态,VMEXIT检查host状态。错误的guest状态导致VMEXIT退出,异常码VMEXIT_INVAILD。以下情况视作错误状态:
- EFER.SVME为0
- cr0.cd是0且cd0.nw为1(cr0.cd是0表示开启缓存策略,cd0.nw表示开启写回策略,为什么会有问题呢?)
- cr0不是0
- cr3、cr4和EFER的MBZ(must be zero)位为1
- EFER.LMA(long mode Activation)或EFER.LME(long mode enable)是非0且处理器不支持长模式(64位下的保护模式)
- EFER.LME且cr0.PG被置位且cr4.PAE(Physical Address Extension)为0
- EFER.LME且cr0.PG被置位且cr0.pe(保护模式)为0
- EFER.LME、cr0.PG、cr4.PAE、CS.L(long mode)和CS.D(D/B位)都不是非0值
- VMRUN的中断位被清0?
- MSR或I/O拦截表扩展到一个大于或等于最大支持的物理地址
- 非法的事件注入
- ASID(用于区分不同的地址空间)为0
- S_CET的保留位被设置
- cr0.WP为0且cr4.cet为1
- eflags.VM为1且cr4.cet为1,u_cet.SS为1
- u_cet的保留位被设置
未完待续,后面会根据项目中实际用到的来补充这篇文章的内容。

浙公网安备 33010602011771号