プログラミングユーティリティ

上級者向きのトピック

プローブポイントの挿入

以下のような場合に、プローブポイントをコードに挿入します。

TNF_PROBE マクロの使い方

単純な例として、TNF_PROBE_0 を以下に示します。このマクロには、引数の型は指定しません。

TNF_PROBE_0 (name, keys, detail);

各変数について説明します。

上記のコマンドで照合される値を表 1-8 に示します。

表 1-8 ユーザー定義属性の例

属性 

値 

prex が照合する値

sunw%debug

entering function A

entering または function または A

comX%exception

no file

no または file

comY%func_entry

 

/.*/ (正規表現)

comY%color

red blue

red または blue

libtnfprobe は、ベンダー記号が前に付いていない属性名をすべて予約します (つまり、名前に % 記号が含まれていない属性をすべて予約します)。「C プログラムのサンプル」cookie.c では、以下の TNF_PROBE_0 を使用しています。

TNF_PROBE_0(start, "cookie main", "sunw%debug starting main");

注 -

プリプロセッサオプションの -DNPROBE (cc(1) のマニュアルページを参照) を使用してコンパイルする場合、またはプリプロセッサ制御文 #include <tnf/probe.h> の前に #define NPROBE を置いてコンパイルする場合には、プローブポイントと TNF の型拡張コードがプログラムにコンパイルされないようにしてください。


TNF_PROBE_1TNF_PROBE_5

引数の名前に含まれている数字 1 〜 5 は、プローブポイントに指定する変数の数を表します。たとえば、TNF_PROBE_1 の構文は次のようになっています。

TNF_PROBE_1(name, keys, detail,
arg_type_1, arg_name_1, arg_value_1);

また、TNF_PROBE_5 の構文は次のようになっています。

TNF_PROBE_5(name, keys, detail,
arg_type_1, arg_name_1, arg_value_1
arg_type_2, arg_name_2, arg_value_2
arg_type_3, arg_name_3, arg_value_3
arg_type_4, arg_name_4, arg_value_4
arg_type_5, arg_name_5, arg_value_5);

以下、各引数について説明します。

表 1-9 定義済みの型

型 

対応する C 言語の型と意味 

tnf_long

int、long 

tnf_ulong

unsigned int、unsigned long 

tnf_longlong

long long (コンパイラに実装されている場合) 

tnf_ulonglong

unsigned long long (コンパイラに実装されている場合) 

tnf_float

float 

tnf_double

double 

tnf_string

char * 

tnf_opaque

void * 

たとえば、「C プログラムのサンプル」cookie.c では、以下のように TNF_PROBE_2 を使用しています。

TNF_PROBE_2(inloop, "cookie main loop","sunw%debug in the loop",
            tnf_long, loop_count,   i,
            tnf_long, total_iterations,  sum);

cookie.c のマクロ定義の一部について、表 1-10 で説明します。

表 1-10 cookie.c の TNF マクロ定義

TNF_PROBE_0 ( 

引数の型が指定されていないプローブ 

start, 

プローブの名前 

"cookie main", 

プローブが cookiemain (keys 属性の値) に属しているグループのリスト

"sunw%debug starting main"); 

ユーザー定義の属性 = sunw%debug、値 = starting main (デバッグプローブ関数で使用されます)

TNF_PROBE_2 ( 

2 つの変数を持ったプローブ 

inloop, 

プローブの名前 

"cookie main loop", 

keys - cookie、mainloop

"sunw%debug in the loop", 

デバッグプローブ関数の値 

tnf_long, 

最初の変数の型 

loop_count, 

最初の変数の名前 (slots 属性の値)

i, 

最初の変数 

tnf_long, 

2 番目の変数の型 

total_iterations, 

2 番目の変数の名前 (slots 属性の値)

sum);

2 番目の変数 

""); 

 

例 − 関数の所要時間の測定

例 1-4 では、関数の入口と出口にプローブポイントを配置して、関数内で費やされる時間を測定しています。関数の入口に配置されたプローブは、その関数への引数も記録します。

prex は、トレースが許可されているプローブポイントをプログラム実行時に検出すると、トレースファイルに記録を書き込みます。各プローブポイントは、検出された時刻を記録し、ファイル名、行番号、name、keys などのプローブポイントの詳細情報が含まれたタグ記録を参照します。これらのタグの記録は、トレースファイルに 1 度だけ書き込まれ、上書きされることはありません。

