• 1148阅读
  • 1回复

对于VM Exit处理过程详解 [复制链接]

上一主题 下一主题
离线天道酬勤
 

只看楼主 倒序阅读 使用道具 楼主  发表于: 2016-02-10
— 本帖被 天道酬勤 从 驱动保护 移动到本区(2016-05-19) —

NBP对于VM Exit处理过程详解  1. 首先来看VM Exit引发后执行的函数是如何调用到的:  由于VM Exit会引发许多寄存器值刷新,这里主要关系到的是RIP和RSP。在VM Exit触发后,RIP和RSP会被VMCS中Host RIP和Host RSP域的值替换。 在Vmx.c中的VmxSetupVMCS中  VmxWrite (HOST_RIP, (ULONG64) VmxVmexitHandler);  这句的把VmxVmexitHandler的函数指针写到了HOST_RIP(0x00006c16)中,这样在VM Exit被触发并替换RIP的值后,就会自动执行VmxVmexitHandler这个方法。  另外VmxWrite (HOST_RSP, (ULONG64) Cpu);表示VM Exit触发后,RSP的值会指向Cpu结构体。所以在执行Trap过程的堆栈在内存里的位置应该是和Cpu结构体的位置相关的。同时在调用VmxVmexitHandler时的第一个参数PCPU Cpu,也就可以通过rsp的值来获得了。

2. VmxVmexitHandler(Vmx-asm.asm)
前期的准备是为调用HvmEventCallback而做的,由于HvmEventCallback的参数为:PCPU Cpu,  PGUEST_REGS GuestRegs。在64位系统中,由调用规范,可以看到,Cpu指针被赋给了rcx,GuestRegs指针被赋给了rdx(这里我的理解是,VmxVmexitHandler首先执行了HVM_SAVE_ALL_NOSEGREGS, 此后rsp指向栈中保存的所有寄存器值的首地址,这样栈内的参数就等于自动与GuestRegs中的变量绑定,然后在trap方法中通过修改GuestRegs中相应变量的值,就相当于修改了栈中对应保存的寄存器的值,然后退出VmxVmexitHandler 之前会调用HVM_RESTORE_ALL_NOSEGREGS来恢复寄存器,此时用来恢复的值已经由于保存在栈中已经被修改,也就达到了对输出结果的修改。  sub rsp,28h,这句根据x64调用规范,为调用函数为被调用函数分配参数在栈中的空间,然后被调用函数会在调用时把参数放到之前保留的空间中。由于必须至少保留4个寄存器参数的空间,因此至少要空出20H的空间(经测试,改为20H也可跑通)。  执行完HvmEventCallback后,把之前分配的空间取消也就是add rsp, 28h。然后恢复寄存器(HVM_RESTORE_ALL_NOSEGREGS),然后通过vmx_resume把控制权交还给VM。  

3. HvmEventCallback(Hvm.c)    if (Hvm->Architecture == ARCH_VMX)     GuestRegs->rsp = VmxRead (GUEST_RSP);  RSP在这里被VmxRead方法赋值,不过这个方法只是与Intel架构相关,GUEST_RSP是指示存储在VMCS中的物理地址,从这里读到的值就是Guest的rsp的值。           //GUEST_RSP = 0x0000681c,   if (Hvm->ArchIsNestedEvent (Cpu, GuestRegs)) { …… }  这里的if语句在Intel环境下不会进入,由于没有实现嵌套。 因此关键的语句是Hvm->ArchDispatchEvent (Cpu, GuestRegs);   if (Hvm->Architecture == ARCH_VMX)  VmxWrite (GUEST_RSP, GuestRegs->rsp);  这两句与之前的read rsp的值对应。其实由于进入vmm,之前提到的保存寄存器的动作执行时,有些寄存器的值是已经被修改了的,比如rsp(此时已经是指向vmm的栈了),这时保存的值是不能和GuestRegs的rsp对应上的,也因此在做保存寄存器的值时,push的是rbp不是rsp:
  注释为rsp,因为在GuestRegs中对应的位置放的应该是rsp值

对rsp的保存和恢复是必须通过VMCS中的GUEST_RSP域的赋值来实现的,这里通过GuestRegs->rsp = VmxRead (GUEST_RSP);来获取rsp的值写到GuestRegs的对应变量中。  
  
4. VmxDispatchEvent (Vmx.c  ArchDispatchEvent对应的intel架构函数指针)  向下函数调用VmxHandleInterception (Cpu, GuestRegs, FALSE                          /* this intercept will not be handled by guest hv */  );  

5. VmxHandleInterception (Vmx.c)  Exitcode = VmxRead (VM_EXIT_REASON);  读取VMCS中的EXIT_REASON,Exitcode的值与含义的对应关系可在Vmx.h中找到,均有芯片固定提供。  Status = TrFindRegisteredTrap (Cpu, GuestRegs, Exitcode, &Trap);  在开始绑定的处理函数与Exit_reason的Trap的链表中找对应的Trap附给Trap变量,找到的话返回STATUS_SUCCESS,没找到返回
STATUS_NOT_FOUND  执行TrExecuteGeneralTrapHandler。  

6. TrExecuteGeneralTrapHandler(Traps.c)  if (Trap->TrapCallback (Cpu, GuestRegs, Trap, WillBeAlsoHandledByGuestHv)) {     // trap handler wants us to adjust guest's RIP     Hvm->ArchAdjustRip (Cpu, GuestRegs, Trap->General.RipDelta);   }  Trap->TrapCallback (Cpu, GuestRegs, Trap, WillBeAlsoHandledByGuestHv)这句相当于调用了对应的处理函数。以CPUID为例,调用的就是VmxDispatchCpuid(VmxTraps.c)。 Hvm->ArchAdjustRip (Cpu, GuestRegs, Trap->General.RipDelta);  内部执行的是VmxWrite (GUEST_RIP, VmxRead (GUEST_RIP) + Delta);  可以理解为由于对某条指令执行了trap,在把控制权返还VM时,需要告诉VM跳过trap的那条指令。   四、 添加Trap  Hvm.c的Status = Hvm->ArchRegisterTraps (Cpu); 会调用到Vmxtraps.c中的VmxRegisterTraps方法  这个方法初始化了目前虚拟机trap的相关内容,对每一种trap调用  TrInitializeGeneralTrap或TrInitializeMsrTrap(intel部分未使用)或TrInitializeIoTrap(均未使用) 来绑定相应的处理函数和属性,再调用TrRegisterTrap把初始完的一个trap添加到对应的链表里。
    简单地说就是通过上图的类似方法添加即可。  目前看来,这样的添加只对某些开启的会引发VM Exit的一些指令相关(如何开启时通过在VMCS相应位置设定值来定义的)
离线v2680267313

只看该作者 沙发  发表于: 2016-04-30
用户被禁言,该主题自动屏蔽!
快速回复
限100 字节
批量上传需要先选择文件,再选择上传
 
上一个 下一个