[BitVisor-devel:32] BitVisor RPC patch

Shougo Matsushita shougo @ softlab.cs.tsukuba.ac.jp
2012年 2月 29日 (水) 16:22:57 JST


BitVisorメーリングリストの皆さん

筑波大学 ソフトウェア研究室の松下正吾です。

このパッチはBitVisorにRPC機能を追加します。別のパッチである動的プロセス生成機能
と組み合わせると、動的に読み込んだプロセスとゲストOS上のプログラムが通信するこ
とが可能となります。BitVisorの応用範囲が広がることが期待されます。


(1) APIの説明

今回追加したRPCの仕様は以下の通りとなります。
BitVisorのソースコードでは、include/rpc.hにあります。

enum rpc_direction {
        RPC_DIRECTION_REQUEST,
        RPC_DIRECTION_RESULT
};

struct rpc_request {
        ulong id;
        enum rpc_direction direction;
        ulong  proc_number;
        ulong  len;
        char  args[0];
};

struct rpc_result {
        ulong id;
        enum rpc_direction direction;
        ulong  status;
        ulong  len;
        char  results[0];
};

int vmmcall_rpc(int desc, struct rpc_request *request, struct
rpc_result *result)

descはプロセスのプログラムを識別する番号です。RPCの要求はrequestで与え、RPCの応
答を受けとるための領域をresultで指定します。struct rpc_requestにおける
proc_numberは呼び出す手続きの番号です。lenにはヘッダとデータを含んだ構造体の大
きさを与えます。可変長となっているargsとresultsには外部で定義した引数と戻り値を
与えます。これはAPI毎の構造体として定義されており、例えば足し算を行うRPCである
rpc_addの場合は下の図のようになります。

struct rpc_add_arg {
        int a;
        int b;
};

struct rpc_add_result {
        int c;
};

struct rpc_resultにおけるstatusは、RPCのステータスコードです。vmmcall_rpcの戻り
値とstatusを分離することで、RPCのエラーと呼び出したAPIのエラーを区別することが
できます。RPCのエラーはvmmcall_rpcの戻り値で表し、APIのエラーはstatusで判別しま
す。よって、RPCの呼び出し側では両方の値をチェックしなければなりません。idは複数
のゲストOSのプログラムからRPCが実行されたとき、それぞれを区別する際に用います。
directionは方向を表し、通信の方向を区別する際に用います。


(2) APIの利用例(ゲストOS側クライアント)

ゲストOSで用いるvmmcallのスタブ関数を以下に示します。スタブ関数のインタフェース
はvmmcallの引数で示したものと同じです。ただし、vmmcallを呼ぶときに、引数を一つ
の構造体でまとめます。

struct rpc_arg {
        int desc;
        struct rpc_request *request;
        struct rpc_result *result;
};

int vmmcall_rpc(int desc,
                struct rpc_request *request,
                struct rpc_result *result)
{
        int r;
        volatile struct rpc_arg arg;

        /* Set rpc_arg. */
        arg.desc = desc;
        arg.request = request;
        arg.result = result;

        asm volatile ("vmcall" : "=a" (r) : "a" (vmmcall_rpc_num), "b" (&arg) :
                        "memory");
        return r;
}


ゲストOS環境で動作するRPCのクライアント側のコードのvmmcall_rpcハイパバイザコー
ルを用いた例を次に示します。完全なソースコードは
guest/rpc-samples-add/add-client.cにあります。

--------------------------------------------------------------------------------------

static int add(int desc, int a /* in */, int b /* in */)
{
        struct rpc_request *request;
        struct rpc_result *result;
        struct rpc_add_arg *arg;
        struct rpc_add_result *res;
        int r;

        request = malloc(sizeof(struct rpc_request)+ sizeof(*arg));
        request->proc_number = PROC_ADD;
        request->len = sizeof(struct rpc_request)+ sizeof(*arg);

        arg = &request->args[0];
        arg->a = a;
        arg->b = b;

        result = malloc(sizeof(struct rpc_result)+ sizeof(*res));
        result->status = 0;
        result->len = sizeof(sizeof(struct rpc_result)+ sizeof(*res));

        r = vmmcall_rpc(desc, request, result);

        free(request);

        if (r < 0) {
                fprintf(stderr, "vmmcall rpc failed(%d).\n", r);
                c = -1;
                goto error;
        }

        if (result->status) {
                fprintf(stderr, "vmmcall rpc procedure failed(%d).\n", r);
                c = -1;
                goto error;
        }

        res = &result->results[0];
        c = res->c;
error:
        free(result);

        return c;
}

