虚拟机自省(Virtual Machine Introspection,简称VMI),是一种从外部(即Hypervisor)对虚拟机内部状态进行监控的技术。从Hypervisor层面,通过虚拟机的陷入,可以直接读取到虚拟机陷入瞬间的内存数据。
部署基于KVM的VMI开发环境主要分为三个部分,分别为KVM、QEMU、LibVMI。前两个是Linux下部署虚拟化的必备组件,LibVMI则是基于各种Hypervisor所实现的VMI库,官网地址https://libvmi.com。
KVM
编译内核
下面的步骤,编译了一个带有KVM的4.9.0版本内核。
首先从kernel.org - kvm/kvm.git上下载KVM的源码,可以直接git clone
,也可以下载Tarball。下载好源码之后,可以先使用make kernelversion
来查看一下源码对应的Kernel版本,然后开始配置内核config。
使用
make olddefconfig
将现有系统的config直接复制过来,调整部分冲突的配置项,其余的维持现状(这里还有一种选择是直接从默认的config里来创建:make x86_64_defconfig
,但是这样编译出来的内核因为一些驱动相关的选项可能没有打开,安装启动之后可能只能进BusyBox的Shell,是没办法进去系统的)使用
make menuconfig
进入命令行可视化界面继续调整部分配置项,这里列出KVM相关的配置项如下:1 2 3 4 5 6 7 8 9 10 11 12
CONFIG_HAVE_KVM=y CONFIG_HAVE_KVM_IRQCHIP=y CONFIG_HAVE_KVM_EVENTFD=y CONFIG_KVM_APIC_ARCHITECTURE=y CONFIG_KVM_MMIO=y CONFIG_KVM_ASYNC_PF=y CONFIG_HAVE_KVM_MSI=y CONFIG_VIRTUALIZATION=y CONFIG_KVM=m CONFIG_KVM_INTEL=m CONFIG_KVM_AMD=m CONFIG_KVM_MMU_AUDIT=y
之后就可以开始准备编译内核。首先更新系统,安装编译的依赖程序:
|
|
使用以下命令编译带有KVM的新内核。需要注意的是,视内核版本,必须使用能够兼容该版本内核的GCC和发行版版本来编译和安装,否则会出现编译失败的情况。
|
|
整个过程花费时间较长(30min以上),若输出中没有任何Error信息,也没有异常终止,说明内核已经成功安装了。
GRUB默认启动项设置
重启后在GRUB菜单中应该能够找到带有新内核的启动选项。若确认新安装的内核能够正常启动,则可以考虑将其设置为默认启动内核:
Ubuntu 18.04:执行命令
cat /boot/grub/grub.cfg| grep menuentry
,查找GRUB的所有启动项,输出如下:1 2 3 4 5 6
menuentry 'Ubuntu' ... submenu 'Advanced options for Ubuntu' ... menuentry 'Ubuntu, with Linux 5.4.0-84-generic' ... menuentry 'Ubuntu, with Linux 5.4.0-84-generic (recovery mode)' ... menuentry 'Memory test (memtest86+)' ... menuentry 'Memory test (memtest86+, serial console 115200)' ...
然后修改
/etc/default/grub
文件中的GRUB_DEFAULT
值为启动项对应的字符串即可(子菜单用项>
符号来指定)。例如上述启动项中第二项的子菜单中的第二项对应的值就是Advanced options for Ubuntu>Ubuntu, with Linux 5.4.0-84-generic (recovery mode)
。设置完毕之后,
sudo update-grub
更新GRUB配置文件,重启即可生效。CentOS 7:CentOS7中采用的是GRUB2,有特定的命令来进行操作,因此直接修改文件的方式可能不生效。
- 同样借助上述方法获取启动项的名称和索引值(文件位于
/boot/grub2/grub.cfg
) - 使用
grub2-editenv list
查看上次启动进入的启动项 - 使用
grub2-set-default 'Boot Option Name'
来设置下次默认的启动项 - 使用
grub2-editenv list
再次查看上次启动进入的启动项,是否更改 - 若已经更改为想要启动的项,则使用
grub2-mkconfig -o /boot/grub2/grub.cfg
重新生成GRUB配置 - 重启后生效
- 同样借助上述方法获取启动项的名称和索引值(文件位于
一些坑
GCC7编译4.9.0内核时报错提示
undefined reference to `____ilog2_NaN'
,这个问题需要打补丁,见https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/diff/?id=474c90156c8dcc2fa815e6716cc9394d7930cb9c,将补丁内容保存为patch.diff,然后patch -i patch.diff
应用修改。提示输入操作文件时,先后输入./include/linux/log2.h
,./tools/include/linux/log2.h
即可。编译报错
No rule to make target ‘debian/canonical-certs.pem‘, needed by ‘certs/x509_certificate_list‘
,这里是缺少一个对模块进行签名的Key,有一个解决办法是自己使用OpenSSL生成一个,但是最好的办法还是直接生成:注释掉这两行:
CONFIG_MODULE_SIG_KEY="certs/signing_key.pem"
、CONFIG_SYSTEM_TRUSTED_KEYS=""
使用
make
编译的时候会有提示,一路回车确定即可,输出如下:1 2 3 4 5 6 7 8 9 10 11 12
* * Certificates for signature checking * File name or PKCS#11 URI of module signing key (MODULE_SIG_KEY) [certs/signing_key.pem] (NEW) Provide system-wide ring of trusted keys (SYSTEM_TRUSTED_KEYRING) [Y/?] y Additional X.509 keys for default system keyring (SYSTEM_TRUSTED_KEYS) [] (NEW) Reserve area for inserting a certificate without recompiling (SYSTEM_EXTRA_CERTIFICATE) [Y/n/?] y Number of bytes to reserve for the extra certificate (SYSTEM_EXTRA_CERTIFICATE_SIZE) [4096] 4096 Provide a keyring to which extra trustable keys may be added (SECONDARY_TRUSTED_KEYRING) [Y/n/?] y # # configuration written to .config #
QEMU
前往https://download.qemu.org/下载指定版本的QEMU(此处以2.12.0为例)。因为LibVMI只针对少数几个版本的QEMU进行了VMI Patch,所以需要下载能够Patch的版本,后续才能正常进行。已经Patch的版本列表可以看LibVMI的仓库:libvmi/tools/qemu-kvm-patch at master · libvmi/libvmi (github.com)
|
|
安装完成后,QEMU相关的程序都被放在/usr/local/bin
目录下。可以使用下面的命令查看QEMU的版本:
|
|
LibVMI
可以从Github上面获得源码:libvmi/libvmi
LibVMI的前身是XenAccess,可以为Xen上运行的虚拟机系统Hypervisor层面的监控,也就是VMI。而如果要在KVM上使用LibVMI,官方给出了两个方案:
- 传统方案,通过给QEMU打补丁来实现额外的内存访问接口;
- KVMi方案,此方案为较新的方案,使用一个名为
kvmi
的KVM子系统提供的API来实现VMI,项目地址为KVM-VMI/kvm-vmi。
这里采用第一种方案(第二种以后有空再试)。由于是Legacy,导致现在官方对其维护积极性也不高,有一些Bug需要自行发现并修复。这里有两个已发现的Bug:
- https://github.com/libvmi/libvmi/issues/1002,由于编译路径少写了一个
/
导致读取不到Glib-2.0的库路径,最终make
编译失败。Issue下面给了一个补丁,patch一下即可:https://src.fedoraproject.org/rpms/libvmi/c/3d132c9990c0377fcf1b7c7faea159d4d5e9722a - 运行示例程序
vmi-module-list
的时候报invalid pointer
错误,Backtrace第一行是kvm_get_vcpureg
,这个是师兄发现的一个Bug,是寄存器结构体的free问题导致的。https://github.com/libvmi/libvmi/pull/1004
对相关文件进行patch之后就可以开始编译了:
|
|
所有相关的可执行文件默认都放在/usr/local/bin
目录下,可以通过在虚拟机里面编译执行tools/xxx-offset-finder
程序来获得对应系统的关键结构体偏移量,并将其写入libvmi的配置文件中/etc/libvmi.conf
(Linux系统还需要拷贝System.map文件到宿主机供LibVMI读取)。一切就绪后,可以运行vmi-process-list
和vmi-module-list
看看是否能够正确输出。
|
|