以下のコード例において、最初のプローブポイント work_args は、そのプローブポイントの 2 つの変数値 (statemessage) も記録します。


例 1-4 関数の入口と出口に配置されたプローブポイント

#include <tnf/probe.h>

int
work(int state, char *message)
{
    TNF_PROBE_2(work_start, "work_module work"
               "sunw%debug in function work",
               tnf_long, int_input, state,
               tnf_string, string_input, message);
   ...
    TNF_PROBE_0(work_end, "work_module work", "");
}

プローブポイントのユーザー定義型

プログラムの構造体をトレースするには、TNF_DECLARE_RECORD マクロと TNF_DEFINE_RECORD_n マクロを使用して新しい型を定義します。これらのマクロは、プローブポイントに渡される型を拡張するためのコンパイル時インタフェースの一部です。

TNF_DECLARE_RECORD(c_type, tnf_type);
TNF_DEFINE_RECORD_1(c_type, tnf_type, 
                    tnf_member_type_1, 
                    tnf_member_name_1)
TNF_DEFINE_RECORD_2(c_type, tnf_type,  
                    tnf_member_type_1, 
                    tnf_member_name_1, 
                    tnf_member_type_2, 
                    tnf_member_name_2)
TNF_DEFINE_RECORD_3(c_type, tnf_type, 
                    tnf_member_type_1, 
                    tnf_member_name_1,
                    tnf_member_type_2, 
                    tnf_member_name_2, 
                    tnf_member_type_3, 
                    tnf_member_name_3)
TNF_DEFINE_RECORD_4(c_type, tnf_type, 
                    tnf_member_type_1, 
                    tnf_member_name_1,
                    tnf_member_type_2, 
                    tnf_member_name_2,
                    tnf_member_type_3, 
                    tnf_member_name_3,  
                    tnf_member_type_4, 
                    tnf_member_name_4) 
TNF_DEFINE_RECORD_5(c_type, tnf_type, 
                    tnf_member_type_1, 
                    tnf_member_name_1,
                    tnf_member_type_2, 
                    tnf_member_name_2,
                    tnf_member_type_3, 
                    tnf_member_name_3,
                    tnf_member_type_4,  
                    tnf_member_name_4,
                    tnf_member_type_5,  
                    tnf_member_name_5)

TNF_DECLARE_RECORDTNF_DEFINE_RECORD は、新しく定義する型ごとに 1 つだけ作成してください。TNF_DECLARE_RECORD は、TNF_DEFINE_RECORD よりも前に置く必要があります。定義されている tnf_type を複数のソースファイルで使用する必要がある場合は、その複数のファイルで共有するヘッダーファイル中に TNF_DECLARE_RECORD を宣言することができます。TNF_DEFINE_RECORD は、いずれか 1 つのソースファイルだけに作成する必要があります。

TNF_DEFINE_RECORD マクロインタフェースは、1 つの関数といくつかのデータ構造体を定義します。したがってこのインタフェースは、関数の内部ではなく、ソースファイル (.c ファイルまたは .cc ファイル) のファイルスコープ (有効範囲) 内で使用してください。


注 -

TNF_DEFINE_RECORD 文の後にセミコロンを置かないでください。セミコロンを置くと、コンパイル時に警告が出力されます。


以下、各変数について説明します。

例 − TNF 型の定義

新しい TNF 型の定義方法と、プローブ内でのその TNF 型の使い方を例 1-5 に示します。

この例 1-5 は、すべてのシンボルの接頭辞に pal を使用している架空のライブラリ libpalloc.so の一部であると仮定しています。


例 1-5 新しい TNF 型の定義

#include <tnf/probe.h>

typedef struct pal_header {
        long    size;
        char * descriptor;
        struct pal_header *next;
} pal_header_t;

TNF_DECLARE_RECORD(pal_header_t, pal_tnf_header);
TNF_DEFINE_RECORD_2(pal_header_t, pal_tnf_header,
                        tnf_long,   size,
                        tnf_string, descriptor)

/* 
 * 注 : 接頭辞 pal_tnf_header が付いている名前空間は、
 *     このクライアントではもう使用できない。
 */

void 
pal_free(pal_header_t *header_p)
{
        int state;

        TNF_PROBE_2(pal_free_start, "palloc pal_free",
                "sunw%debug entering pal_free",
                tnf_long,       state_var, state,
                pal_tnf_header, header_var, header_p);
        . . .
}

