ctrace(1)

名前
ctrace - C プログラム・デバッガ

形式
ctrace [options] [file]

機能説明
ctrace コマンドを使用すると、 C プログラムの実行を文単位で監視することができます。 これには、 -x オプション付きでシェル手続きを実行した場合と同様の効果があります。 ctrace は、ファイル ( file が指定されていない場合は標準入力) から C プログラムを 読み取り、各実行可能文とそこで参照または変更される すべての変数の値を出力する文を挿入し、 その結果のプログラムを標準出力に書き込みます。 cc(1) コマンドは ctrace の出力は一時ファイルに保存する必要があります。 この一時ファイルは、コンパイルし、実行することができます。

プログラム中の各文が実行されるたびに、端末上にその文の内容、 そこで参照または変更された変数の名前と値、さらにその文による出力があれば その内容が表示されます。 トレース出力からはループも検出され、 その場合はループから抜けるかループ内で別のシーケンスが実行されるまで トレースが停止されます。 無限ループを見つけることができるように、 ループが 1000 回繰り返されるたびに警告メッセージが出力されます。 トレース出力は標準出力に書き込まれるため、 その内容をファイルに保存した後にエディタ、 または tail(1) コマンドを使って調べることができます。

共通に使われるオプションは次のとおりです。

-f functions
    指定された関数 functions だけをトレースします。
-v functions
    指定された関数 functions を除く、すべての関数をトレースします。

変数を出力するためのデフォルトの書式に、 別の書式を追加したいことがあります。 デフォルトでは、 long 変数とポインタ変数は常に符号付き整数として出力されます。 文字配列を指すポインタは、適切であれば文字列として出力されます。 char 変数、 short 、 int 変数も符号付き整数として出力され、 適切であれば文字として出力されます。 double 変数は、指数部付き表記の浮動小数点数として出力されます。 次のオプションを使って、 各変数を上記以外の書式で (その書式が適切であれば) 出力することができます。

-o  8 進数
-x  16 進数
-u  符号なし整数
-e  浮動小数点数

次のオプションは、特別な環境でのみ使用されます。

-l n
    n 回連続して実行された文をループと見なします。 デフォルト値は 20 回です。 0 を指定すると、ループからのすべてのトレース出力が得られます。
-s  単純な代入文と文字列コピー関数の呼び出しによる冗長なトレース出力を抑止します。 このオプションを使用すると、演算子 == の代わりに = が使われたことによる バグを見つけることができなくなります。
-t n
    1 文あたり n 個の変数をトレースします。デフォルト値は 10 個です。 n の最大値は 20 です。 このオプションをどのような場合に使用するかについては、 「診断」の項を参照してください。
-P  トレースの前に、入力データの前処理を行います。同時に、 cc(1) のオプションである -D 、 -I 、 -U も使用できます。
-pstring
    トレース出力に使用する関数を指定します。 デフォルトでは printf が使用されます。たとえば、 string に fprintf(stderr) を指定すると、トレースの内容は標準エラー出力に送られます。
-rf トレース関数パッケージ runtime.c の代わりに、ファイル f を使用します。 このオプションでファイルを置き換えれば、名前と先頭の引数だけではなく、 出力関数全体を変更することができます ( -p オプションを参照)。
-V  バージョン情報を標準エラーに出力します。
-Qarg
    arg が y のとき、 ctrace に関する識別情報が出力ファイルに追加されます。 この情報は、ソフトウェア管理に役立ちます。 arg に n を指定すると識別情報は出力されません (デフォルト)。

使用例
ファイル lc.c に次の C プログラムが含まれているとします。

 1 #include <stdio.h>
 2 main()	/* 入力の行数を数える */
 3 {
 4 	int c, nl;
 5
 6 	nl = 0;
 7 	while ((c = getchar()) != EOF)
 8 		if (c = '\n')
 9 			++nl;
10 	printf("%d\n", nl);
11 }

次のコマンドとテスト用データを入力します。

cc lc.c
a.out
1
(Ctrl-d)

上記のプログラムがコンパイルされ、実行されます。 結果として、2 という数字が出力されます。 テスト用データには 1 行しか含まれていないため、この結果は正しくないと言えます。 このプログラムのような誤りを犯すことはよくありますが、 なかなか見つけることができません。 そこで、次のように、 ctrace を呼び出し、作成されたプログラムをコンパイル、実行してみます。

ctrace lc.c >temp.c
cc temp.c
a.out

出力は次のようになります。

 2 main()
 6 	nl = 0;
    	/* nl == 0 */
 7 	while ((c = getchar()) != EOF)

プログラムは、ここで端末からの入力を待機します。 上記と同じテスト用データが入力されると、 引き続き次のように出力します。

   	/* c == 49 or '1' */
 8 		if (c = '\n')
    		/* c == 10 or '\n' */
 9 			++nl;
   			/* nl == 1 */
 7 	while ((c = getchar()) != EOF)
   	/* c == 10 or '\n' */
 8 		if (c = '\n')
    		/* c == 10 or '\n' */
 9 			++nl;
   			/* nl == 2 */
 7 	while ((c = getchar()) != EOF)

