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

第4回 前提条件の記述

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

述語記述における注意点

述語(前提条件)を用いたDスクリプトを記述する際には,いくつか注意すべき事があります。

記述順序に関する注意

dtraceコマンドは,指定されたDスクリプトの内容を先頭から順に実施します。

たとえば,範囲絞り込みを行うDスクリプトを以下のように記述したと仮定します。オリジナル(リスト3)に対して,節 (2)/(3) の順序を入れ替えています。

リスト7 記述順序の入れ替え

/* 節 (1) */
pid$target:show_nesting:f3:entry
{
    self->traced = 1;
}

/* 節 (2) */
pid$target:show_nesting:f3:return
{
    self->traced = 0;
}

/* 節 (3) */
pid$target:show_nesting::entry,
pid$target:show_nesting::return
/self->traced/
{
}

このスクリプトの各節は,関数f3()の開始の際に以下のように振る舞います。

  1. 関数f3()の開始なので,self->tracedを1に設定
  2. 関数 f3() の開始なので,無視
  3. self->traced が1なので,実施=関数フロー表示

結果として,⁠関数f3()の開始」は節(3)における関数フロー採取対象となります。

その一方で,関数f3()の終了の際に,各節は以下のように振る舞います。

  1. 関数f3()の終了なので,無視
  2. 関数f3()の終了なので,self->tracedを0に設定
  3. self->traced が0なので,無視

開始と異なり,⁠関数f3()の終了」は節(3)における関数フロー採取対象となりません。つまりリスト7のDスクリプトを使用すると,採取の対称性が崩れてしまうのです。

もうひとつの例として,前ページのリスト5のDスクリプトも見てみましょう。

リスト8 抑止付きの絞り込み(リスト6の再掲)

/* 節 (1) */
pid$target:show_nesting:f4:entry
{
    self->traced = 1;
}

/* 節 (2) */
pid$target:show_nesting:f2:return
/self->traced/
{
    self->suppressed = 0;
}

/* 節 (3) */
pid$target:show_nesting::entry,
pid$target:show_nesting::return
/self->traced && !self->suppressed/
{
}

/* 節 (4) */
pid$target:show_nesting:f2:entry
/self->traced/
{
    self->suppressed = 1;
}

/* 節 (5) */
pid$target:show_nesting:f4:return
{
    self->traced = 0;
}

関数f4()に対する節(1)/(5)ではentry/returnの順序で記述されているプローブが,関数f2()に対する節(2)/(4)では逆順で書かれていることがわかります。

これは,節(2)/(4)の順序を入れ替えてentry/returnの順で記述した場合,節(3)による表示が実施されるよりも先に,節(4)の entryプローブによるself->suppressed = 1が実施されるため,関数f2()自身がトレース採取対象から除外されてしまうのを防ぐためです。

指定の関数を採取対象に含めるか否かに応じて,上記のような順序の調整が必要になります。

再帰呼び出しに関する注意

範囲を絞り込んだ採取の際に使用したDスクリプト(リスト3)を,もう一度見てみましょう。

リスト9 採取範囲の絞り込み(再掲)

pid$target:show_nesting:f3:entry
{
    self->traced = 1;
}

pid$target:show_nesting::entry,
pid$target:show_nesting::return
/self->traced/
{
}

pid$target:show_nesting:f3:return
{
    self->traced = 0;
}

先述した実行例では期待通りの結果を得られましたが,実はこの記述には問題があります。

もしも,絞り込み契機となる関数f3()が,self->traced = 1実施後に再度呼ばれる,いわゆる再帰呼び出しを行うものと仮定した場合,複数回のf3()呼び出しがあっても,最初の f3()終了でself->traced = 0が実施されてしまうため,それ以後の関数フローは採取されません。

この問題を解決するためには,関数f3()呼び出しの入れ子状況を正しく把握する必要があります。

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

リスト10 再帰に対応した絞り込み

pid$target:show_nesting:f3:entry
{
    self->traced += 1;
}

pid$target:show_nesting::entry,
pid$target:show_nesting::return
/self->traced/
{
}

pid$target:show_nesting:f3:return
{
    self->traced -= 1;
}

上記スクリプトでは,以前は単なるフラグ値として扱っていたself->traced値を,関数f3()呼び出しの入れ子の深さを表す値として扱っています。

これにより,関数f3()の最初の呼び出しから復帰するまでは,self->traced値が0になることがありませんので,期待通りの関数フローを採取することができます。

なお,前ページのリスト6に関しても,対象関数が再帰呼び出しを行うケースでは正しく動作しません。ただし,これを適切に実現するには,第5回で説明する配列機能が必要となりますので,そこで改めて説明したいと思います。

次回予告

次回は,関数フローから少々趣向を変えて,DTrace の統計情報採取機能について説明したいと思います。

著者プロフィール

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

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