デバッグ作業とDDDデバッガによるデバッグ
デバッグ(debug, 虫取り)とは,作成したプログラムの間違いを発見して直す作業のことであり,デバッガ(debugger)とは異常動作するプログラムの動作を追跡し誤りを見つけ出すためのツールであり,プログラムが期待した動作をしているか検証したり,うまく動作しない原因を追及するために用いる.
通常のデバッグ作業
プログラム中の要所にprintf()関数やfprintf(stderr,"・・・",・・・)関数を埋め込み,プログラムの実行中の変数や配列要素の値が想定通りに正しく代入されているかどうかを確認する.
例:
int array[5];
for(i = 0; i < 5; i++) {
....
printf("array[%d] = %d", i, array[i]); /* これで配列要素が正しいかどうか確認 */
....
}
基本的なデバッグは,上記ように変数の値を画面表示して行うのが一般的だが,それだけでは不十分な場合もある.デバッガは,プログラムが異常終了した位置を調べたり,プログラムの実行を順番に追跡して変数の値などを確認しながら実行させることができる.
Linuxで利用できるデバッガにgdbがある.これはキーボードから各種のコマンドを入力して操作するもので,初心者には若干使い難い.これに対しウィンドウ画面上でマウス操作が使えるようにしてくれるgdbのフロントエンドであるdddの簡単な使い方を説明する.
dddはgdbのGUI版であり,コマンドなどを全てマウスによるボタン操作で行える. また,ソースリストも同時に表示できる機能を持っている.
dddデバッガの使用法まず,デバック対象となるプログラムを -g オプションを付けてコンパイルする.
% gcc -g sample.c
これにより実行ファイル a.out にデバッグ情報が付加される(注:-oオプションで実行可能形式ファイルの名前を指定した場合は、その名前のファイルに付加される).
この実行ファイルの名前を引数としてdddを起動する.
% ddd a.out &
これにより,ddd操作ウインドウが表示される.
dddの操作ウィンドウ
このウィンドウは3つの表示領域を持ち,上部が変数表示領域,中央がソースコード表示領域,下部がコマンドライン表示入力領域となっている.また,これとは別にデバッグ実行を制御するコマンドボタンウィンドウも表示される.コメント部分の日本語が上手く表示出来ないのが残念だが,特に支障はないだろう.
コマンドボタンウィンドウ
ここで、コマンドボタンの[run]をクリックすると,コマンドラインに run
コマンドが入力されプログラムが実行される.これはktermなどのターミナルから実行ファイルを起動した場合と同様に動作する.キ−ボ−ド入力もコマンドライン領域で行う.キーボード操作が必要なくエラーも出なければ,そのまま最後まで実行される.
[実行ファイルの指定]
もし実行ファイルを指定せずにdddを起動してしまったり,途中で他のプログラムのデバッグへ切り替える場合は,FileメニューからOpen Programアイテムを選択するとファイル選択ダイアログが表示されるので,デバッグしたい実行形式プログラムを選択する.
[dddの終了]
FileメニューからExitアイテムを選択する.またはコマンドラインで quit
又は q
と打ち込む.
存在しないメモリアドレスを参照した場合などにでる Segmentation fault エラーはプログラムの実行中に出るエラーであり,エラーの発生場所のわかりづらい 厄介なエラーの一つである. ここでxxgdbデバッガ使うとエラーの発生した箇所を調べることができる.
まず,前節で示した方法で実行ファイルにデバッグ情報を付加し,その実行ファイルを対象にxxgdbを立ち上げる.次に[run]ボタンをクリックしてプログラムを実行する. Segmentation faultが表示されたら [stack]ボタンをクリックすると,エラーが発生した箇所が表示される.
[実際のデバッグ例]
次のようなtest.cというプログラムを仮定する.このプログラムは実行するとSegmentation fault(セグメンテーション違反)エラーが出て停止しまう.
#include <stdio.h> main() { int a = 0; scanf( "%d", a ); printf( "%d¥n", a ); }
まず,デバッグ情報を付加してコンパイルし直す.
% gcc -g test.c
次に,ここで生成された実行ファイル(a.out)を対象にdddを起動する.
% ddd a.out &
これで操作ウインドウが表示される.
次に,コマンドウィンドウの[run]ボタンをクリックしてプログラムを実行し,コマンドラインに Segmentation faultが表示された時,StatusメニューからBacktraceアイテムを選択すると次のようなウィンドウが表示される.
これは,関数呼び出しスタックの状態を表示するものである.この例の場合,これでmain()関数からscanf()関数が呼び出されており,さらにscanf()関数から_IO_vfscanf()という関数(C言語の標準関数で使われている内部関数)でエラーが起きていることが分かる.
これにより,エラー発生箇所となっている関数が判別できるので,それを手がかりとして間違いの原因を探すこと. 注意することとして,Segmentation faultは、エラー発生箇所そのものが 原因となっている場合はあまり多くない.特にポインタを 用いている場合にはその傾向が顕著である.
しかし,デバッガによって発生箇所が分れば、そこから原因を類推することは難しくないはずである.その位置より前に エラーの原因があることが分かるだけでも利用価値は十分にある.
また,Segmentation faultよりも厄介なものとして ,「プログラムは問題なく動作しエラーは起きないが,得られる結果や動作が想定通りにならない」 とか「概ね正しく動くが,時々上手く動かない」などという症状がある.
一般的なデバッグ作業では,prinf()などを要所々々に挿入して色々な変数の値を表示させながらデバッグするのであるが,printf()は毎回変数を表示するので不必要に多くの情報が表示されてしまい,かえって分かりにくくなってしまうことがある.そこで,プログラムの実行を一時停止し,例えば繰返し処理のある特定付近を詳細に少しずつ調べたい場合などに 役にたつのがdddのbreak point設定とステップ実行機能である.
これまでと同様,実行ファイルにデバッグ情報を付加し,それを対象にしてdddを立ち上げる.
[breakpointの設定]
ソースファイルが表示されているウインドウで,break pointを設定したい行の先頭をクリックしてカーソルを表示させ,そこで右クリックして表示されるメニューからSet Breakpointアイテムを選択すると,STOPアイコンが表示される.これが breakpointが設定されていることを示す.設定が終わったら、[run]ボタンで実行を開始する.
実行が進み,breakpointを設定した場所まで来ると処理が一旦停止する.ここで,その時点での変数の内容の表示などの操作を行う.
また,Set Temporary Breakpointアイテムは一回限り有効なbreakpointを設定する.一回限り有効とは,プログラムの実行が一旦停止するのは最初の一回だけであり,プログラム実行が再開されて再度temporary breakpointに到達しても停止はしないことを意味する.
[break pointでの変数の内容表示]
ソースウインドウで、表示させたい変数名をドラッグして選択反転させる.そこで右クリックする."Print 変数名"アイテムを選ぶとコマンドラインに変数の中身が表示される."Display 変数名"アイテムを選択すると,変数表示領域に変数の内容が表示され,break pointで停止した時点での変数の内容を常に表示してくれる.また,ポインタ変数の場合,"Print *変数名"及び"Display *変数名"のアイテムを選択すると,ポインタ変数が示す先(メモリ番地)の内容を表示してくれる.
[一旦停止時からの操作]
contボタン: Continue操作を意味し,次にbreak pointに到達するまで実行続行する.
nextボタン: 一行ずつ実行する.ただし関数呼出し時は,関数内部のコードは表示せずに関数の実行を完了させ,その次の行に制御が移る(コマンドラインからnext 数値
とすると、その数値の行数分をまとめて実行する).
step 数値
とすると,その数値の行数分をまとめて実行する). [break pointの解除]
breakpointを表すSTOPアイコンを右クリックしてDelete Breakpointを選択すると,そのbreakpointは解除される.一時的に解除したい場合は,Disable Breakpointを選択し,一時解除していたbreakpointを再開するには,Enable Breakpointを選択する.