C に関する基本的なデバッグ・タスクの説明では、下記の C プログラムを 参照します。
ここで述べた内容に関して詳しくは、以下のトピックを参照してください。
下記のプログラムは、デバッグ・タスクを示すために各種のトピックで取り上げられます。
このプログラムは、その入力を文字バッファーから読み取る単純な 計算プログラムです。整数を読み取ると、スタックに 入れます。演算子 (+ - * /) の 1 つが読み取られると、最上位の 2 つのエレメントがスタックから取り出され、それらのエレメントに演算が行われ、結果がスタックに 入れられます。演算子 = は、スタックの一番上のエレメントの値をバッファーに 書き出します。
CALC.H
/*----- FILE CALC.H --------------------------------------------------*/
/* */
/* Header file for CALC.C PUSHPOP.C READTOKN.C */
/* a simple calculator */
/*--------------------------------------------------------------------*/
typedef enum toks {
T_INTEGER,
T_PLUS,
T_TIMES,
T_MINUS,
T_DIVIDE,
T_EQUALS,
T_STOP
} Token;
Token read_token(char buf[]);
typedef struct int_link {
struct int_link * next;
int i;
} IntLink;
typedef struct int_stack {
IntLink * top;
} IntStack;
extern void push(IntStack *, int);
extern int pop(IntStack *);
CALC.C
/*----- FILE CALC.C --------------------------------------------------*/
/* */
/* A simple calculator that does operations on integers that */
/* are pushed and popped on a stack */
/*--------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include "calc.h"
IntStack stack = { 0 };
main()
{
Token tok;
char word[100];
char buf_out[100];
int num, num2;
for(;;)
{
tok=read_token(word);
switch(tok)
{
case T_STOP:
break;
case T_INTEGER:
num = atoi(word);
push(&stack,num); /* CALC1 statement */
break;
case T_PLUS:
push(&stack, pop(&stack)+pop(&stack) );
break;
case T_MINUS:
num = pop(&stack);
push(&stack, num-pop(&stack));
break;
case T_TIMES:
push(&stack, pop(&stack)*pop(&stack));
break;
case T_DIVIDE:
num2 = pop(&stack);
num = pop(&stack);
push(&stack, num/num2); /* CALC2 statement */
break;
case T_EQUALS:
num = pop(&stack);
sprintf(buf_out,"= %d ",num);
push(&stack,num);
break;
}
if (tok==T_STOP)
break;
}
return 0;
}
PUSHPOP.C
/*----- FILE PUSHPOP.C -----------------------------------------------*/
/* */
/* A push and pop function for a stack of integers */
/*--------------------------------------------------------------------*/
#include <stdlib.h>
#include "calc.h"
/*--------------------------------------------------------------------*/
/* input: stk - stack of integers */
/* num - value to push on the stack */
/* action: get a link to hold the pushed value, push link on stack */
/* */
extern void push(IntStack * stk, int num)
{
IntLink * ptr;
ptr = (IntLink *) malloc( sizeof(IntLink)); /* PUSHPOP1 */
ptr->i = num; /* PUSHPOP2 statement */
ptr->next = stk->top;
stk->top = ptr;
}
/*--------------------------------------------------------------------*/
/* return: int value popped from stack */
/* action: pops top element from stack and gets return value from it */
/*--------------------------------------------------------------------*/
extern int pop(IntStack * stk)
{
IntLink * ptr;
int num;
ptr = stk->top;
num = ptr->i;
stk->top = ptr->next;
free(ptr);
return num;
}
READTOKN.C
/*----- FILE READTOKN.C ----------------------------------------------*/
/* */
/* A function to read input and tokenize it for a simple calculator */
/*--------------------------------------------------------------------*/
#include <ctype.h>
#include <stdio.h>
#include "calc.h"
/*--------------------------------------------------------------------*/
/* action: get next input char, update index for next call */
/* return: next input char */
/*--------------------------------------------------------------------*/
static char nextchar(void)
{
/*--------------------------------------------------------------------*/
/* input action: */
/* 2 push 2 on stack */
/* 18 push 18 */
/* + pop 2, pop 18, add, push result (20) */
/* = output value on the top of the stack (20) */
/* 5 push 5 */
/* / pop 5, pop 20, divide, push result (4) */
/* = output value on the top of the stack (4) */
/*--------------------------------------------------------------------*/
char * buf_in = "2 18 + = 5 / = ";
static int index; /* starts at 0 */
char ret;
ret = buf_in[index];
++index;
return ret;
}
/*--------------------------------------------------------------------*/
/* output: buf - null terminated token */
/* return: token type */
/* action: reads chars through nextchar() and tokenizes them */
/*--------------------------------------------------------------------*/
Token read_token(char buf[])
{
int i;
char c;
/* skip leading white space */
for( c=nextchar();
isspace(c);
c=nextchar())
;
buf[0] = c; /* get ready to return single char e.g."+" */
buf[1] = 0;
switch(c)
{
case '+' : return T_PLUS;
case '-' : return T_MINUS;
case '*' : return T_TIMES;
case '/' : return T_DIVIDE;
case '=' : return T_EQUALS;
default:
i = 0;
while (isdigit(c)) {
buf[i++] = c;
c = nextchar();
}
buf[i] = 0;
if (i==0)
return T_STOP;
else
return T_INTEGER;
}
}
ここで述べた内容に関して詳しくは、以下のトピックを参照してください。
このトピックでは、AT CALL コマンドおよび AT ENTRY コマンドを使用して、ルーチンが呼び出される直前または直後に停止する方法を説明します。 これらのコマンドの説明には、例: デバッグが行われる C プログラムの例 が使用されます。
AT CALL コマンドを使用するには、呼び出すプログラムを TEST コンパイラー・オプションを使用して コンパイルしておく必要があります。
read_token が呼び出される直前に停止するには、次のコマンドを入力します。
AT CALL read_token ;
AT ENTRY コマンドを使用するには、呼び出したプログラムを TEST コンパイラー・オプションを使用して コンパイルしておく必要があります。
read_token が呼び出された直後に停止するには、次のコマンドを入力します。
AT ENTRY read_token ;
push が呼び出された直後に、num が 16 である場合にのみ停止するには、以下のコマンドを入力します。
AT ENTRY push WHEN num=16;
単一の変数の内容をリストするには、変数名のところにカーソルを移動させ、PF4(LIST) を押します。値はログ・ウィンドウに表示されます。これは、コマンド行に LIST TITLED variable を入力するのと同等です。
上記の CALC プログラムを CALC1 というラベルの付いたステートメントまで実行するには、カーソルを num の上に移動して PF4 (LIST) を押します。これにより、ログ・ウィンドウに次のように表示されます。
LIST ( num ) ; num = 2
num の値を 22 に変更するには、num = 2 の行に num = 22 と上書きし、Enter を押してこれをコマンド行に置き、Enter を再度押してこのコマンドを出します。
ほとんどの C の式はコマンド行で入力することができます。
次に、PF2 (STEP) を押して、push() の呼び出しに進み、PUSHPOP2 というラベルの付いたステートメントまでステップを進めます。変数 ptr の属性を表示するには、次のDebug Tool コマンドを出します。
DESCRIBE ATTRIBUTES *ptr;
結果はログ・ウィンドウに次のように表示されます。
ATTRIBUTES for * ptr
Its address is 0BB6E010 and its length is 8
struct int_link
struct int_link *next;
int i;このアクションを使用して、構造体と共用体をブラウズすることができます。
次のコマンドにより、ptr によりポイントされている構造体のメンバーの、すべての値をリストすることができます。
LIST *ptr ;
結果はログ・ウィンドウに次のように表示されます。
LIST * ptr ; (* ptr).next = 0x00000000 (* ptr).i = 0
構造体メンバーの値を変更するには、次の例のように、割り当てをコマンドとして出すことにより可能です。
(* ptr).i = 33 ;
ユーザー・プログラムの特定の部分が、最初の数千回は正常に機能しますが、その後ある条件の下では失敗するということがよくあります。 単純な行ブレークポイントの設定は、プログラムの非効率的なデバッグ手段です。その理由は、ある条件に達するまで、GO コマンドを 1000 回実行する必要があるためです。ある条件になるまで、Debug Tool がプログラムの実行を続けるよう命令することができます。
例えば、上記のプログラムの main プロシージャーでは、(例外が起こる前に) 除数が 0 である場合にのみ、T_DIVIDE で停止したいとします。この場合、次のようにブレークポイントをセットします。
AT 40 { if(num2 != 0) GO; }行 40 は CALC2 というラベルの付いたステートメントです。 このコマンドにより、Debug Tool は行 40 で停止します。 num2 の値が 0 でない場合、プログラムは続行します。Debug Tool コマンドを入力して、num2 の値を非ゼロ値に変更できます。
PUSHPOP.C ファイル内の関数 push() への入り口にブレークポイントを設定したいとします。PUSHPOP.C は TEST を使用してコンパイルされましたが、その他のファイルはそうではありません。Debug Tool は、空のソース・ウィンドウで立ち上がります。コンパイル単位を表示するには、次のコマンドを入力します。
LIST NAMES CUS
LIST NAMES CUS コマンドは、Debug Tool が認識しているすべてのコンパイル単位のリストを表示します。使用しているコンパイラーによっては、あるいはアプリケーションにより後から "USERID.MFISTART.C(PUSHPOP)" がフェッチされた場合、コンパイル単位を Debug Tool が認識しない場合があります。これが表示された場合、次のコマンドを入力します。
SET QUALIFY CU "USERID.MFISTART.C(PUSHPOP)" AT ENTRY push; GO ;
または
AT ENTRY "USERID.MFISTART.C(PUSHPOP)":>push GO;
これが表示されていない場合、次のように APPEARANCE ブレークポイントをセットします。
AT APPEARANCE "USERID.MFISTART.C(PUSHPOP)" ; GO ;
この APPEARANCE ブレークポイントの唯一の目的は、PUSHPOP コンパイル単位内の関数が最初に実行されるときに制御を得ることです。 これが起こったときに、次のように、push() への入り口にブレークポイントを設定できます。
AT ENTRY push;
また、次のように、ブレークポイントを結合することもできます。
AT APPEARANCE "USERID.MFISTART.C(PUSHPOP)" AT ENTRY push; GO;
stdout をログ・ウィンドウにリダイレクトするには、次のコマンドを出します。
SET INTERCEPT ON FILE stdout ;
この SET コマンドにより、ユーザー・プログラムからだけでなく、対話式関数呼び出しからも stdout を取り込むことができます。例えば、コマンド行で対話式に printf を呼び出し、次のように入力してヌル終了ストリングを表示することができます。
printf(sptr);
この方が LIST STORAGE を使用するより簡単かもしれません。
stdin 入力データをコマンド・プロンプトから入力可能になるように入力宛先変更するには、以下のステップを行います。
EQA1290I The program is waiting for input from stdin
EQA1292I Use the INPUT command to enter up to a maximum of 1000
characters for the intercepted variable-format file. ここで述べた内容に関して詳しくは、以下のトピックを参照してください。
コマンド行で呼び出すことで、ライブラリー関数 (strlen など)、 あるいはプログラム関数の 1 つを対話式に開始できます。この関数は、以下の要件に準拠している必要があります。
次の例では、push() を対話式に 呼び出して、値がポップオフされる直前にスタックにもう 1 つの値を 入れます。
AT CALL pop ; GO ; push(77); GO ;
追加の値がスタックに入れられたため、計算プログラムは、前とは異なる結果を出します。
char * 変数 ptr は、印刷可能文字を含むストレージの一部をポイントすることができます。最初の 20 文字を表示するには、次のように入力します。
LIST STORAGE(*ptr,20)
ストリングがヌルで終了している場合、次のように、対話式関数呼び出しを コマンド行で使用することもできます。
puts(ptr) ;
ここで述べた内容に関して詳しくは、以下のトピックを参照してください。
push() および pop() をエクスポートし、PUSHPOP.C を DLL として作成します。PUSHPOP という DLL から push() と pop() をインポートするプログラムとして、CALC.C および READTOKN.C を作成します。アプリケーション CALC が DLL を開始する時、PUSHPOP は Debug Tool には知られていません。次の例で示されるように、AT APPEARANCE ブレークポイントを使用して、コンパイル単位内に最初にコードが現れたときに、DLL 内の制御を得るようにします。
AT APPEARANCE "USERID.MFISTART.C(PUSHPOP)" ; GO ;
この APPEARANCE ブレークポイントの唯一の目的は、PUSHPOP コンパイル単位内の関数が最初に実行されるときに制御を得ることです。これが起こった時に、PUSHPOP 内にブレークポイントをセットすることができます。
プログラミング・エラーの検討中に、どのようにしてこの状態になったのか、また特に、関数呼び出しのトレースバックが何なのかを知りたいことがよくあります。この情報を得るには、次のコマンドを出します。
LIST CALLS ;
例えば、次のコマンドを使用して CALC の例を実行すると、
AT ENTRY read_token ; GO ; LIST CALLS ;
ログ・ウィンドウには、次のような情報が表示されます。
At ENTRY in C function CALC ::> "USERID.MFISTART.C(READTOKN)" :> read_token. From LINE 18 in C function CALC ::> "USERID.MFISTART.C(CALC)" :> main :> %BLOCK2.
これは、呼び出し元のトレースバック情報を示しています。
プログラムを変更せずに、プログラムの入り口点と出口点を示しながらプログラムをトレースするには、次の Debug Tool コマンドをファイルに入れ、Debug Tool が最初にユーザー・プログラムを表示するときに、これを USE するようにします。次の Debug Tool コマンドを含むデータ・セット USERID.DTUSE(TRACE) が あるとします。
int indent;
indent = 0;
SET INTERCEPT ON FILE stdout;
AT ENTRY * { ¥
++indent; ¥
if (indent < 0) indent = 0; ¥
printf("%*.s>%s¥n", indent, " ", %block); ¥
GO; ¥
}
AT EXIT * {¥
if (indent < 0) indent = 0; ¥
printf("%*.s<%s¥n", indent, " ", %block); ¥
--indent; ¥
GO; ¥
}次のコマンドを入力し、このファイルを Debug Tool へのコマンドのソースとして使用することができます。
USE USERID.DTUSE(TRACE)
次にリストしたプログラムの実行に関するトレース情報は、USE ファイルの実行後、ログ・ウィンドウに表示されます。
int foo(int i, int j) {
return i+j;
}
int main(void) {
return foo(1,2);
}USE ファイルを Debug Tool コマンドの入力源として指定したサンプル・プログラムの 実行後、次のトレース情報がログ・ウィンドウに表示されます。
>main >foo <foo <main
USE ファイルを作成したくない場合、コマンド行から コマンドを入力しても同じ結果が得られます。
プログラムの実行時に、あるストレージが不意にその値を変更したため、いつ、どこでこのようなことが生じたかを検出したい場合があります。関数 set_i が、呼び出し元が想定した以上に変わる次のような例を考えてみます。
struct s { int i; int j;};
struct s a = { 0, 0 };
/* function sets only field i */
void set_i(struct s * p, int k)
{
p->i = k;
p->j = k; /* error, it unexpectedly sets field j also */
}
main() {
set_i(&a,123);
}次のコマンドにより、a のアドレスを見つけます。
LIST &(a.j) ;
その結果が 0x7042A04 であったとします。このアドレスから始まる次の 4 バイトのストレージの値の変更を監視する ブレークポイントを設定するには、次のコマンドを出します。
AT CHANGE %STORAGE(0x7042A04,4)
プログラムが実行されるとき、Debug Tool は、このストレージの値が変更されると停止します。
初期化されていないストレージのエラーの検出をしやすくするため、言語環境プログラムの TEST ランタイム・オプションと STORAGE オプションを使用してユーザー・プログラムを実行します。次のような例を考えてみます。
TEST STORAGE(FD,FB,F9)
STORAGE の最初のサブパラメーターは、ヒープから割り振られたストレージのための充てんバイトです。例えば、malloc() により割り振られたストレージは、バイト 0xFD で埋められます。このバイトがストレージにわたって繰り返されている場合、おそらく、初期化されていないヒープ・ストレージであると考えられます。
STORAGE の 2 番目のサブパラメーターは、ヒープから 割り振られたが、その後、解放されたストレージのための充てん文字です。例えば、free() を呼び出して解放されたストレージは、バイト 0xFB で埋められている場合があります。このバイトがストレージにわたって繰り返されている場合、おそらく、ヒープに割り振られたが解放されたストレージであると考えられます。
STORAGE の 3 番目のサブパラメーターは、新しいスタック・フレーム内の自動ストレージ変数のための充てんバイトです。このバイトがストレージにわたって繰り返されている場合、おそらく、初期化されていない自動ストレージであると考えられます。
この例で選択される値は、できるだけ早期に問題検出ができるように、奇数で大きな値です。例えば、奇数アドレスにブランチしようとすれば、ただちに例外が起こります。
初期化されていないヒープ・ストレージの例として、プログラム CALC に、STORAGE ランタイム・オプションとして STORAGE(FD,FB,F9) を指定し、PUSHPOP2 のラベルの付いた行まで実行し、次のコマンドを出します。
LIST *ptr ;
次の例で示されるように、初期化されていないヒープ・ストレージの充てんバイトを見ることができます。
LIST * ptr ; (* ptr).next = 0xFDFDFDFD (* ptr).i = -33686019
未定義の関数の呼び出し、または NULL を指す関数ポインターを使用した関数の呼び出しは、重大エラーとなります。このような呼び出しが実行される直前に停止するには、次のブレークポイントをセットします。
AT CALL 0
Debug Tool がこのブレークポイントで停止した場合、GO BYPASS コマンドを入力して CALL をバイパスすることができます。これにより、条件を起こさずにデバッグ・セッションを続行することが できます。