(作成:2005/10)
「プログラムは思った通りに動かない。書いた通りに動く」
「バグを起こさない唯一の方法はプラグラムを組まないこと」
「運用後のデバグはバグを呼ぶ」
「三つのデバグは一つのバグを産む」
。。。等と言われる世知辛い世の中。少しはデバグ作業も報われたって良いじゃない。という訳で、デバグに関する事など。
そういえばデバッグて言葉変だよね。bug(虫)なのに。でもDebuggerはデバッガて書くのよね。bug(虫)なのに。
GDBはGNUプロジェクト用Debugger。どういう物かってーと、以下のような事が出来るものらしい。和訳合ってるかな(ぉ
- GDB: The GNU Project Debugger
- プログラムを起動して、その振舞いから生じる恐れのある影響について特定できる
- 特定の状態でプログラムを止められる
- プログラムが停止した時に何が起こったか調査できる
- プログラム変更時、バグ修正の影響を実験して他の作業に取り掛かれる
デバグ可能にする
まずはごく簡単なプログラムを書いてみる。
#include<stdio.h>
// test.c //
void getData(int* data) {
char buf[256];
int index = 0;
do {
scanf("%s", buf);
if(atoi(buf) != -1) {
data[index] = atoi(buf);
index++;
}
}while(atoi(buf) != -1);
data[index] = -1;
}
int main(int argc, char* argv[]) {
int data[10];
int* ptr;
getData(data);
ptr = data;
while(*ptr != -1) {
printf("%d ", *ptr);
ptr++;
}
printf("\n");
exit(0);
}
内容は、入力待ち状態から文字列をスペース区切りで順に取得、 -1
以外の値を int
型にして配列に仕込み、ポインタを用いて表示するというもの。
別にバグは無いけれど。ソースに深い意味も無いけれど。
プログラムをDebuggerに掛ける際には、OS毎に固有のデバグ情報が必要になる。例えばGNU C/C++の場合は以下のようなコンパイルオプションでデバグ情報を作成してくれる。
$ cc -g test.c
``
オプション無しと比べると、容量が大分増えてる。
```shell
$ cc hoge.c; ls -l a.out
-rwxrwxr-x 1 riyo local 7700 2005-10-26 17:21 a.out
$ cc -g hoge.c; ls -l a.out
-rwxrwxr-x 1 riyo local 13168 2005-10-26 17:21 a.out
$
Debuggerで実行
以下のコマンドでGDBを起動。
$ gdb [実行ファイル名]
...
(gdb)
Emacsでプログラムを組んでるなら M-x gdb
で実行ファイル名を指定して起動できる。こちらはソースを追いながらデバグ出来るんでかなり判り易い。
取り敢えず起動。 r
の代わりに run
と打っても良い。
(gdb) r
Starting program: /home/riyo/Program/a.out
4 3 5 2 6 -1 (手入力)
4 3 5 2 6
Program exited normally.
(gdb)
まぁ、問題無く動きますな。
ある場所で一端止める
例えば main()
関数の最初の方で止めたければ以下のように。 b
の代わりに break
と打っても良い。
(gdb) b main
Breakpoint 1 at 0x80484e7: file hoge.c, line 21.
(gdb) r
Starting program: /home/riyo/Program/a.out
Breakpoint 1, main (argc=1, argv=0xbffffa74) at hoge.c:21
21 getData(data);
(gdb)
変数宣言部では止まってくれないみたいね。まぁ良いけど。
ブレークポイントを削除したい場合は以下の2通りがある。前者は行数等による指定、後者はブレークポイント番号による指定。
それぞれ、 cl
は clear
、 d
は delete
と打っても良い。
(gdb) cl main
Deleted breakpoint 1
(gdb)
(gdb) d 1
(gdb)
そうでなく一時的に無効化/有効化したい場合は以下の通り。それぞれ dis
は disable
、 en
は enable
と打っても良い。
(gdb) dis 1
(gdb) r
Starting program: /home/riyo/Program/a.out
4 3 5 2 6 -1 (手入力)
4 3 5 2 6
Program exited normally.
(gdb) en 1
(gdb) r
Starting program: /home/riyo/Program/a.out
Breakpoint 1, main (argc=1, argv=0xbffffa74) at hoge.c:21
21 getData(data);
(gdb)
一行ずつ処理する
一行ずつ処理するなら以下のコマンド。 n
の代わりに next
と打っても良い。Emacsなら C-c C-n
。
(gdb) n
4 3 5 2 6 -1 (手入力)
23 ptr = data;
(gdb) n
24 while(*ptr != -1) {
(gdb)
でもこれだと getData()
の方で本当にうまい事処理が行なわれてるか疑問。てな場合はステップ実行すれば getData()
関数内の方も一行ずつ見てくれる。
s
の代わりに step
と打っても良い。Emacsなら C-c C-s
。
(gdb) s
getData (data=0xbffff9c0) at hoge.c:5
5 int index = 0;
(gdb) s
8 scanf("%s", buf);
(gdb)
また、現在位置からまた暫く普通に実行したいなーと思ったら以下のようにする。
当然ながら、再び停止させたい地点にブレークポイントを追加しておかないと最後まで実行しちゃうので注意。
c
の代わりに continue
と打っても良い。Emacsなら C-c C-r
。
(gdb) b 27
Breakpoint 2 at 0x804851f: file hoge.c, line 27.
(gdb) c
Continuing.
4 3 5 2 6 -1 (手入力)
Breakpoint 2, main (argc=1, argv=0xbffffa74) at hoge.c:28
28 printf("\n");
(gdb)
変数の値を見る
以下、上記で n
による実行例に続く。
この辺で、そろそろ変数等にどんな値が代入されてるか確認してみたいなーと思ったら以下のようにして見ることが出来る。 p
の代わりに print
と打っても良い。
(gdb) p argc
$1 = 1
(gdb) p data
$2 = {4, 3, 5, 2, 6, -1, -1073743368, 134514010, 1075134452, 134514096}
(gdb)
配列に対して表示する長さを決めたいならこんな感じ。
(gdb) p *data@5
$3 = {4, 3, 5, 2, 6}
(gdb) p data[5]@2
$4 = {-1, -1073743368}
(gdb)
じゃあポインタは?
(gdb) p ptr
$5 = (int *) 0xbffff9c0
(gdb)
ptr
指定だけだと内容は表さない。まぁポインタだし。
でもソースを見れば、このポインタは data[]
を指しとる事が分かるので、こうすれば見られ。。。
(gdb) p *ptr
$6 = 4
(gdb)
。。。まぁ、ポインタだからな。
内容表示には、配列の長さを決める時と同じように指定してやれば良い。
(gdb) p *ptr@6
$7 = {4, 3, 5, 2, 6, -1}
(gdb)
因みに、ウチの環境ではここで初期化等のされてない *argv[]
を見てみると。。。
(gdb) p *argv
$8 = 0xbffffbae "/home/riyo/Program/a.out"
(gdb) p *argv[0]
$9 = 47 '/'
(gdb) p argv[1]
$10 = 0x0
(gdb) p *argv[1]
Cannot access memory at address 0x0
(gdb) p argv[2]
$11 = 0xbffffbca "SSH_AGENT_PID=1985"
(gdb) p argv[3]
$12 = 0xbffffbdd "MRXVT_TABTITLE=Terminal"
(gdb)
。。。。。。バッファオーバーフローの危険性って、こういう事なのかしらね。