在 kvm 中实现自定义超级调用

Posted

技术标签:

【中文标题】在 kvm 中实现自定义超级调用【英文标题】:Implementing a custom hypercall in kvm 【发布时间】:2016-02-09 00:28:27 【问题描述】:

我对虚拟化非常陌生,最近我一直在尝试熟悉 VMM 的操作方式以及如何进行超级调用。

谈到我打算在安装在我的 Ubuntu 桌面上的 KVM 中实现一个新的超级调用,然后可以从来宾环境中调用。通过这个超级调用,我打算只返回一个字符串“Hello World”。在这一点上,我对如何实现它一无所知。如果你能指导我如何实现这样的超级调用,那将非常有帮助。谢谢!

【问题讨论】:

【参考方案1】:

下面的补丁实现了一个超级调用,它将trace_printk“Hello World”发送到主机的ftrace。 (基础代码是Linux 4.10。)

    (在guest内核源代码中)添加调用hypercall的系统调用:

    diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
    index e93ef0b..2ff3b3f 100644
    --- a/arch/x86/entry/syscalls/syscall_64.tbl
    +++ b/arch/x86/entry/syscalls/syscall_64.tbl
    @@ -338,6 +338,7 @@
     329    common  pkey_mprotect       sys_pkey_mprotect
     330    common  pkey_alloc      sys_pkey_alloc
     331    common  pkey_free       sys_pkey_free
    +332    common  hello_hypercall     sys_hello_hypercall

    diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
    index 91a740f..19208d5 100644
    --- a/include/linux/syscalls.h
    +++ b/include/linux/syscalls.h
    @@ -902,5 +902,6 @@ asmlinkage long sys_pkey_mprotect(unsigned long start, size_t len,
                      unsigned long prot, int pkey);
     asmlinkage long sys_pkey_alloc(unsigned long flags, unsigned long init_val);
     asmlinkage long sys_pkey_free(int pkey);
    +asmlinkage long sys_hello_hypercall(void);

     #endif

    diff --git a/hello_hypercall/hello_hypercall.h b/hello_hypercall/hello_hypercall.h
    new file mode 100644
    index 0000000..cc727ee8
    --- /dev/null
    +++ b/hello_hypercall/hello_hypercall.h
    @@ -0,0 +1 @@
    +asmlinkage long sys_hello_hypercall(void);

    diff --git a/hello_hypercall/Makefile b/hello_hypercall/Makefile
    new file mode 100644
    index 0000000..6247351
    --- /dev/null
    +++ b/hello_hypercall/Makefile
    @@ -0,0 +1 @@
    +obj-y:=hello_hypercall.o

    diff --git a/Makefile b/Makefile
    index f1e6a02..6a84315 100644
    --- a/Makefile
    +++ b/Makefile
    @@ -910,7 +910,7 @@ export mod_sign_cmd

     ifeq ($(KBUILD_EXTMOD),)
    -core-y     += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/
    +core-y     += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/ hello_hypercall/

     vmlinux-dirs   := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
                 $(core-y) $(core-m) $(drivers-y) $(drivers-m) \
    (在客户内核源代码中)实现系统调用来调用超级调用

    diff --git a/hello_hypercall/hello_hypercall.c b/hello_hypercall/hello_hypercall.c
    new file mode 100644
    index 0000000..aa333f8
    --- /dev/null
    +++ b/hello_hypercall/hello_hypercall.c
    @@ -0,0 +1,17 @@
    +#include<linux/kernel.h>
    +#include<linux/syscalls.h>
    +#include<linux/init.h>
    +#include<linux/linkage.h>
    +#include "hello_hypercall.h"
    +#include<uapi/linux/kvm_para.h>
    +#include<linux/cpumask.h>
    +
    +asmlinkage long sys_hello_hypercall(void)
    +
    +   kvm_hypercall0(KVM_HC_HELLO_HYPERCALL);
    +   return 0;
    +

kvm_hypercall0 使用零参数调用 X86_FEATURE_VMMCALL(其他 kvm_hypercall 函数最多处理四个参数)。数字KVM_HC_HELLO_HYPERCALL(在include/uapi/linux/kvm_para.h 中定义)作为超级调用号传入。在使用超级调用号和可能的参数调用X86_FEATURE_VMMCALL 后,执行将跳转到arch/x86/kvm/x86.c 中的主机内核函数kvm_emulate_hypercall

更多信息请参见https://elixir.bootlin.com/linux/latest/source/arch/x86/include/asm/kvm_para.h#L21。

    (在主机内核源代码中)定义超级调用号(include/uapi/linux/kvm_para.h)并打印“Hello World”作为超级调用代码(arch/x86/kvm/x86.c

    diff --git a/include/uapi/linux/kvm_para.h b/include/uapi/linux/kvm_para.h
    index bf6cd7d..67304a17 100644
    --- a/include/uapi/linux/kvm_para.h
    +++ b/include/uapi/linux/kvm_para.h
    @@ -23,6 +23,7 @@
     #define KVM_HC_MIPS_GET_CLOCK_FREQ 6
     #define KVM_HC_MIPS_EXIT_VM        7
     #define KVM_HC_MIPS_CONSOLE_OUTPUT 8
    +#define KVM_HC_HELLO_HYPERCALL     9

    diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
    index e52c908..b755ccf 100644
    --- a/arch/x86/kvm/x86.c
    +++ b/arch/x86/kvm/x86.c
    @@ -6151,6 +6209,9 @@ int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
            kvm_pv_kick_cpu_op(vcpu->kvm, a0, a1);
            ret = 0;
            break;
    +   case KVM_HC_HELLO_HYPERCALL:
    +       trace_printk("Hello World");
    +       break;
        default:
            ret = -KVM_ENOSYS;
            break;

KVM_HC_HELLO_HYPERCALL 存储超级调用的号码 9(请参阅here 了解现有的超级调用号码)。在arch/x86/kvm/x86.c中,在kvm_emulate_hypercall函数中,添加超级调用号匹配KVM_HC_HELLO_HYPERCALL的情况。

在来宾内核中调用超级调用以在主机的 ftrace 上查看其输出。

【讨论】:

【参考方案2】:

您可以在用户程序中使用 vmcall 指令在 KVM 中进行超级调用。您需要在 kvm 中为此 VMCALL 编写一个处理程序。如果您在来宾中运行代码;

#define VMCALL_ID 100
do_vmcall ()

   asm volatile ("vmcall" : "eax"(VMCALL_ID));

这将导致 KVM 中的陷阱。 kvm 会调用 handle_vmcall 函数。在handle_vmcall函数中需要写一个对应this的handler。

int handle_vmcall(struct kvm_vcpu *vcpu)

    unsigned eax = kvm_read_register(vcpu, VCPU_REGS_RAX);

    switch (eax) 
        case VMCALL_ID:
            BLAH; break;
        default: BLAH; BLAH;
    
    return 0;

【讨论】:

以上是关于在 kvm 中实现自定义超级调用的主要内容,如果未能解决你的问题,请参考以下文章

[转]Java中实现自定义的注解处理器

如何在Canvas中实现自定义路径动画

如何在Canvas中实现自定义路径动画

为啥不推荐使用 JScript 在 WiX 中实现自定义操作?

在具有条件的 keras 中实现自定义损失函数

在 Freemarker 中实现自定义 Escaper