欢迎来到 黑吧安全网 聚焦网络安全前沿资讯,精华内容,交流技术心得!

探索QEMU-KVM中PIO处理的奥秘

来源:本站整理 作者:佚名 时间:2017-07-11 TAG: 我要投稿

我们都知道在kvm/qemu的虚拟机中向端口读写输入会陷入kvm中(绝大部分端口)。但是其具体过程是怎么样的,虚拟机、kvm和qemu这三者的关系在这个过程中又是如何相互联系来完成这一模拟过程的。本文就是对这一问题的探索,通过对kvm进行调试来了解其中的奥秘。
零.  准备工作
工欲善其事,必先利其器。为了了解kvm如何对PIO进行截获处理,首先需要调试kvm,这需要 配置双机调试环境,网上很多例子,需要注意的是,4.x内核清除kernel text的可写保护有点问题。 所以本文还是用的3.x内核,具体为3.10.105。所以我们的环境是target为3.10.105的内核,debugger随意。
如果我们直接用kvm/qemu调试,由于一个完整的环境会有非常多的vm exit,会干扰我们的分析。 这里我们只需要建立一个使用kvm api建立起一个最简易虚拟机的例子,在虚拟机中执行in/out 指令即可。网上也有很多这种例子。比如使用KVM API实现Emulator Demo, Linux KVM as a Learning Tool.
这里我们使用第一个例子,首先从
https://github.com/soulxu/kvmsample  
把代码clone下来,直接make,如果加载了kvm应该就可以看到输出了,kvm的api用法这里不表,仔细看看 前两篇文章之一就可以了,qemu虽然复杂,本质上也是这样运行的。这个例子中的guest是向端口输出数据。
一.  IO端口在KVM中的注册
首先我们需要明确的一点是,IO port 这个东西是CPU用来与外设进行数据交互的,也不是所有CPU都有。 在虚拟机看来是没有IO port这个概念的,所以是一定要在vm exit中捕获的。
对于是否截获IO指令,是由vmcs中的VM-Execution controls中的两个域决定的。 参考intel SDM 24.6.2:

我们可以看到如果设置了Use I/O bitmpas这一位,Unconditional I/O exiting就无效了,如果在IO bitmap 中某一位被设置为1,则访问该端口就会发生vm exit,否则客户机可以直接访问。 IO bitmap的地址存在vmcs中的I/O-Bitmap Addresses域中,事实上,有两个IO bitmap,我们叫做A和B。 再来看看SDM

每一个bitmap包含4kb,也就是一个页,bitmap A包含了端口0000H到7FFFFH(4*1024*8),第二个端口包含了8000H到 FFFFH。
好了,我们已经从理论上对IO port有了了解了,下面看看kvm中的代码。
首先我们看到arch/x86/kvm/vmx.c中,定义了两个全局变量表示bitmap A和B的地址。 在vmx_init函数中这两个指针都被分配了一个页大小的空间,之后所有位都置1,然后在bitmap A中对第 80位进行了清零,也就是客户机访
这个0x80端口不会发生vm exit。
static unsigned long *vmx_io_bitmap_a;
static unsigned long *vmx_io_bitmap_b;
static int __init vmx_init(void)
{
    vmx_io_bitmap_a = (unsigned long *)__get_free_page(GFP_KERNEL);
    vmx_io_bitmap_b = (unsigned long *)__get_free_page(GFP_KERNEL);
    
   
    /*
    * Allow direct access to the PC debug port (it is often used for I/O
    * delays, but the vmexits simply slow things down).
    */
    memset(vmx_io_bitmap_a, 0xff, PAGE_SIZE);
    clear_bit(0x80, vmx_io_bitmap_a);
    memset(vmx_io_bitmap_b, 0xff, PAGE_SIZE);
    ...
}
在同一个文件中,我们看到在对vcpu进行初始化的时候会把这个bitmap A和B的地址写入到vmcs中去,这样 就建立了对IO port的访问的截获。
static int vmx_vcpu_setup(struct vcpu_vmx *vmx)
{
    /* I/O */
    vmcs_write64(IO_BITMAP_A, __pa(vmx_io_bitmap_a));
    vmcs_write64(IO_BITMAP_B, __pa(vmx_io_bitmap_b));
    return 0;
}
二.  PIO中out的处理流程
本节我们来探讨一下kvm中out指令的处理流程。首先,将上一节中的test.S代码改一下,只out一次。
.globl _start
    .code16
_start:
    xorw %ax, %ax
    mov  $0x0a,%al
    out %ax, $0x10
    inc %ax
    hlt
kvm中guest发送vm exit之后会根据发送exit的原因调用各种handler。这也在vmx.c中
static int (*const kvm_vmx_exit_handlers[])(struct kvm_vcpu *vcpu) = {
    [EXIT_REASON_EXCEPTION_NMI]           = handle_exception,
    [EXIT_REASON_EXTERNAL_INTERRUPT]      = handle_external_interrupt,
    [EXIT_REASON_TRIPLE_FAULT]            = handle_triple_fault,
    [EXIT_REASON_NMI_WINDOW]          = handle_nmi_window,
    [EXIT_REASON_IO_INSTRUCTION]          = handle_io,
    ...
}
对应这里,处理IO的回调是handle_io。我们在target中执行:

root@ubuntu:/home/test# echo g >/proc/sysrq-trigger
这样调试机中的gdb会断下来,给handle_io下个断点:

[1] [2] [3] [4] [5] [6] [7] [8]  下一页

【声明】:黑吧安全网(http://www.myhack58.com)登载此文出于传递更多信息之目的,并不代表本站赞同其观点和对其真实性负责,仅适于网络安全技术爱好者学习研究使用,学习中请遵循国家相关法律法规。如有问题请联系我们,联系邮箱admin@myhack58.com,我们会在最短的时间内进行处理。
  • 最新更新
    • 相关阅读
      • 本类热门
        • 最近下载