从VMM中识别GUEST OS中的用户进程

 

VMM 中识别 GUEST OS 中的用户进程

康华 :主要从事 Linux 操作系统内核、虚拟机、 Linux 技术标准、计算机安全、软件测试等领域的研究与开发工作,曾就职 MII-HP 软件实验室 、瞬联软件公司 /MOTOROLA LENOVO 研究院 。其所合写的 Linux 专栏见 http://www.csdn.net/subject/linux/   如果需要可以联系通过 [email protected] MSN )联系他

 

摘要 : 本文给出了一种从 VMM(virtual machine monitor) 中根据截获的硬件访问信息和 GUEST OS 的进程管理信息,在系统运行时自动识别 GUEST OS 中运行进程的方法——该方法不需要 GUEST OS 做任何修改或者安装任何软件。其意义在于将 VMM 的监控粒度从系统级 , 提高到进程级别。我们可以很方便的在此基础上实现很多有趣的功能 ( 见下文 )

 

1. 背景介绍

       我的动机来自于一个简单的问题 " 如何从 VMM 中获知 GUEST OS 的负载 ?" , 最直接的办法是在 GUEST OS 安装一个精灵进程 , 不断收集负载信息 , 然后通过共享内存告诉 VMM, 但是如果 GUEST OS 禁止安装任何软件 , 那该怎么办呢 ? 我当时想借助最原始的方法:计算 idle 进程在单位时间内的运行时间 , 从而获得 GUEST OS 的运行负载。所以我需要从 VMM 中识别 GUEST OS idle 进程。 沿着该思路 , 我想也许由 VMM 识别 GUEST OS 的进程会对一些管理场景有所帮助 , 比如: 1 VMM 中监控 GUEST OS 的进程运行状况和资源利用情况; 2 VMM 中杀死 GUEST OS 中的运行进程 , 这也许被用于死锁解锁等目的; 3 加固 GUEST OS —— VMM 可限特定进程资源访问权限和范围等 ( 比如只让某个进程访问物理特定内存 ) 4 VMM 中为 GUEST OS 的进程动态打补丁( patch , 在不重启进程或系统的情况下修改 bug

 

   

: xen VMM 会利用 GUEST OS 执行 idle 进程时调用的 hlt 指令 (hlt 指令可以配置成陷入条件 ) 时间来估计 GUEST OS 的负载 , 当然这么做的前提是 GUEST OS idle 指令会循环调用 hlt 指令。

 

2. 工作原理

    VMM 会利用 GUEST OS 在做进程切换时 ( 需要访问特权积存器 CR3, 以载入新进程的页表基地址 , VT 环境下会造成一个陷入—— vmexit) 的陷入时机和所带硬件信息 , 建立并维护一个 GUEST OS 进程踪迹记录 (GPTR) 的多维向量 , 其中包含 1 GUEST OS 的进程页目录地址 (GPPDA), 其值从 CR3 中获取; 2 GUEST OS 进程的名字或 ID, 其值从 GUEST OS 的进程描述符中获得 , 或者人为指定一个唯一值 ( 如果无法从描述符获得的情况下 )

       有了 GPTR 向量我们就可以在运行时识别 GUEST OS 的运行进程了 , 具体做法是——用被 VMM 捕获的 GUEST OS 当前进程的 GPPDA 做键值 , GPTR GPPDA 记录向量里进行匹配 , 以识别 GUEST OS 中哪个进程在运行。

      : 寻找GUEST OS 进程描述符号的方法需要根据操作系统而定, 并无固定做法. 比如对于Linux 系统当前进程描述符号的索引和内核堆栈连续存放于2 页或1( 根据内核堆栈的编译选项), 所以我们可通过分析内核堆栈指针(RSP 也将在访问CR3 陷入时被VMM 获得) 而定位进程描述符位置, 从而解析出其中的进程ID 等信息( 见下图); 对于windows 系统, 定位当前进程描述符号更为方便。因为当前进程描述符会被放在一个位置固定( 对每种处理器而言)PRCB(processor control block).

 

定位 Linux 的进程描述符:

movl $0xffffe000,%ecx /* or 0xfffffe000 for 8KB  kernel stacks */    

andl %esp,%ecx

movl (%ecx),p              /* p pointe to current process descriptor */

 

3. 实现概要

我们以LinuxGUEST OS 为例讲述实现概要。

1.        VMM GUEST OS 做进程切换时捕获到 vmexit

2.        VMM CR3 中获得待运行进程的GPPDA 和栈指针RSP (指向内核堆栈,因为进程切换发生与内核态)。

3.        VMM 通过RSP 找到当前进程的描述符。

4.        VMM 解析当前进程描述符,进程IDGPRID)

