はじめに
この記事では,BitVisor の保護ドメインのインターフェイスについて解説したいと思います.
保護ドメインとは?
BitVisor には保護ドメインという機能があります.
BitVisor のソースコード内で,"process"という呼称で扱われているものです.
保護ドメインはRoot mode の Ring 3 でプログラムを実行する機能です.
保護ドメインは複数作ることができ,また各保護ドメイン間および BitVisor のカーネル(Ring 0 で動作する部分)はメモリ空間が隔離されています.
このため,保護ドメインはゲストOS やほかのプログラムから隔離され,また,BitVisor 本体への影響も抑制される実行環境といえます.
dbgsh などの,BitVisor のメインラインに入っている機能も一部は保護ドメイン内で動作しています.
参考資料
(2016/12/27 追記: 資料のタイトル,2つ目の資料)
Customizing BitVisor 1.3
https://www.bitvisor.org/summit/slides/BitVisor-Summit-04-matsubara.pdf
初回の BitVisor Summit で発表された松原さんのスライドに保護ドメインの使い方が少し書かれています.
macでdbgsh / dbgshに機能を追加
http://qiita.com/mmi/items/561ff1ed5923e6a0f1a0
mmi さんが書いた BitVisor Advent Calendar 13日目の記事です.
記事の後半に,dbgsh から呼び出す保護ドメインの作り方や,dbgsh からメッセージを直接投げる方法などが書いています.
メッセージインターフェイス
保護ドメインのインターフェイスはメッセージインターフェイスと呼ばれるものになっています.
これは,ディスクリプタとメッセージを使って通信(メッセージング)するものです.
メッセージングはディスクリプタと送りメッセージを指定して行います.
また,各メッセージングはメッセージを転送するだけではなく,戻り値を受け取ることができます.
つまり,一度のメッセージングで双方向にデータをやり取りすることもできます.
メッセージの用途
主に以下の2つです.
- カーネルや保護ドメインが新たな保護ドメインでプログラムを起動する
- 保護ドメイン内のプログラムから起動元ドメインの機能を呼び出す
図にすると以下のようになります.上側の矢印が1.に, 下側の矢印が2. になります.
2.のメッセージは,例えば画面出力のためにBitVisor カーネルの関数を呼び出したい,という風な時に使います.
ただし,起動元で登録されている関数しか呼び出すことができず,なんでも呼び出せるわけではありません(なんでも呼び出せてしまうと隔離する意味がないですしね).
イメージとしては,システムコール呼び出しに近いかと思います.
また,保護ドメインから新たな保護ドメインを起動することもできます.
これは,dbgsh で実際に行われています.
dbgsh は受け取ったコマンドに応じて,その機能を実現するプログラムを別の保護ドメインで起動しています.
例えば,dbgsh を起動して,そこで log コマンドを打つと,カーネル-->dbgsh-->log という風に起動されます.
また,log の出力は直接カーネルにメッセージを飛ばすのではなく,dbgsh が仲介しています.
つまり,log の出力 カーネル <-- dbgsh <-- log という風に渡されます.
メッセージ経路とディスクリプタ
メッセージ経路とは,メッセージを送るための経路です.
そして,各経路にはディスクリプタと呼ばれる値が割り当てられます.
ディスクリプタは,カーネル,および各ドメインで別々に管理しています.
つまり,カーネル内のディスクリプタ1と保護ドメイン1内のディスクリプタ1は指すものが違います.
図で書くと,以下のようになるでしょうか.
メッセージングの実体
ここまで,メッセージング,などと抽象的な表現をしてきましたが,その実体は,引数を渡して他のドメインの関数をキックするというものです.
といっても,先ほども書いたように,直接なんでも関数が呼べると困るので,関数を直接指定するのではなく,ディスクリプタを指定するようになっています.
保護ドメインを起動する前に,ディスクリプタと呼び出される関数(メッセージハンドラ)を紐づけています.
以後で,メッセージ経路とディスクリプタの設定,およびメッセージングの方法を解説します.
保護ドメイン起動用メッセージ経路の作成
- newprocess () 関数を呼び出します.
- 引数に渡す文字列はプロセスを指定するものです.process/*.bin.s 的なファイルがあるものは指定できるんじゃないかと思います.
- プロセスの一覧は,ビルドするときにいろいろゴニョニョやって特定のアドレスにテーブルが作られていた気がします.
- 戻り値はプロセスディスクリプタです.プロセスをキックするときに使います.
保護ドメインに起動元の機能を呼び出すメッセージ経路の作成
- msgopen () 関数で通信用のディスクリプタを生成します.
- この時指定する文字列は,メッセージハンドラとディスクリプタを紐づける際に使います.
- また,この時の戻り値(メッセージディスクリプタ)はメッセージ経路を保護ドメインに登録するときに使います.
- msgregister () でメッセージハンドラとディスクリプタを紐づけます
- 第1引数に msgopen () で指定した文字列を,第2引数にハンドラの関数ポインタを指定します.
- msgsenddesc () でプロセスにメッセージハンドラを紐づけます
- 第1引数に保護ドメインのプロセスディスクリプタ,第2引数にメッセージディスクリプタを指定します.
- 保護ドメインからはこの時登録した順に,0, 1, 2... とディスクリプタで参照できます.
保護ドメインを起動する
- プロセスディスクリプタに対してメッセージを送ります.
- msgsendint () 関数で,プロセスディスクリプタと引数となる値を指定します.
- msgsendbuf () という関数もあり,こちらは引数としてバッファのポインタを渡します
保護ドメインから起動元の機能を呼び出す
- これも,保護ドメインを起動するときと同様に,ディスクリプタに対してメッセージを送ります
- msgsendint() もしくは msgsendbuf () を呼び出します.
例: dbgsh
まずはコードを
static void
dbgsh_thread (void *arg)
{
int shell, ttyin, ttyout;
msgregister ("dbgsh_ttyin", dbgsh_ttyin_msghandler);
msgregister ("dbgsh_ttyout", dbgsh_ttyout_msghandler);
debug_msgregister ();
ttyin = msgopen ("dbgsh_ttyin");
ttyout = msgopen ("dbgsh_ttyout");
for (;;) {
shell = newprocess ("shell");
if (ttyin < 0 || ttyout < 0 || shell < 0)
panic ("dbgsh_thread");
msgsenddesc (shell, ttyin);
msgsenddesc (shell, ttyout);
msgsendint (shell, 0);
msgclose (shell);
dbgsh_send_to_guest (0x100 | '\n');
schedule ();
}
}
- 最初に2つのメッセージハンドラを"dbgsh_ttyin", "dbgsh_ttyout" と紐づけます.
- msgopen () で,メッセージハンドラに対応するメッセージディスクリプタを生成します.
- newprocess () で "shell" のプロセスディスクリプタを生成します.
- msgsenddesc() で "shell" プロセスディスクリプタに ttyin と ttyout のメッセージディスクリプタを登録します.
- msgsendint () で "shell" を起動します.
では,起動される側の方を見てみましょう
int
_start (int a1, int a2)
{
char buf[100];
getlog ();
logoff = 0;
getnextstate ();
for (;;) {
printf ("debug> ");
lineinput (buf, 100);
switch (buf[0]) {
case 'h':
if (buf[1] != '\0' && buf[1] != 'e') {
hexaddsub (buf);
break;
}
/* fall through */
case '?':
print_help (buf);
break;
case 'd':
dump_mem (buf, 0);
break;
case 'D':
dump_mem (buf, 1);
break;
case 'q':
exitprocess (0);
break;
case 'r':
guestreg (buf);
break;
case 'n':
getnextstate ();
break;
case '!':
callprog (buf);
break;
default:
command_error ();
break;
}
}
}
保護ドメインを起動する時は,最初は _start()
が呼び出されます.
引数が2つありますが,1つ目はメッセージのタイプです.
msgsendint() か msgsendbuf() かの情報が入っています.
2つ目は,msgsend*() から渡されるものです.
ただ,ここでは,msgsend*() からの引数などは使わず,入力をlineinput ()で,カーネルの機能を呼び出して,キーボードからの入力を受け取っています.
おわりに
途中で力尽きた感じになって雑になりましたが,保護ドメインのインターフェイスについて解説してみました.
そのうち追記してちゃんとした解説にしたいですね.