ここでファイルの終端を示す文字 (Ctrl-d) が入力されると、 プログラムは次のように出力して終了します。

   	/* c == -1 */
10 	printf("%d\n", nl);
   	/* nl == 2 */2
	return

最後から 2 行目の変数 nl のトレース行の終わりに出力される情報に注目してください。 また、トレース出力の最後に追加された return というコメントにも注目してください。 これは、関数を閉じる中括弧のところで、 暗黙的に return が行われたことを示しています。

トレース出力から、 7 行目では変数 c に '1' が代入されたのに、 8 行目でその値が '\n' になっていることが分かります。 この if 文に注目することができれば、 おそらく等価演算子 ( == ) の代わりに、誤って代入演算子 ( = ) が使用されていることに気づくはずです。

実行時のトレース制御
-f オプションまたは -v オプションでトレース対象の関数が指定されないかぎり、 ctrace はプログラムファイル全体をトレースします。 このデフォルトの動作では、 文単位でトレースを制御したり、 トレース後のプログラムを実行するときに トレースをオン / オフすることができません。

プログラムに関数 ctroff() および ctron() の呼び出しを追加すれば、実行時にこの 2 つの操作を行うことができます。 関数 ctroff() はトレースをオンに、 ctron() はトレースをオフにします。 このようにして、トレースを制御するための複雑な基準も、 if 文を使って自由にコーディングすることができます。 さらに、 ctrace では環境変数 CTRACE が定義されるため、次の例のように、 トレースを制御するコードを条件付きで取り込むこともできます。

    #ifdef CTRACE
        if (c == '!' && i > 1000)
            ctron();
    #endif

-g オプションでコンパイルすれば、これらの関数を dbx(1) から呼び出すことも可能です。 たとえば、 main 関数の 7 行目から 10 行目を除くすべての行を トレースするためには、次のように入力します。

    dbx a.out
    when at 7 {call ctroff();}
    when at 7 {call ctron();}
    run

静的変数 tr_ct_ の設定値によってトレースをオン / オフすることもできます。 0 に設定するとトレースはオフ、 1 に設定するとオンになります。 この方法は、上記の 2 つの関数を直接呼び出すことができないデバッガを 使用している場合に利用できます。

ファイル
runtime.c    実行時トレース・パッケージ

関連項目
bfs(1), dbx(1), tail(1), ctype(3C), fclose(3S), printf(3S), string(3C)

診断
トレース後のコードには cc の警告メッセージが含まれることがあるため、ここでは ctrace と cc(1) の両方から出力される診断メッセージを取り上げます。 ごくまれに、 cc のエラーメッセージを受け取ることもありますが、 そのケースは避けることができます。

  ctrace の診断メッセージ
warning: some variables are not traced in this statement
    C コンパイラからの "out of tree space; simplify expression" というエラーを 避けるため、デフォルトでは、各文につき変数は 10 個までしかトレースできません。 この数は、 -t オプションを使って増やすことができます。

warning: statement too long to trace
    この文の長さが 400 文字を超えています。 スペースを使ってインデントが行われている場合は、 それをタブに変えて 1 行あたりの文字数を少なくしてください。

cannot handle preprocessor code, use -P option
    通常、C の文の途中に前処理文 #ifdef/#endif があるか、 前処理文の最後にセミコロンが置かれていることが原因です。

'if ... else if' sequence too long
    途中の else を削除して、シーケンスを分割してください。

possible syntax error, try -P option
    -P オプションと適切な前処理オプション ( -D 、 -I 、 -U ) を使って、入力プログラムの前処理を行なってください。

注意事項
システム関数と同じ名前で関数を定義した場合、 その引数の数がシステム関数と異なっていると構文エラーになります。 別の名前を使用してください。

ctrace は、 BADMAG を前処理マクロ、 EOF および NULL を #define で定義された定数と見なします。" int EOF ; " のように、これらを変数として宣言すると構文エラーになります。

ポインタ値は常に、文字列ポインタを指すポインタとして扱われます。

ctrace は、構造体、共用体、配列といった集合体の構成要素を識別できません。 集合体全体に代入が行われる場合、 その集合体の全要素を出力するための書式を適切に選択できません。 集合体のアドレスを出力したり、 集合体の値を誤った書式で出力することがあります (たとえば、 2 つの整数メンバーを持つ構造体を 3.149050e-311 のように出力します)。

複数のファイルから構成されるプログラムの場合、 ループによるトレース出力の抑止は各ファイルごとに別々に行われます。 そのため、ループから呼び出される関数が繰り返しトレースされたり、 ある関数のトレースが同じファイル内の別の関数が呼び出されるまで抑止されることが あります。