zoukankan      html  css  js  c++  java
  • [zz] 从VMM中识别GUEST OS中的用户进程

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

    康华 :主要从事 Linux 操作系统内核、虚拟机、 Linux 技术标准、计算机安全、软件测试等领域的研究与开发工作,曾就职 MII-HP 软件实验室 、瞬联软件公司 /MOTOROLA 、 LENOVO 研究院 。其所合写的 Linux 专栏见http://www.csdn.net/subject/linux/ 。   如果需要可以联系通过 kanghua151@msn.com ( 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. 实现概要

    我们以Linux 做GUEST OS 为例讲述实现概要。

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

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

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

    4.        VMM 解析当前进程描述符,进程ID (GPRID) 。

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

    6.        VMM 执行正常流程。

    进程切换示意图:

    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 id 到KVM 结构中 (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 .跟踪进程切换——主要功能模块,其算法描述如下:

       检查是否VM 的trace enable flag 被设置

          IF No : return;

       检查是否VM 的opaque 指针是NULL

          IF No : 创建gptrv 结构.

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

          IF Yes :

    更新last index.

              更新timestamp

              记录gpdaddr 到last 进程跟踪记录中。

              记录gpid/gpname 到last 进程跟踪记录中。

              更新last index 。

          IF No : 

              记录gpptaddr 到curr 进程跟踪记录中, 并更新其 timestamp

              记录gpdaddr 到last 进程跟踪记录中

              记录gpdaddr 到last 进程跟踪记录中

              更新 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

  • 相关阅读:
    ubuntu创建用户命令
    C#图像处理(各种旋转、改变大小、柔化、锐化、雾化、底片、浮雕、黑白、滤镜效果)
    Ubuntu14.04 64bit 编译安装nginx1.7+php5.4+mysql5.6
    ubuntu mysql 远程连接问题解决方法
    如何在LabWIndows/CVI中调用LabVIEW DLL
    NI MAX中缺少串口(转)
    LabWindows/CVI入门之第四章:库文件(转)
    c#中多线程同步Lock(锁)的研究以及跨线程UI的操作 (转)
    C# 实现生产者消费者队列 (转)
    Unity3d基于Socket通讯例子(转)
  • 原文地址:https://www.cnblogs.com/zhangzhang/p/2372643.html
Copyright © 2011-2022 走看看