Plan 9でVM床抜き

KVM環境を再整備したので、@go_vmさんのVM床抜きを試してみた。伝説のコミケ本「軽快なPlan 9」のあれである。ハイパーコールや/dev/kvmへのioctlを追加する方法の勉強にもなるので、Plan 9に興味なくても一読をお勧めする。

基本的に問題なく動いたのだけど、2点ほど修正の必要があった。一つ目は、KVMのハイパーコール処理部でゲストのCPL(Current Privilege Level)をチェックしている箇所の回避である。KVM88あたりではチェックなどなかったのだが、最近はCPLが0、つまりゲストが特権モードで動作している場合のみハイパーコールを受け付けるように変更されているのだ。「軽快なPlan 9」ではPlan 9のユーザプロセスがハイパーコールを発行するので、CPLが3で弾かれてしまう。まずはこのチェックをコメントアウトした。

kvm-kmod/include/arch/x86/kvm/x86.c

+ #if 0
+ 	/*
+ 	 * Ignore CPL checking for a hypercall from user processes
+ 	 * on the gues OS.
+ 	 */
  	if (kvm_x86_ops->get_cpl(vcpu) != 0) {
  		ret = -KVM_EPERM;
  		goto out;
  	}
+ #endif

二つ目はゲストとホストにおけるポインタサイズの違いの吸収である。ソケット関数への引数を構造体でゲストからホストに渡しており、そこにはポインタが含まれている。私の環境では、Plan 9は32ビットモードで動作しているが、ホストOSはx86_64の64ビットモードで動作しているので、両者でポインタサイズが違ってしまう。オリジナルコードはその辺を考慮してなかったようなので、ポインタを格納するフィールドを64bitに統一した。

qemu-kvm/hypercall.c

+static int hcall_send(CPUState *env, gva_t garg, int len)
+{
+    struct {
+      int32_t fd;
+      uintptr_t __attribute__((aligned(4))) buf;
+      int32_t len;
+      int32_t flags;
+    } __attribute__((packed)) harg;
+    void *buf;
+    int ret;
+
+    if (sizeof(harg) != len)
+	return -EINVAL;
+    if (copy_from_guest(env, &harg, garg, len))
+	return -EFAULT;
+
+    buf = malloc(harg.len);
+    if(!buf)
+	return -ENOMEM;
+    if (copy_from_guest(env, buf, (gva_t)harg.buf, harg.len)) {
+	free(buf);
+	return -EFAULT;
+    }
+
+    ret = send(harg.fd, buf, harg.len, harg.flags);
+    if (ret < 0)
+	ret = -errno;
+    free(buf);
+    return ret;
+}

これに対応するPlan 9側のライブラリ関数は次のようになる。

int hsend(int fd,void *buf,int len,int flags)
{
    struct {int fd; uvlong buf; int len; int flg;} arg;

    arg.fd = fd;
    arg.buf = (uvlong)(uintptr)buf;
    arg.len = len;
    arg.flg = flags;

    return vmcall(2, (int)&arg, sizeof(arg));
}

とりあえずここまでの成果は、githubcommitした。