5.        VMM 将上次获得的GPPDA 和本次获得的GPRID 作为键值对形式,存储到GPTR 向量中。注意上轮获得的GPPDA 对于上轮来说就是待运行进程,对于本次来说则是当前进程(见下图)。

6.        VMM 执行正常流程。

 

进程切换示意图:

 从VMM中识别GUEST OS中的用户进程_第1张图片

4. 原形设计与实现

 

为了验证上述方法是否可行,我以 KVM VMM 实现了一个原形加以验证(之所以选择 KVM 是因为 KVM 易于调试,结构清晰;当然你也可以使用 XEN VMM 作平台),该原形中用户可以指定被跟踪的 VM virtual machine, GUEST OS , 并且在运行期获取该 VM 的运行进程信息。当然 GUEST OS 不需要有任何改动,修改的仅仅是 KVM 。原型代码请见< http://sourceforge.net/project/showfiles.php?group_id=200727 >。

   

4.1 kvm 修改部分

主要修改是增加了用户操作接口,以及一个进程跟踪模块(目前该模块仅仅面向 Linux GUEST OS)

 

1 设置了一个 Hook 函数( in handler_cr )—— ToTraceCr3 (vm process id) 来截获并解析 CR3 寄存器,并添加一个注册函数( in vmx.c) 以将 gptrace.ko 中的回调函数挂到 hook 上。

注:当 kvm 处理 GUEST OS 因为 EXIT_REASON_CR_ACCESS 原因而陷入时,回调用 "handler cr" 函数。

typedef int (Cr3TraceFunc)(struct kvm *kvm ,int reg);

Register_cr3_trace(Cr3TraceFunc *func)

{

  Hook_ToTraceCr3 = func;

}

EXPORT_SYMBOL_GPL(Register_cr3_trace);

 

2 kvm 结构中增加了 vm_id ( 即承载 GUEST OS 运行的 Qemu 进程的 pid) ,目的是为了能识别VM( virtual machine ,即 GUEST OS )。

3 kvm 结构中增加了 trace_enable 标识 (vm trace enable) ,目的是为了打开或关闭VM跟踪。

4 kvm 结构中增加一个 opaque pointer 域,目的是为了存储 gptrv 结构 ( 见下文 )

5 增加一些 ioctl 处理项

KVM_ENABLE_VM_TRACE (ioctl for /dev/kvm)

KVM_GET_VM_GPTRV(ioctl for /dev/kvm)

KVM_SET_VMID(for vcpu fd)

 

4.2 主要数据结构

 

最重要的数据结构是 "struct gptrv" ,每个 VM 都会维护一个该结构数据,用来存储进程跟踪记录 "guest process trace record vector"

 

struct gptrv                             //GUEST process trace record vector

   Struct gptritem{

   unsigned long gpptaddr;               //GUEST process page table directory address

   unsigned long gpdaddr;        //GUEST process descriptor address

   int gpid;                            //GUEST process id

   char gpname[30];                    //GUEST process's name

   __64 begin_time;              // first record time

   __64 last_time;               // last record time

}gptr[MAX_TRACE_NUMBER];           //how many process that can be record

int last;                          //last time running process

int curr;                           //current running process

}

 

