C/C++プログラマのためのDTrace入門

第2回 関数引数/戻り値の採取

この記事を読むのに必要な時間:およそ 5 分

引数文字列の採取

文字列表示に関する制限

関数呼び出しにおける引数値表示の次は,文字列引数の内容を表示してみましょう。

show_args コマンドは,main()関数のargv[0]引数,つまり自身の名前を使って以下の関数shownameを呼び出すものと仮定します。

リスト6 採取対象関数showname

static void
showname(const char* name)
{
    .....
}

先ほどの例ですでに,"%s" フォーマットとprobefunc組み込み変数を用いたprintf() による文字列表示を行いましたので,同じ要領でshowname()関数呼び出しにおける文字列引数を表示してみましょう。

リスト7 文字列引数採取Dスクリプト~その1

pid$target:show_args:showname:entry
{
    printf("(%s)\n", arg0);
}

しかし,このDスクリプトでdtraceコマンドを実行してみると……

図2 文字列引数の採取~その1

$ dtrace -s watch_arg_val.d \
         -q \
         -c './show_args'
dtrace: failed to compile script watch_args_str_bad.d: line 3: \
    printf( ) argument #2 is incompatible with conversion #1 prototype:
        conversion: %s
         prototype: char [] or string (or use stringof)
          argument: int64_t
$

何やらエラーが表示されてしまいました。

まず第1の問題は,関数引数に関する型情報が無いことから,各引数がデフォルトでは整数型(この実行例では int64_tとして扱われるため,フォーマット"%s"が期待する型と一致していない点にあります。

もうひとつの問題は,単に文字列領域のアドレスを指定しただけでは,文字列の「安全性」を担保することができない点にあります。

実はDTraceは,指定されたDスクリプトをカーネル空間で実行しています。そのため,普通のアプリケーションのように「変な動作をしてしまったら,いったん止めて再実行」というわけにはいきません。実行時の安全性が確保できない仕組みでは,稼働中のシステムに対しては怖くて適用できないのです。

「文字列」のようなデータ形式は:

  • 妥当な長さであるか不明
  • 本当に "\0" で終端しているか不明
  • 当該メモリ領域が使用可能であるか不明

といった点で「安全」とは言えないため,"char*"のようなアドレス値からprintf("%s") による出力が直接は実施できないように,DTraceがガードをかけているのです。

先の例で使用した probefuncprintf("%s")で表示できたのは,組み込み変数である probefunc の値が「安全な文字列」であることを保証されているためです。

stringof サブルーチン(※3)は,参照先領域に格納されている文字列に対して安全性の確認を行い,⁠安全な文字列」を意味するstring型オブジェクトを返却します。

リスト8 文字列引数採取Dスクリプト~その2

pid$target:show_args:showname:entry
{
    printf("(%s)\n", stringof(arg0));
}

それではエラーメッセージの指示に従い,stringofを使ったDスクリプトを実行してみると……

図3 文字列引数の採取~その2

$ dtrace -s watch_arg_val.d \
         -q \
         -c './show_args'
dtrace: error on enabled probe ID 1 \
    (ID 60308: pid11310:show_args:showname:entry): \
    invalid address (0x8047d40) in action #1
$

またもやエラーが出てしまいました(色違い部分は環境等に応じて変化しますので,違う値でも気にしないでください⁠⁠。

実はこのエラーも,DTrace の実際の処理がカーネル空間で実行されていることに関係があります。

表示しようとしている文字列の格納先arg0の値)は,ユーザプロセス中のアドレス(いわゆるユーザ空間のアドレス)ですから,そのアドレス値を元に処理を実施しようとしても,カーネル空間における当該アドレス値はそもそも無効か,あるいは全然関係の無いデータが格納されている領域かのどちらかです。そのため,上記のようなエラー("invalid address")となってしまうわけです。

※3)
DTrace では,採取データを外部に出力する機能を「アクション⁠⁠,スクリプト実行に閉じた機能を「サブルーチン」と呼び分けていますが,あまり気にする必要はないでしょう。

文字列引数の表示

前述したように,ユーザプロセスの文字列引数の内容を表示することはできないのでしょうか?

安心してください。以下のようなDスクリプトにより,文字列引数の内容を表示することができます。

リスト 9 文字列引数採取Dスクリプト~その3

pid$target:show_args:showname:entry
{
    printf("show_args(%s)", copyinstr(arg0));
}

copyinstr サブルーチンは,以下のように振る舞います。

  1. 文字列データを,ユーザ空間からカーネル空間に複製(有限長)
  2. string(安全な文字列)型オブジェクトに変換

上記のDスクリプトを実行してみると……

図4 文字列引数の採取~その3

$ dtrace -s watch_arg_val.d \
         -q \
         -c './show_args'
showname(./show_args) ← 採取結果
$

今度は無事に文字列引数の内容を採取することができました。

もしも,
(1) 固定長領域に格納されていて,
(2) 必ずしも "\0" 終端していない文字列を扱う場合は,
以下の方法で文字列表示が可能です。

リスト10 固定長文字列引数採取

pid$target:show_args:showname:entry
{
    printf("show_args(%s)", stringof(copyin(arg0), 64));
}

上記のDスクリプトでは,copyinによりユーザ空間からカーネル空間に64バイト分のデータが転送され,stringofにより当該データが「安全な文字列」であるstring型へと変換されます(必要であれば'\0'の付与も行われます⁠⁠。

著者プロフィール

藤原克則(ふじわらかつのり)

Mercurial三昧の日々が嵩じて, いつの間にやら『入門Mercurial Linux/Windows対応』を上梓。凝り性なのが災いして,年がら年中アップアップな一介の実装屋。最近は仕事の縁が元で,OpenSolarisに入れ込む毎日。