0x01 硬件虛擬化數據結構
早期的虛擬機都是進程級虛擬機,也就是做爲已有操做系統的一個進程,徹底經過軟件的手段來模擬硬件,軟件再翻譯內存地址的方法實現物理機器的模擬,這樣虛擬效率較低,資源利用率低。以後Intel和AMD相繼推出了支持硬件虛擬化的處理器,所謂硬件虛擬化,也就是在硬件抽象層對虛擬技術提供直接支持,提升虛擬效率。咱們以前提到過在進程級虛擬機中的客戶機訪問的物理地址須要通過軟件的再次轉化成真實機器的物理地址,並且也須要給不一樣的客戶機操做系統編寫不一樣的虛擬設備驅動程序從而能夠共享同一真實的硬件資源。而硬件虛擬化則是實現了內存地址甚至與I/O設備的直接映射,無需通過再一次的轉換。而硬件虛擬化技術中引出了一個重要的概念——VMM(Virtual Machine Monitor),使硬件虛擬化技術產生的一個新的特權級,用來處理虛擬硬件和真實硬件的通訊和一些事件的處理,所以其系統權限在操做系統之上,產生了一個新的特權級「Ring -1」。能夠簡單理解爲利用硬件虛擬化技術可讓咱們的代碼運行在操做系統之下,監視整個操做系統的運行。app
Intel VIrtual Techonlogy , intel 硬件虛擬化技術 ,在硬件級別上完成計算機的虛擬化
爲實現硬件虛擬化 ,VT增長了 12條新的 VMX指令:
[VMCS控制 5 條]
VMPTRLD
VMPTRST
VMCLEAR
VMREAD
VMWRITE
[VMX命令 5條]
VMLAUNCH
VMCALL
VMXON
VMXOFF
VMRESUME
[Guest software 2條]
INVEPT
INVVPID
12條指令對應的機器碼(xen-3.4.1\xen\include\asm-x86\hvm\vmx\vmx.h)
代碼中使用 _emit
#define VMCALL_OPCODE ".byte 0x0f,0x01,0xc1\n"
#define VMCLEAR_OPCODE ".byte 0x66,0x0f,0xc7\n" /* reg/opcode: /6 */
#define VMLAUNCH_OPCODE ".byte 0x0f,0x01,0xc2\n"
#define VMPTRLD_OPCODE ".byte 0x0f,0xc7\n" /* reg/opcode: /6 */
#define VMPTRST_OPCODE ".byte 0x0f,0xc7\n" /* reg/opcode: /7 */
#define VMREAD_OPCODE ".byte 0x0f,0x78\n"
#define VMRESUME_OPCODE ".byte 0x0f,0x01,0xc3\n"
#define VMWRITE_OPCODE ".byte 0x0f,0x79\n"
#define INVEPT_OPCODE ".byte 0x66,0x0f,0x38,0x80\n" /* m128,r64/32 */
#define INVVPID_OPCODE ".byte 0x66,0x0f,0x38,0x81\n" /* m128,r64/32 */
#define VMXOFF_OPCODE ".byte 0x0f,0x01,0xc4\n"
#define VMXON_OPCODE ".byte 0xf3,0x0f,0xc7\n"
驅動初在始化一個VMCS (Virtual Machine Control Structures)內存區域後,啓動VMM (Virtual Machine Monitor) ,
獲得最高權限從而管理硬件資源, 操做系統是運行於ring0下
VMM要監控管理整個系統的資源,於是VMM的權限是大於操做系統,它處於一個全新的級別ring -1ide
0x02 虛擬機的體系結構函數
Intel提供了處理器級的VMX(Virtual-Machine Extensions),從硬件層面支持VT技術。Intel VMX的體系結構可劃分爲兩層:VMM和VM。 ui
VMM(Virtual-Machine Monitors)做爲host,具備對processor(s)和平臺硬件的徹底控制權限。它爲guest提供VCPU(virtual processor)的抽象,並容許guest直接運行在邏輯處理器(logical processor)上。VMM具備對處理器資源、物理內存、中斷和IO的選擇控制的權利。(舉例:Xen就是一種VMM。)spa
VM(Virtual-Machine)相應地做爲guest,實際上是提供了一種guest軟件環境:它維護一個棧,其中包含了OS和application software。其每一個操做都獨立於其餘的VM,而且使用由同一個物理平臺所提供的對處理器、內存、硬盤、顯卡、IO訪問的統一接口。此外,這個棧並不知道VMM的存在。運行於VM中的軟件其權限是受限的,這樣才能保障VMM對整個平臺資源的徹底控制。(舉相應的例子:Xen上跑的Guest OS就是VM。)操作系統
0x03 VMX operation翻譯
支持虛擬化的處理器其虛擬化相關的操做被稱爲VMX operation 。它分爲兩類:VMX root operation 和 VMX non-root operation 。一般來講,VMM 運行於 VMX root operation 而 guest 運行於 VMX non-root operation 。兩種operation之間的轉換被稱爲VMX transitions:從root到non-root被稱爲VM entries,而從non-root到root則稱爲VM exits。設計
處於VMX root operation的CPU其行爲與在VMX operation以外是基本同樣的,最根本的不一樣之處在於其增長了一套新的VMX指令集,且能存儲到特定控制寄存器的值是有限的。而處於VMX non-root operation的CPU起行爲是受限的,且通過了修改以幫助實現virtualization。與其普通的operation不一樣,特定的指令(包括新增的VMCALL指令)和事件將致使VM exits從而進入VMM:因爲這些VM exits代替了之前正常的行爲,因此在VMX non-root operation中的軟件的功能是受限的(也正是這種限制保證了VMM可以始終具備控制處理器資源的能力)。
從guest的角度,沒有任何一個Guest可見的位來指示一個邏輯處理器是否處於VMX non-root operation,這樣VMX就能保證guest並不知道其正在運行於一個VM中。即使是CPL(current privilege level)爲0,VMX operation也給guest加了限制,這樣guest software就能夠徹底沒必要改變其原始的設計,這也簡化了VMM的開發。調試
看一下VMM與Guest之間的交互了。大致的流程是這樣子的:
0x04 VMCS(Virtual-Machine Control Structure)
VMCS(Virtual-Machine Control Structure)是一個很是重要的數據結構,這個數據結構在下文中還要很是詳細地介紹。對VMCS的訪問是由一組被稱做VMCS pointer(每一個logical processor有一個VMCS pointer)的處理器狀態來管理的。VMCS pointer是一個64位的VMCS地址,可經過VMPTRST和VMPTRLD指令對其進行讀寫;VMM可使用VMREAD、VMWRITE、VMCLEAR指令對VMCS進行配置。對VMM所管理的每一個VM,VMM可使用不一樣的VMCS;且對VM中的每一個logical processor(or vcpu),VMM也能夠爲每一個vcpu使用不一樣的VMCS。
VMX operation須要處理器支持,那麼如何從軟件層面判定一個處理器是否支持VMX operation:經過CPUID --- 若是CPUID.1:ECX.VMX[bit 5] = 1,那說明該CPU支持VMX operation。現有的VMX體系結構的設計具備良好的可擴展性,software可使用一個VMX capability MSRs集來得到VMX新增的擴展特性。
如今再來看看如何使能和進入VMX operation:進入VMX operation以前,system software經過設置CR4.VMXE[bit 13] = 1 來使能VMX,以後就能夠經過VMXON指令來進入VMX operation。若是CR4.VMXE = 0,VMXON將致使一個invalid-opcode異常(#UD),並且一旦進入VMX operation,CR4.VMXE就沒法在此中被清零;system software經過VMXOFF離開VMX operation,只有在VMX operation外部CR4.VMXE才能被清零。
VMXON由IA32_FEATURE_CONTROL MSR (MSR address 3AH)所控制,當一個logical processor被rest時,該MSR被清零。此MSR的相關位以下:
在執行VMXON以前,software應該分配出(保留)一塊4KB對齊的內存區域供logical processor用以支持VMX operation。這個內存區域就被稱爲VMXON region。
最後,簡單說說VMX operation上的限制:VMX operation對processor operation做了一些限制,諸如,
從源碼級別,Xen裏面的vmx.c描述的就是Intel VMX體系結構相關的VM Exits支持:對照着Intel的Manual能夠找到Xen對其的具體實現。
0x05 初識VMM的創建過程
首先經過CPUID檢查CPU是否支持VT
硬件環境的檢測,經過指令CPUID檢測CPU是否支持VT
ECX的第5個bit標誌表明對 VT 的支持與否
初始化主要的內存區域
使用VMXON 進入 VMX (虛擬機指令擴展指令集)操做
執行VMXON指令 , EFLAGS.CF 可判斷執行是否成功
__asm
{
PUSH 0
PUSH PhysicalVMXONRegionPtr.LowPart
_emit 0xF3 // VMXON [ESP]
_emit 0x0F
_emit 0xC7
_emit 0x34
_emit 0x24
PUSHFD
POP eFlags
ADD ESP, 8
}
接下來開始初始化VMCS區域,(至關於部署軍隊,準備起義)
這個結構只可以被VMCLEAR, VMPTRLD, VMREAD,和VMWRITE操做。
VMM運行後guest 執行這些指令時會引起#VMExit事件
VMCS 是一個4K的內存區域
在邏輯上,虛擬機控制結構被劃分爲 6 部分:
1) GUEST-STATE 域:虛擬機從根操做模式進入非根操做模式時,處理器所處的狀態;
2) HOST-STATE 域:虛擬機從非根操做模式退出到根操做模式時,處理器所處的狀態;
3) VM 執行控制域:虛擬機在非根操做模式運行的時候,控制處理器非根操做模式退出到根操做模式;
4) VM 退出控制域:虛擬機從非根操做模式下退出時,須要保存的信息;
5) VM 進入控制域:虛擬機從根操做模式進入非根操做模式時,須要讀取的信息;
6) VM 退出信息域:虛擬機從非根操做模式退出到根操做模式時,將退出的緣由保存到該域中。
在這裏初始化的代碼比較多,要初始化 host 與 guest 的 CR0,CR3,CR4,IDTR,GDTR,LDTR,Rflag,SYSENTER_CS,SYSENTER_EIP,SYSENTER_ESP等
初始化時使用VMWRITE 指令設置 , 設置時主要的宏是
GUEST_ES_SELECTOR = 0x00000800,
GUEST_CS_SELECTOR = 0x00000802,
GUEST_SS_SELECTOR = 0x00000804,
GUEST_DS_SELECTOR = 0x00000806,
GUEST_FS_SELECTOR = 0x00000808,
GUEST_GS_SELECTOR = 0x0000080a,
GUEST_LDTR_SELECTOR = 0x0000080c,
GUEST_TR_SELECTOR = 0x0000080e,
HOST_ES_SELECTOR = 0x00000c00,
HOST_CS_SELECTOR = 0x00000c02,
HOST_SS_SELECTOR = 0x00000c04,
HOST_DS_SELECTOR = 0x00000c06,
HOST_FS_SELECTOR = 0x00000c08,
HOST_GS_SELECTOR = 0x00000c0a,
HOST_TR_SELECTOR = 0x00000c0c,
…
使用 VMWRITE 指令集成的函數
VOID WriteVMCS( ULONG encoding, ULONG value )
{
__asm
{
PUSHAD
PUSH value
MOV EAX, encoding
_emit 0x0F
_emit 0x79
_emit 0x04
_emit 0x24
POP EAX
POPAD
}
}
例如初始化 GDT , IDT 使用的是如下代碼
__asm
{
SGDT gdt_reg
}
temp32 = 0;
temp32 = gdt_reg.BaseHi;
temp32 <<= 16;
temp32 |= gdt_reg.BaseLo;
Log( "Setting Host GDTR Base" , temp32 );
WriteVMCS( HOST_GDTR_BASE, temp32 );
__asm
{
SIDT idt_reg
}
temp32 = 0;
temp32 = idt_reg.BaseHi;
temp32 <<= 16;
temp32 |= idt_reg.BaseLo;
Log( "Setting Host IDTR Base" , temp32 );
WriteVMCS( HOST_IDTR_BASE, temp32 );
比較重要的是設置 #VMExit 事件處理入口 HOST_RIP
WriteVMCS( HOST_RIP, (ULONG)VMMEntryPoint ); //0x6C16
最後執行 VMLAUNCH 指令 ,正式啓動虛擬機(創建了新的政權)
__asm
{
_emit 0x0F
_emit 0x01
_emit 0xC2
}
至此,整個VMM的帝國已經運行起來,帝國將會管理整個系統資源
接下來的就是#VMExit的事件循環處理(相似在調試器中下斷點後,等待事件發生)
運行過程當中Guest OS 遇到要監控的指令,會發生 #VMExit 事件 (至關於被調試程序執行到中斷事件)
此時由VMM 處理,例如cpuid ,
處理完後由 VMRESUME 繼續執行 (至關於在 OD 中 F9 繼續運行)
#VMExit事件的類型
#VMExit事件分爲二種
1 無條件事件CPUID GETSEC INVD XSETBV 全部 VMX指令
2 有條件事件I/O訪問,中斷事件, MSR寄存器訪問, HTL 等 (要設置VMCS相應部分觸發)
VMM使用VMREAD讀取虛擬機狀態,主要的一些變量有
VM_EXIT_REASON = 0x00004402, //退出代碼
VM_EXIT_INTR_INFO = 0x00004404, //中斷信息
VM_EXIT_INTR_ERROR_CODE = 0x00004406,
IDT_VECTORING_INFO = 0x00004408,
IDT_VECTORING_ERROR_CODE = 0x0000440a,
VM_EXIT_INSTRUCTION_LEN = 0x0000440c, //指令長度
VMX_INSTRUCTION_INFO = 0x0000440e,
GUEST_ES_LIMIT = 0x00004800,
GUEST_CS_LIMIT = 0x00004802,
GUEST_SS_LIMIT = 0x00004804,
GUEST_DS_LIMIT = 0x00004806,
…
其中最重要的就是VM_EXIT_REASON , 能夠看做是消息類型
主要的類型有
#define EXIT_REASON_EXCEPTION_NMI 0
#define EXIT_REASON_EXTERNAL_INTERRUPT 1
#define EXIT_REASON_TRIPLE_FAULT 2
#define EXIT_REASON_INIT 3
#define EXIT_REASON_SIPI 4
#define EXIT_REASON_IO_SMI 5
#define EXIT_REASON_OTHER_SMI 6
#define EXIT_REASON_PENDING_VIRT_INTR 7
#define EXIT_REASON_PENDING_VIRT_NMI 8
#define EXIT_REASON_TASK_SWITCH 9
#define EXIT_REASON_CPUID 10
#define EXIT_REASON_HLT 12
#define EXIT_REASON_INVD 13
#define EXIT_REASON_INVLPG 14
#define EXIT_REASON_RDPMC 15
#define EXIT_REASON_RDTSC 16
#define EXIT_REASON_RSM 17
#define EXIT_REASON_VMCALL 18
#define EXIT_REASON_VMCLEAR 19
#define EXIT_REASON_VMLAUNCH 20
#define EXIT_REASON_VMPTRLD 21
#define EXIT_REASON_VMPTRST 22
#define EXIT_REASON_VMREAD 23
#define EXIT_REASON_VMRESUME 24
#define EXIT_REASON_VMWRITE 25
#define EXIT_REASON_VMXOFF 26
#define EXIT_REASON_VMXON 27
…
DWORD VmxRead(DWORD in_code)
{
DWORD m_vmread = 0;
__asm
{
PUSHAD
MOV EAX, in_code
_emit 0x0F // VMREAD EBX, EAX
_emit 0x78
_emit 0xC3
MOV m_vmread, EBX
POPAD
}
return m_vmread;
}
VOID VMMReadGuestState( )
{
HandlerLogging = 0;
ExitReason = VmxRead(VM_EXIT_REASON); //0x4402
ExitInterruptionInformation = VmxRead( VM_EXIT_INTR_INFO); //0x4404
ExitInstructionLength = VmxRead(VM_EXIT_INSTRUCTION_LEN); //0x440c
ExitQualification = VmxRead(EXIT_QUALIFICATION) ; //0x6400
ExitInterruptionInformation = VmxRead(VM_EXIT_INTR_INFO); //0x4404
ExitInterruptionErrorCode = VmxRead(VM_EXIT_INTR_ERROR_CODE); //0x4406
IDTVectoringInformationField = VmxRead(IDT_VECTORING_INFO); //0X00004408 // IDT-Vectoring Information Field
IDTVectoringErrorCode = VmxRead(IDT_VECTORING_ERROR_CODE); //0X0000440A // IDT-Vectoring Error Code
ExitInstructionLength = VmxRead(VM_EXIT_INSTRUCTION_LEN); //0x0000440C // VM-Exit Instruction Length
ExitInstructionInformation = VmxRead(VMX_INSTRUCTION_INFO) ; //0x0000440E //VM-Exit Instruction Information
GuestEIP = VmxRead(GUEST_RIP); //0x0000681E; //GuestEIP
GuestESP = VmxRead(GUEST_RSP); //0x0000681c //esp
GuestCR3 = VmxRead(GUEST_CR3); //0X6802 GuestCR3
}
CPUID指令的攔截與修改
當 guest OS 執行 cupid 時,會獲得這樣一個錯誤號 10 (VMX_EXIT_CPUID )
當執行到CPUID這條指令的時候,響應,而後就能夠修改cpuid的返回值 if( ExitReason == VMX_EXIT_CPUID ) { if( GuestEAX == 0 ) { DbgPrint("CPUID EIP == %08X \n" , GuestEIP ); //0x34EC2B __asm { POPAD MOV EAX, 0 CPUID //修改CPUID返回值 MOV EBX, 0x80808080 MOV ECX, 0x90909090 MOV EDX, 0x10101010 JMP Resume } } else { __asm { POPAD MOV EAX, GuestEAX CPUID JMP Resume } } }