--------------------------------------------------------------------------------------

これはプロセスへのRPCを用いて足し算を行うプログラムです。引数descはRPCを行うプ
ロセスを指定する番号です。普通、動的プロセスロード機構の戻り値を保存しておいて
指定します。この関数はまず、vmmcall_rpcハイパバイザコールのための引数領域を確保
します。この例ではADDは2つの引数aとbです。引数の型はstruct rpc_add_argと
rpc_add_resultです。その後、引数の中身をセットします。vmmcall_rpcハイパバイザコ
ールのための戻り値の領域を確保します。vmmcall_rpcハイパバイザコールを呼び出し、
結果を関数の戻り値とします。RPCの戻り値はvmmcall_rpcの戻り値とresult->statusの
両方をチェックしなければなりません。

上記の完全なソースコードは、sample/guest/rpc-add-server/add-client.cにあります。
次のコマンドによりバイナリを生成することができます。

$ make

(3) APIの利用例(BitVisor側サーバ)

プロセスで動作するRPCのサーバ側の例を次に示します。

--------------------------------------------------------------------------------------

#include <rpc.h>

struct msgbuf {
        void *base;
        int len;
        int rw;
};

int _start (int msgcode, int proc_number, struct msgbuf *buf, int bufcnt)
{
        if (msgcode != MSG_BUF) {
                return -1;
        }

        return svc(buf, bufcnt);
}

int svc(struct msgbuf *buf, int bufcnt)
{
        struct rpc_request *request;
        struct rpc_result *result;
        int ret;

        /* Args check. */
        if (bufcnt < 2
                || buf[0].len < sizeof(*request) || buf[0].rw
                || buf[1].len < sizeof(*result) || !buf[1].rw) {
                return -1;
        }

        request = (struct rpc_request *)buf[0].base;
        result = (struct rpc_result *)buf[1].base;

        switch (request->proc_number) {
        case PROC_ADD:
                ret = svc_add(request, result);
        default:
                ret = -1;
        }

        return ret;
}

int svc_add(struct rpc_request *request,
        struct rpc_result *result) {
        struct rpc_add_arg *arg;
        struct rpc_add_result *res;

        /* Args check. */
        if (request->len < sizeof(*request)+sizeof(*arg)
        || result->len < sizeof(*result)+sizeof(*res)) {
                result->status = -1;
                return -1;
        }

        arg = (struct rpc_add_arg *)(request->args);
        res = (struct rpc_add_result *)(result->results);

        res->c = arg->a + arg->b;
        result->len = sizeof(*result)+sizeof(*res);
        return 0;
}

--------------------------------------------------------------------------------------

vmmcall_rpcハイパバイザコールが呼び出されると、BitVisorにより_start()関数が呼ば
れます。_start()関数の引数はどのような呼び出し方で呼び出されたかを表すmsgcode、
手続きの番号を表すproc_number、RPCの引数と戻り値が格納されるbuf、bufの数を表す
bufcntからなります。struct msgbufとはBitVisor内で標準的に使用される構造体です。
_start()関数ではまず呼び出し方法をチェックします。msgcodeがMSG_BUFの場合は、プ
ロセスのプログラムへのRPCを意味します。svc()関数はまず引数のチェックを行います
。引数のrwメンバをチェックすることで、引数のバッファは読み取り専用になっている
か、戻り値のバッファは書き込めるかをチェックします。不正な引数でなかった場合、
引数を取り出します。switch文ではproc_numberを見て処理を振り分けます。
proc_numberがPROC_ADDであった場合は、svc_add()関数を呼び出します。svc_add()関数
では手続き毎の引数チェックを行います。そして引数を取り出し、結果と戻り値の長さ
を戻り値のバッファに代入します。この変更した戻り値の領域は、RPCが終了するときに
vmmcall_rpcハイパバイザコール側がゲストOS側に変更を反映させます。

完全なソースコードはsamples/rpc/bin_add.cにあります。
次のmakeコマンドによりバイナリを生成、署名の生成、BitVisorへのロード
をすることができます。この動的ロードをするためには、別のパッチで実装されている
vmmcall_newprocess機能が必要です。

$ make
$ make sign
$ make load
-------------- next part --------------
テキスト形式以外の添付ファイルを保管しました...
ファイル名: bitvisor-rpc.patch
型:         text/x-patch
サイズ:     16394 バイト
説明:       無し
URL:        <http://www.bitvisor.org/archives/bitvisor-devel/attachments/20120229/69c8c7bf/attachment.bin>


BitVisor-devel メーリングリストの案内