Bareflankを使ってみる
前から気になっていたBareflankを少し触って見ました.
あまりドキュメントがないようなので半分メモがてらBareflankについて書いてみます.
BareflankはいわゆるThin-hypervisorの一種です. 複数VMの動作を目的とする通常のハイパーバイザとは異なり,そのようなハイパーバイザは基本的に一つのゲストOSを実行し,ゲストOSのフックや解析をおこないます. セキュリティや研究目的で用いられることが多いです. そのようなハイパーバイザは(特に最近)結構あって,
などがあります.これらと比較してBareflankは"hypervisor Software Development Toolkit"を謳っており,特徴として
が挙げられます.
とりあえず動かす
公式のCompilation Instructionsを参考にすれば動くと思います. ただ最近も良く開発されており,masterブランチはunstableだったりすることがあるので場合によっては適当なタグをチェックアウトした方がいいかもしれません.
以下,Linuxで動かしたときの実行結果です.
% git clone https://github.com/bareflank/hypervisor.git % cd hypervisor % mkdir build; cd build % cmake .. % make -j8 % make driver_quick % make quick % make status vmm running Built target status % make dump Scanning dependencies of target dump [0] DEBUG: host os is now in a vm [1] DEBUG: host os is now in a vm [2] DEBUG: host os is now in a vm [3] DEBUG: host os is now in a vm Built target dump % dmesg ... [345609.788134] [BAREFLANK DEBUG]: dev_init succeeded [345613.817106] [BAREFLANK DEBUG]: dev_open succeeded [345613.822454] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE_LENGTH: succeeded [345613.822471] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE: succeeded [345613.822478] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE_LENGTH: succeeded [345613.822480] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE: succeeded [345613.822491] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE_LENGTH: succeeded [345613.822495] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE: succeeded [345613.822579] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE_LENGTH: succeeded [345613.822612] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE: succeeded [345613.822632] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE_LENGTH: succeeded [345613.822637] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE: succeeded [345613.823104] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE_LENGTH: succeeded [345613.823214] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE: succeeded [345613.823324] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE_LENGTH: succeeded [345613.823359] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE: succeeded [345613.823526] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE_LENGTH: succeeded [345613.823573] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE: succeeded [345613.823583] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE_LENGTH: succeeded [345613.823590] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE: succeeded [345613.823598] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE_LENGTH: succeeded [345613.823600] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE: succeeded [345613.823669] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE_LENGTH: succeeded [345613.823713] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE: succeeded [345613.823731] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE_LENGTH: succeeded [345613.823745] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE: succeeded [345613.823755] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE_LENGTH: succeeded [345613.823762] [BAREFLANK DEBUG]: IOCTL_ADD_MODULE: succeeded [345613.872731] [BAREFLANK DEBUG]: IOCTL_LOAD_VMM: succeeded [345613.872741] [BAREFLANK DEBUG]: dev_release succeeded [345613.876854] [BAREFLANK DEBUG]: dev_open succeeded [345613.911072] [BAREFLANK DEBUG]: IOCTL_START_VMM: succeeded [345613.911077] [BAREFLANK DEBUG]: dev_release succeeded [345616.927978] [BAREFLANK DEBUG]: dev_open succeeded [345616.932016] [BAREFLANK DEBUG]: dev_release succeeded
構成
Linuxで使用する場合について簡単に見てみます.
前述の通りBareflankはC++で開発されています. BareflankはOSの機能が使えないベアメタル上で動作するため,そのためのC++ランタイムが実装されています.
Bareflankのロード
Bareflankはカーネルモジュールをロードすることで起動します(カーネルモジュール自体は当然ながらCで書かれています).
このときBareflank本体はelfバイナリとしてコンパイルされており,カーネルモジュールはそのelfバイナリを動的にロードして呼び出すということをおこなっています.
このときの_start_func()
(Bareflankのエントリポイント)はbfvmm/src/entry/entry.cpp:bfmain()
になります.
仮想化の流れ
カーネルモジュールからBF_REQUEST_VMM_INIT
を受け取ったBareflankは自身を仮想化します.
処理の流れは高度に抽象化されていて分かりにくいですが,private_init_vmm()
のg_vcm->create()
によってbfvmm:vcpu
のインスタンスが作成され,g_vcm->run()
によりintel_x64::vcpu::run_delegate::run_delegate()
が実行されます.
ここで::intel_x64::vmcs::launch()
=> ::intel_x64::vm::launch_demote()
=> _vmlaunch_demote()
でVMLAUNCH
が以下のように実行されます.
global _vmlaunch_demote _vmlaunch_demote: call _vmlaunch_trampoline ret _vmlaunch_trampoline: pop rsi mov rdi, 0x0000681E ; VMCS_GUEST_RIP vmwrite rdi, rsi mov rdi, 0x0000681C ; VMCS_GUEST_RSP vmwrite rdi, rsp mov rax, 0x1 vmlaunch mov rax, 0x0 jmp rsi
_vmlaunch_trampoline
ではスタック上の関数の戻り値をVMCS_GUEST_RIP
に設定したのちVMLAUNCH
します.
これによりVMLAUNCH
が成功した場合はvmlaunch_demote
の呼び出し元から仮想化した状態で処理が再開されます.
なおVMCSの初期化はvcpu
のコンストラクタ内から呼ばれるintel_x64::vmx
のコンスタラクタでおこなっています.
Exit handler
VMCSのhost ripにはexit_handler_entry
が設定されています.
exit_handler_entry
は第一引数(rdi
)に[gs:0x00A0]
を設定してbfvmm::intel_x64::exit_handler::handle(bfvmm::intel_x64::exit_handler*)
を呼び出しますが,この[gs:0x00A0]
の値はexit_handler_ptr
経由で設定されています.
exit handlerはexit reason毎にリストで複数持てるようになっており,add_handler()
を利用してハンドラを登録します.
拡張
Bareflank/hypervisor
だけでは仮想化するだけでほとんど何もしません.
EPTも使っていません.
何か具体的なことをするにはハイパーバイザを拡張する必要があります.
普通であればここでハイパーバイザのコードを直接修正するわけですが,ここからが"Extensible hypervisor"の本領発揮といったところで,CMakeを駆使したビルドスクリプトによりBareflankは主要な関数(クラス)を別ファイルに記述してオーバライドできるようになっています.
拡張方法の概要はextension_instructions.md
に書いてあります.
Bareflank/hypervisor_example_cpuidcountにexit handlerを追加する例があるので,とりあえずこれを参考にするのが良いと思います.
hypervisor_example_cpuidcount
ではvcpu_factory::make()
をオーバライドし,bfvmm::intel_x64::vcpu
を継承して作成したvcpu
を返すようにしています.
vcpu
のコンストラクタの中でexit_handler()->add_handler
を呼び出すことでexit handlerを追加しています.
この例ではcpuid命令によりvmexitが発生する度にm_count
をインクリメントし,bareflankが終了時にm_count
の値を出力しています.
ちなみにデフォルトではシルアルポートのCOM0に出力されます.
EPT Hook
Bareflank/extended_apisにEPTハンドラ等のベースがあります. EPTを利用したい場合はこれを拡張するのが簡単だと思います.
Bareflank/extended_apis_example_hookにextended_apisを利用した拡張例があります.
所感
BareflankはModern C++で書かれたthin hypervisorとして(まだまだ活発に開発中ですが)一番の完成度ではないでしょうか. C++のランタイムがきちんと移植されていること,そしてあの(正直どうなってるか分かってない)ビルドシステムがすごいです. UEFIからType1 hypervisorとしても起動できるようなので後で試してみようと思います. まだ応用例は多くはないようですが,これからに期待です.
最後に,何かあればgitterで質問すれば多分答えてもらえると思います.