另外一个需要解释的地方是 PID_OFFSE/COMM_OFFSET 这些宏 , 它们表示的是 pid/comm 域在进程描述符表中的偏移.我们解析 GUEST OS 的进程 id 和名称需要找到并读取这些值。不过要注意由于不同版本的 Linux 内核进程描述符表结构有变化 , 其中 pid/comm 的偏移不尽相同。 ( 今后我会寻求一个能自动探测并获取 pid/comm 等域的方法 )

 

4.3 操作和接口

1 .在VM 启动时设置VM process idKVM 结构中 (interface)

   利用KVM_SET_VMID ioctl,VM 启动时将qemu 进程的id 写入对应的kvm 结构。具体实现在kvm_qemu_create_context:ioctl(kvm_context->vm_fd, KVM_SET_VMID ,getpid())

2 .打开/ 关闭跟踪VM 功能(interface)

   利用 KVM_ENABLE_VM_TRACE ioctl, enable trace ==0 是关闭,1 则相反。

3 .获取VM 的运行进程信息(Interface)

利用KVM_GET_VM_GPTRV ioctl 获取给定VM 所维护的 进程跟踪记录.

4 .杀死VM 中的给定进程(interface)  -- 下篇文章中介绍.

5 .跟踪进程切换——主要功能模块,其算法描述如下:

   检查是否VMtrace enable flag 被设置

      IF No return;

   检查是否VMopaque 指针是NULL

      IF No : 创建gptrv 结构.

   检查是否CR3 值是否已经被记录在GPTR 向量中

      IF Yes

更新last index.

          更新timestamp

          记录gpdaddrlast 进程跟踪记录中。

          记录gpid/gpnamelast 进程跟踪记录中。

          更新last index

      IF No  

          记录gpptaddrcurr 进程跟踪记录中, 并更新其 timestamp

          记录gpdaddrlast 进程跟踪记录中

          记录gpdaddrlast 进程跟踪记录中

          更新 last  index curr  index.

   

注:

1  gpdaddr 获得通过两步 ( 方法同 Linux 获取当前进程描述符 current 的方法 ) :a rsp&0xffff000 ( fffff000 for 4k kernel stack;ffffe000 for 8k ) ;b kvm_read_GUEST (vcpu, rsp&0xffff000,4, gpdaddr) (read the GUEST virtual address of process descriptor)

2  gpid/gpname 获取通过如下语句 : kvm_read_GUEST(vcpu,gpdaddr+PID_OFFSET/COMM_OFFSET,length,gptr->ptrt[].gpid/gpname)

 

6 加载模块

  Insmod gptrace.ko

   

4.4 限制  

1.     目前获取 pid/comm 都是通过硬编码完成 , 因此仅仅适合 FC6 作为 GUEST OS, 如果你需要运行其它 Linux 发布版或者其他内核 , 需要你修改 PID_OFFSE/COMM_OFFSET 这些宏。

2.     目前只能记录 270 个活跃进程踪迹 . 因为现在我用 ioctl 带回数据 , ioctl 的最大传输限制是 4 ,16k.

3.     KVM 一样 , 该方法只能在支持 VT CPU 上实现。

4.    目前仅仅是原型验证,因此程序中尚有很多bug

5. 编译、运行

5.1 编译方式

1 下载原代码

2 进入目录 kvm-24

3 执行编译过程 ( KVM)

            ./make clean

            ./configure -prefix=/usr/local/kvm

            ./make

            ./make install

             ./modprobe kvm-intel    ( 我使用 Intel VT CPU)

            ./modprobe gptrace

4 启动一个 VM

   ./use/local/kvm/bin/qemu  <your vm image>

5 为了简单期间 , 我将用户工具实现在 <kvm>/user 目录下的 main.c . 你可执行在 <kvm>/user 目录执行 make 生成 kvmctl 执行文件 .

 

5.2 运行方式

1 .打开跟踪功能

./kvmctl -E vmid    (vmid is qemu process id ,you can get it form ps -aux|grep -I qemu)

2 .关闭跟踪功能

./kvmctl -D vmid

3 .显示 VM 的运行进程信息

    ./kvmctl -S vmid

4 .显示用法

    ./kvmctl -h

 

你可能感兴趣的:(从VMM中识别GUEST OS中的用户进程)