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

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

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

メモリ内容の採取

先の採取例では,ポインタ引数が参照する文字列を表示しましたが,実際のプログラムでは,文字列以外の入出力データ=メモリ内容を確認したい,というニーズもあることでしょう。

そこで,データ領域を参照する引数を持つ以下の様な関数checksumを想定します。なお,この関数の実装自体には特に意味はありません。データ領域へのポインタを引数に持っている,という点だけが重要です。

リスト11 採取対象関数checksum

int
checksum(const char* buf)
{
    int val = 0;
    int i;
    int length = 32;
    for(int i = 0 ; i < length ; i += 1){
        val = (val << 1) ^ buf[i];
    }
    return val;
}

先述したように,DTraceはカーネル空間で動作していますので,引数bufが指しているユーザ空間のメモリ内容を一旦カーネル空間にコピーしてから,その内容を表示する必要があります。

そこで,以下のようなDスクリプトを使用します。

リスト12 データ領域内容採取Dスクリプト

pid$target:show_args:checksum:entry
{
    this->iobuf = alloca(32);
    copyinto(arg0, 32, this->iobuf);
    tracemem(this->iobuf, 32);
}

このDスクリプトによって,bufの指す領域は以下のような形式で表示されます。

図5 データ領域内容の採取

$ dtrace -s watch_arg_mem.d \
         -q \
         -c './show_args'
             0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f  0123456789abcdef
         0: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00  .ELF............
        10: 02 00 03 00 01 00 00 00 80 09 05 08 34 00 00 00  ............4...
$

上記の D スクリプトにおけるアクション部分は,以下の手順で処理を行います。

  1. 変数 iobuf を確保
  2. 32 バイトのバッファ領域をalloca サブルーチンによって)カーネル空間に確保
  3. iobuf 変数にバッファ領域を参照させる
  4. checksum関数の引数bufの指すメモリ領域(ユーザ空間)から,iobufの指す領域(カーネル空間)へ32バイト分をcopyintoサブルーチンによって)複製
  5. iobufの指す領域32バイト分をtracememアクションによって)表示

copyintotracememなどは,名称と用法,機能から,どのようなものかは想像が付くと思います。

それではここで初めて出てきたthisというキーワードは何でしょうか?

Dスクリプトでは,this->VariableNameと記述することで,一般的なプログラミング言語で言うところの局所変数に相当する,節固有変数と呼ばれる記憶領域の作成/参照を行うことができるのです(※4⁠⁠。

上記のDスクリプト(リスト12)では,メモリ内容を表示するための一連の処理の間,カーネル内部に確保された一時バッファ領域への参照を保持するために,iobuf変数を使用していることになります。

なお,tracememに指定するデータ長は,実行時可変値を使用することができませんので,可変長バッファの内容を表示したい場合は,一工夫してやる必要があります(※4⁠⁠。

※4)
Dスクリプトでの変数の扱いについての詳細は第4回で,可変長領域の内容を表示する方法については第7回で扱う予定です。

戻り値の採取

これまでに説明してきた手法を組み合わせれば,引数に関する情報採取は十分行えることでしょう。

後は関数の戻り値を採取できれば,関数呼び出しにおける情報採取で必要な情報は,概ね網羅できることになります。

以下のDスクリプトは,プローブ指定における関数名指定部分を空欄にすることで,コマンドshow_args中の全ての関数に対して,戻り値の表示を行います。

リスト13 関数戻り値採取Dスクリプト

pid$target:show_args::return
{
    printf("%s()=0x%p", probefunc, arg1);
}

pidプロバイダでentryプローブを指定するDスクリプトでは,arg0arg1は関数引数の参照に使用しました。しかし,returnプローブを指定するDスクリプトでは,これらの値は違う意味を持ちます。

  • arg0: 関数の戻り先アドレス
  • arg1: 関数の戻り値

何らかのデータが格納されている領域を指すアドレスが戻り値になっていて,その参照先の内容を表示したい場合には,文字列引数や引数参照先メモリ内容の表示と同様に,copyinstrcopyinto + tracememなどを使用する必要があります。

なお,関数の戻り値を保持するarg1の値は,以下の場合には不定となりますので注意してください。

  • 戻り値を持たないvoidな)関数
  • 構造体/オブジェクトを返却する関数

次回予告

関数フローにおける基本的な情報採取について,2回に渡って説明してきましたが,いかがでしたでしょうか?

次回は,マルチスレッドや子プロセス,共有ライブラリなどが関与する状況での注意点について説明します。

著者プロフィール

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

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