これまでの連載における説明では、基本的にpid
プロバイダが提供するプローブ(entry
およびreturn
)を使用して、関数の呼び出し/復帰に関して情報を採取してきました。
しかし、実際のプログラム開発においては、情報を採取したい場所が、必ずしも関数呼び出しとは直接関係無い場合も多々あります。
そこで、今回と次回の2回に渡って、関数呼び出しの境界以外から情報を採取するための、独自プロバイダの定義とその利用について説明します。
今回は、単純な値を採取する独自プロバイダについて説明します。
ソースファイルの準備
以下の手順で必要となるソースファイルを準備します。
プロバイダの定義
何をおいても、まずは独自プロバイダを定義する必要があります。
プロバイダcheckpoint
が提供するプローブpass
は、以下のような用途を想定しています。
プログラムの要所要所に埋め込まれたプローブ位置において、ファイル名と行番号情報を採取することで、実行時にどのような経路を通過したのかを知る
つまり、関数フローよりもさらに細かい粒度で、実行フローを採取することができるわけです。
なおプロバイダ定義を記述する際には、ブロック末尾("{ }
" の後ろ)にセミコロンが必要です。普段のDスクリプトでは不要な記述であることから、このセミコロンは忘れがちになりますので注意してください。
ヘッダファイルの生成
独自プロバイダを定義したならば、図1に示す要領でdtrace
コマンドを実行してください。
独自プロバイダを定義したDスクリプトの指定("-s
" オプション)と共に、"-h
" オプションを指定することで、リスト2のような内容を持ったヘッダファイルcheckpoint.h
が生成されます。
"-h
"オプション指定によってdtrace
コマンドが生成するヘッダファイルでは、各プローブごとに「プロバイダ名」+「プローブ名」の形式でマクロが定義されます(名称は共に大文字に変換されます)。
通常は、ここで生成されたヘッダファイル中のマクロを使用して、対象ソースファイルにプローブの埋め込みを行います。
リスト2の例に見られるように、マクロそのものは別途宣言されている関数(この例では __dtrace_checkpoint___pass()
)を呼び出すだけの単純なものですが、名称の長さの点などからも、直接関数を呼ぶのではなくマクロを使う方が良いでしょう。
ユーティリティヘッダの定義
先述したように、通常はdtrace
コマンドによって生成されたヘッダファイルと、そこで定義されているマクロを直接使いますが、今回のpass
のようなプローブの場合、実装時におけるファイル名や行番号指定の手間を軽減するために、リスト3のようなマクロ定義を含むヘッダファイルcheckpoint_impl.h
を定義しましょう。
CHECKPOINT_PASS()
マクロを直接使用するのではなく、新たに定義したDTRACE_CHECKPOINT_PASS()
マクロを使うことで、対象プログラムに対するプローブの埋め込みの手間が(幾分)軽減されます。
Cプログラムの実装
今回独自に定義したpass
プローブの埋め込み対象として、リスト4に示す処理をmain()
に持つプログラムを想定します。
この実装では与えられた引数に応じて条件分岐を行いますが、あくまで関数内に閉じた処理であり、他の関数を呼び出すわけでもありませんから、これまで使用してきたpid
プロバイダによる関数フロー採取では、どのような経路が実行されたのかを知ることができません。
そこで、今回新たに定義したpass
プローブを使用することで、どのファイルのどの行が実行されたのかを採取するわけです。
実行可能ファイルの生成
Cプログラムのコンパイル
プローブ埋め込み対象のCプログラム(前ページのリスト4)は、以下の要領でコンパイルします。
ここでのコンパイルにはDTraceの独自プローブ埋め込みの影響はありませんから、必要に応じて適宜オプション等を指定してください。
Cプログラムのリンク
コンパイルによって生成された、プローブ埋め込み対象のオブジェクトファイル(*.o ファイル)をリンクしようとすると、以下のようなエラーが発生します。
上記実行例で、「シンボル未定義」と判定された関数の呼び出しは、プローブ埋め込み用マクロ(checkpoint.h
におけるCHECKPOINT_PASS
マクロ)の展開によるものです(前ページのリスト2参照)。
dtrace
コマンドにより生成されたのはヘッダファイルのみですし、別途プローブ用に何かを実装したわけでもなければ、特別なライブラリをリンクしたわけでもありませんから、シンボル未定義となるのは至極当然の成り行きですね。
実はユーザプログラムに独自プローブを埋め込む場合、リンクに先立ってdtrace
コマンドによる前処理を行う必要があります。
dtrace
コマンドによるリンク前処理は、図4の要領で、"-G
"オプションと、プローブが埋め込まれている全てのオブジェクトファイルを指定します。
"-G
"オプションが指定された場合、dtrace
コマンドは以下の処理を実施します。
- 列挙されたオブジェクトファイルから、プローブ(="
__dtrace_*
"関数)呼び出しを除去- 除去したプローブ呼び出しに関する情報をまとめ、それを格納したオブジェクトファイル(この場合は
checkpoint.o
)を生成
前処理が完了したなら、dtrace
コマンドにより生成されたオブジェクトファイルを含めて、必要なオブジェクトファイルをリンクしてください。
リンク時オプションに関しても、DTraceの独自プローブ埋め込みの影響はありませんから、必要に応じて適宜オプション等を指定してください。
なお、dtrace
コマンド+"-G
"オプションによる前処理は、関連するオブジェクトファイル群の内容を改変してしまう点に注意してください。
一部のソースファイルを改変してから再度リンクを行う場合、ソースファイルを改変していないオブジェクトファイルに関しても、コンパイルによる再生成が必要となります。コンパイル~リンクまでの作業をmake
コマンド等で自動化する場合には、特に注意が必要です。
プローブからの情報採取
独自に定義したプロバイダからの情報採取の要領は、pid
プロバイダの場合と同じ(※1)です。
リスト5に、checkpoint
プロバイダのpass
プローブを使用するDスクリプトの例を示します。
それでは、さっそく情報採取をしてみましょう。
引数によって分岐した先でDTRACE_CHECKPOINT_PASS()
マクロを通過するつど、ファイル名/行番号情報が出力されているのがわかります。
Emacsやviのエラー行ジャンプ機能と併用すれば、DTraceで採取した情報を元に処理経路順にソースを参照する、といったことも可能になります。
次回予告
今回は独自プロバイダの定義を行いましたが、いかがだったでしょうか?
定義/利用共に非常に簡単ですから、ぜひ実際のプログラミングに取り入れてみてください。
次回は、独自プロバイダ定義の後編ということで、プローブに指定された値から、より詳細な情報を得る方法を説明したいと思います。