OSがどんなハイパーバイザー/コンテナ上で動いているか確認するツールのコードを読んだ

githubのTrendingと見ていてOSがどんなハイパーバイバー上で動いているか確認できるツールslabbed-or-notがあったので見てみた。

README.mdによると現状対応しているハイパーバイザーはXenVMwareHyper-VKVM、bhyveに対応している。コンテナ型のOpenVZにも対応しているけど、LXCはまだ未対応だけど作者さん対応したいと思っているようなのでそのうち対応されるかも。 そして、一番興味があるのはどうやって判別しているのかというところなのでそこを見てみる。 判別はまず大きく2種類でハイパーバイザー型かコンテナ型かの判定をする。

コードだと下のような感じでチェック用の関数ポインタ等を登録した構造体を登録する。

struct test_impl *hv_test_impls[] = {
    &xen_impl,
    &vmware_impl,
    &hyperv_impl,
    &kvm_impl,
    &bhyve_impl,
};

struct test_impl *ctr_test_impls[] = {
    &openvz_impl,
};

そしてctr_test_impls -> hv_test_implsという順番でチェックしている。

最初のチェックはコンテナで今はOpenVZだけなのでそこを見ると、チェック方法はaccess(2)で/proc/vz/vzaquotaもしくは/proc/user_beancountersにアクセスして返り値が0ならOpenVZ環境と判断している。

次にハイパーバイザー型。ここは3種類のチェック方法があってXenVMWare、その他という感じに分けられる。 まずはXenの場合。Xenは完全仮想化と準仮想化の2種類があるため別になっている。最初は完全仮想化かどうかのチェックでこれはcpuid命令を使ってXenVMMXenVMMというシグネチャが見つかればXenの完全仮想化モードと判定。

cpuidを実行しているのはxen-detect.cにあるこの関数。この関数はxen_check_inner()から呼ばれる。

static void xen_cpuid(uint32_t idx,
                  uint32_t *eax,
                  uint32_t *ebx,
                  uint32_t *ecx,
                  uint32_t *edx)
{
    asm volatile (
            "test %1,%1 ; jz 1f ; ud2a ; .ascii \"xen\" ; 1: cpuid"
            : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx)
            : "0" (idx), "1" (xen_pv_context) );
}

この関数はxenという名前が付いているけどKVMとかの判定に使うcpuid-detect.cにあるcpuid_scan()からも呼ばれている。。 上のチェックで完全仮想化と判定しなかった場合は準仮想化のチェックを以下の手順でする。

  • setjmpで現在のコンテキストを保存
  • sigaction()でSIGILLをキャッチするように設定。sigaction()が失敗したらXenではないと判定
  • 再度xen_check_inner()でcpuidをチェック
  • SIGILLのシグナルハンドラを元に戻す

Xenxen_cpuid()の実行時にeaxにXenのバージョン取れるようなのでハイパーバイザー名の表示と一緒にバージョンも表示される。

次にVMWareの場合。 これも最初はcpuid命令で判定を試す。シグネチャVMwareVMwareが見つかればVMwareと判定できるが、違った場合はVMWareバックドアポート(0x5658)にアクセスできるかどうかで判定する。

#define VMWARE_PORT(cmd, eax, ebx, ecx, edx)                            \
        __asm__("inl (%%dx)" :                                          \
                        "=a"(eax), "=c"(ecx), "=d"(edx), "=b"(ebx) :    \
                        "0"(VMWARE_HYPERVISOR_MAGIC),                   \
                        "1"(VMWARE_PORT_CMD_##cmd),                     \
                        "2"(VMWARE_HYPERVISOR_PORT), "3"(ebx) :    \
                        "memory");

残りはHyper-VKVM、bhyveでこれらはcpuid命令のみで判定。 Hyper-VシグネチャMicrosoft HvKVMKVMKVMKVM、bhyveはbhyve bhyve となっている。

実際に動かした時の表示例はREADME.mdのusageのところを見ると良いかも。これはコンテナにOpenVZ、ハイパーバイザーにXenという複合環境の表示をしている。

このslabbed-or-notは面白かったのでAURにパッケージ登録しておいたのでArch Linuxの人はyaourtとかでインストールできます。 AURのパッケージページはここ