next フィールドを使用して自分自身を指し示す構造体 (リンクされたリスト) 中などで、tnf_type を再帰的または相互再帰的に定義することができます。

このような構造体が TNF_PROBE に渡されると、リンクされたリストの全体がトレースファイルに記録されます (next フィールドが NULL になるまで続きます)。ただし、そのリストが循環リストの場合には、処理が無限ループに入ります。この無限ループを中断するには、tnf_type から next フィールドを削除するか、または next メンバーの型を tnf_opaque として定義します。

パフォーマンスについて

頻繁に使用される mutex ロックのような、頻繁に通過するコード内のセクションには、プローブポイントを配置しないでください。

SPARCStation10 の場合、各プローブが使用するワーキングセットメモリーは約 30 ワード (データが 10 ワード、テキストが 20 ワード)、無効な各プローブで費やされる時間は約 200 ns (ナノ秒) です。アプリケーションのパフォーマンスは、配置するプローブの数を調整することによって改善できます。

プローブポイントを配置したライブラリを出荷する場合は、ベンチマークを実行して、パフォーマンスが著しく低下していないことを確認してください。パフォーマンスを改善するにはプローブの数を減らすか、プローブの配置を変更してください。

/proc

dbxtruss、および prex は、/proc を使用してターゲットプロセスを制御します。/proc では、1 つのクライアントだけが安全にターゲットプロセスを制御できます。このため、dbxprex のようなユーティリティを同じターゲットプログラム上で同時に実行することはできません。dbx または truss が実行されているターゲットプログラム上で prex を実行しようとすると、prex は「cannot attach to target (ターゲットに接続できません)」というメッセージを表示します。

ただし、以下の手順によって prexdbx を交互に実行できます。

  1. prex を起動します。

  2. プローブポイントの状態を設定します。

  3. quit suspend コマンドを実行します。

  4. dbx を起動します。

  5. 中断されているプログラムに接続します。

    ターゲットは、prexdbx 以外のコードは実行しません。

    ターゲットに SIGSTOP シグナルを送ってそのターゲットを一時停止させてから、"quit resume" を prex に入力することもできます。これを行なった場合には、停止されたプロセス上で dbx を呼び出してから、SIGCONT シグナルを送る必要があります (そうしない場合、dbx がハングします)。

dlopen()dlclose()、履歴

dlopen(3X) によって取り込まれた共有オブジェクト内のプローブは、prex のコマンド履歴に従って自動的に設定されます。dlclose(3X) によって共有オブジェクトが削除されると、prex はターゲットプログラム内のプローブの情報をリフレッシュします。これは、dlopendlclose に必要な作業がほかにもあるということを意味しています。したがって、dlopendlclose の所要時間は多少長くなります。

この機能を必要とせず、また dlopendlclose の処理に影響を与えたくない場合は、ターゲットから prex を分離してください。

シグナル

prex は、ターゲットプログラムに直接送られるシグナルを妨げません。ただし、prex は Control-c (SIGINT) や Control-z (SIGSTOP) などの端末から生成されたシグナルをすべて受信しますが、それをターゲットプログラムには転送しません。

ターゲットプログラムにシグナルを送るには、シェルから kill(1) コマンドを実行してください。

イベント書き込み操作の障害

トレースファイルにイベントを書き込む際には、システムコール障害などの障害がいくつか発生する可能性があります。これらの障害が発生すると、ターゲットプロセス内で障害コードが設定されます。ターゲットプロセスは正常に実行を継続しますが、トレース記録は書き込まれません。

Control-c を prex に入力して prex プロンプトを表示させると、prex はターゲット内の障害コードを検査して、トレース障害が発生したかどうかを通知します。

ターゲットによる fork() または exec() の実行

プログラムが fork() を実行した場合には、子プロセスが検出したプローブはすべて同じトレースファイルに記録されます。イベントにはプロセス ID 付きで記録されるので、そのイベントがどのプロセスから発生したのかを判定できます。

マルチスレッドのプログラムで、あるスレッドが fork を行なったときに他のスレッドが実行されていると、競合状態が発生することがあります。トレースファイルが破損しないようにするには、fork を実行するときに他のスレッドが停止しているようにするか、または fork1(2) を使用してください。

ターゲットプログラム自身 (ターゲットプログラムがフォークした子プロセスではない) が exec(2) を実行した場合には、prex がターゲットから分離して終了します。ユーザーは、以下のコマンドを使用して、prex を再接続することができます。

$ prex -p pid