過去ログ 13
過去ログは下にいくほど新しい記事となっております。
※注意:このサイトにあるプログラムのソースコードや、その他の情報はすべて無保証です。やばいと思ったら実行しないでください。コンパイル、実行などによって生じた損害に対する責任は負いませんので、ご了承ください。
GDB
24-1-2010
gdbはLinuxで動くデバッガ。コマンドラインで動くので、Visual Studioのような便利さはないけど、機能はそんなに悪くはない。
使い方は簡単で、まずプログラムを作成する。とりあえず、簡単なもので。
#include <stdio.h> int main(){ printf("Hello World!\n"); return 0; }
説明はもはやいらないHello Worldプログラム。分からなかったら←のGCCのページを見てちょうだい。
これをコンパイルする時に、-gのオプションをつけます。
$ gcc test.c -g
コンパイル成功したら、gdbコマンドで実行します。
gdb a.out
a.outはプログラム名です。別の名前にした場合は、それにしてください。
実行すると、GPLv3やらNO WARRANTYやら、よく見かけるメッセージが出てきて、コマンド入力待ちになります。
(gdb)
ここで、コマンドを入力すれば、プログラムが動いたり、止まったりします。
runで、プログラムが動きます。
(gdb) run
後述するブレイクポイントがなければ、プログラムは終了するか、エラーが発生して強制終了するまで停止しません。
はじめから一行ずつ見ていきたい、という場合は、runではなく、startを使います。
(gdb) start
実行すると、プログラムの最初の行で止まります。
Temporary breakpoint 2, main () at test.c:4 4 printf("Hello World!\n"); (gdb)
ここから1行づつ見ていきたい場合はstepもしくはnextを、次のブレイクポイント、あるいはプログラム終了まで飛ばしたい場合はcontinueをします。
nextとstepの違いは関数の中に入るかどうかです。stepは関数の中まで入り、nextは中までは入りません。
void func2(){ return; } void func1(){ func2(); /* <-- */ return; }
矢印で示したところで止まっているとして、stepを実行した場合、次に止まる行はfunc2のreturn、nextを実行した場合は、func1のreturnになります。もっとも、nextであっても、func2の中で止まらないだけで、func2は実行しています。
なお、プログラムが終了しても、gdbは終了しません。gdbを終了する時は、quit、面倒だったら、qをタイプし、エンターキーを押します。
(gdb) q
また、gdbは終了したくないけど、テストしているプログラムを終了したい場合は、killで終了できます。
さて、一行一行実行していくときに変数の中身を見たいというときがあります。そんなときは、displayを使います。
#include <stdio.h> int main(){ int i = 10; printf("number = %d\n",i); i++; printf("number = %d\n",i); return 0; }
これをgdbで実行すると、
(gdb) start Temporary breakpoint 1 at 0x40052c: file test7.c, line 4. Starting program: /PATH/TO/PROJECT/a.out Temporary breakpoint 1, main () at test.c:4 4 int i = 10; (gdb)
このプログラムではiの値が変化しますので、それを見たいですね。そこで、displayを使います。displayに続けて、見たい変数名を入力します。
(gdb) display i 1: i = 0
今現在iは0であることがわかりました。iに10が代入される前ですから。
このdisplayはundisplayされたり、何らかの原因で無効になるまで表示されます。
表示したくなくなったときは、左側の数字、「1: i = 0」の場合、1を指定します。
(gdb) undisplay 1
今回はiの値を追っていきたいので、表示させ続けます。特に中に入る必要のある関数もないので、nextで進めます。
(gdb) next 5 printf("number = %d\n", i); 1: i = 10 (gdb) number = 10 6 i++; 1: i = 10 (gdb) 7 printf("number = %d\n", i); 1: i = 11 (gdb) number = 11 8 return 0; 1: i = 11 (gdb) 9 } 1: i = 11
強調表示した部分はprintfで出力されているものです。i++でiが10から11に変わっていることが確認できます。
なお、nextを1回入力しておけば、次に別のコマンドを入力するまで、入力を省略(エンターキーだけでnextを実行)できます。stepなど他のコマンドも同様です。
ポインタの場合はどうでしょう?
#include <stdio.h> int main(){ int *p; int i = 10; p = &i; printf("number = %d\n", *p); i++; printf("number = %d\n", *p); return 0; }
printfの行までは特になにもないので飛ばし、最初のprintfで一旦停止させたいですね。そんな時は、ブレイクポイントを作ります。ブレイクポイントには2種類あり、消すまで消えないブレイクポイントと、一度だけ有効な一時的なブレイクポイントがあります。前者はbreak、後者はtbreakで作成できます。
ブレイクポイントの作成には行番号、関数名を指定します。関数名を指定した場合、関数の最初の行で停止します。ここでは関数はmainのみなので、行番号を調べなければなりません。gdbで調べるには、listコマンドを使用します。
(gdb) list 1 #include <stdio.h> 2 3 int main(){ 4 int *p; 5 int i = 10; 6 p = &i; 7 printf("number = %d\n", *p); 8 i++; 9 printf("number = %d\n", *p); 10 return 0; (gdb)
実行すると、現在の行の前後5行を表示します。行番号、関数名を指定することもでき、行番号の場合は、指定された行番号の前後5行、関数名を指定した場合は関数の最初から前後5行が表示されます。
なお、list実行後、もう一度エンターキーを押すと、次の10行が表示されます。
実行結果により、最初のprintfが7行目にあることが分かったので、7行目にブレイクポイントをおき、実行します。
(gdb) break 7 Breakpoint 1 at 0x40053b: file test.c, line 7. (gdb) run Starting program: /PATH/TO/PROJECT/a.out Breakpoint 1, main () at test.c:7 7 printf("number = %d\n", *p); (gdb)
最初のprintfで止まりました。
displayでポインタを見てみると、
(gdb) display p 1: p = (int *) 0x7fffffffe24c (gdb)
と、アドレスが表示されました。NULLポインタを調べるのには有効ですが、今回は中身を調べたいので、これでは意味がありません。ポインタの中身を表示したい場合は、実際のプログラムでやるように*pとします。
(gdb) display *p 2: *p = 10 (gdb)
ポインタの中身が分かりました。
これ以降は先ほどと同じなので流しましょう。
(gdb) continue Continuing. number = 10 number = 11 Program exited normally. (gdb)
正常終了しました。
では、変数が構造体の場合はどうでしょう?
#include <stdio.h> typedef struct { int a; int b; } test; int main(){ test t; test *p; t.a = 10; t.b = 23; p = &t; printf("number a = %d\n", p->a); printf("number b = %d\n", p->b); return 0; }
displayでいろいろな値を見てみます。C言語の記述と同じ方法で表示できるので、
Breakpoint 1, main () at test.c:14 14 printf("number a = %d\n", p->a); (gdb) display t 1: t = {a = 10, b = 23} (gdb) display p 2: p = (test *) 0x7fffffffe240 (gdb) display *p 3: *p = {a = 10, b = 23} (gdb) display p->a 4: p->a = 10 (gdb) display t.a 5: t.a = 10 (gdb)
構造体の中の変数も表示できます。これは、構造体の中にポインタが含まれている時に有効で、
#include <stdio.h> typedef struct { int a; int b; int *p; } test; int main(){ int i = 13; test t; test *p; t.a = 10; t.b = 23; t.p = &i; p = &t; printf("number a = %d\n", p->a); printf("number b = %d\n", p->b); return 0; }
のとき、display *pでは
(gdb) display *p 1: *p = {a = 10, b = 23, p = 0x7fffffffe24c}
と、ポインタ部分の中身が表示できませんので、
(gdb) display *p->p 2: *p->p = 13
↑のようにして、表示させます。
また、displayはキャストさせることができます。
たとえば、下のプログラム
#include <stdio.h> typedef struct { int a; int b; int *p; } test; void func(void* arg){ test *obj; obj = (test*)arg; printf("number = %d\n", obj->a); } int main(){ int i = 13; test t; test *p; t.a = 10; t.b = 23; t.p = &i; p = &t; func(p); return 0; }
func内で、void*からtest*にキャストしていますが、キャスト前に、渡された時点で、ちゃんと値が渡されているかチェックしたいときなど、void*はそのままでは表示できませんので、キャストして表示させます。
(gdb) tbreak func Temporary breakpoint 1 at 0x400530: file test.c, line 11. (gdb) run Starting program: /PATH/TO/PROJECT/a.out Temporary breakpoint 1, func (arg=0x7fffffffe230) at test.c:11 11 obj = (test*)arg; (gdb) display *(test*)arg 1: *(test*)arg = {a = 10, b = 23, p = 0x7fffffffe24c} (gdb) display *(*(test*)arg)->p 2: *(*(test*)arg)->p = 13
ちゃんと値が渡されていることが分かります。キャストは、わけあってchar*やvoid*で値を保持している場合などに有効です。
なお、C++の場合(g++でコンパイル)、クラスのアップキャスト、ダウンキャストの結果も見ることができます。普通に表示させると変数のみ(継承元のものもすべて)、メソッドのアドレスを知りたい場合は、メソッドを指定すれば見ることができます。
(gdb) ((subclass*)object)->method 1: ((subclass*)object)->method = {void ( subclass *)} 0x404970 <subclass::method()>
のように表示されます。
今まで値を表示させることをやってきましたが、デバッグ中に、変数に自分で決めた値をセットして動きを見たいというときがあります。その場合は、set variableを実行します。
(gdb) set variable t.a = 20 (gdb) display t 1: t = {a = 20, b = 0, p = 0x4003e0} (gdb)
t.aにデバッグ中に入れた20が入っていることが分かります。
最後にエラーが発生するプログラムをgdbを使ってエラーの原因(バグ)を発見し、直してみます。
#include <stdio.h> typedef struct { int a; int b; int *p; } test; void func(void* arg){ test *obj; obj = (test*)arg; obj->a = 100; } int main(){ test t; test *p; func(p); return 0; }
そのまま実行すると、エラーになります。
$ ./a.out Segmentation fault
gdbを使って実行すると
(gdb) run Starting program: /PATH/TO/PROJECT/a.out Program received signal SIGSEGV, Segmentation fault. 0x00000000004004d8 in func (arg=0x0) at test7.c:12 12 obj->a = 100; (gdb)
エラーが発生した場所で停止します。
1行だけではよく分からない場合は、listで前後を表示します。
(gdb) list 7 } test; 8 9 void func(void* arg){ 10 test *obj; 11 obj = (test*)arg; 12 obj->a = 100; 13 } 14 15 int main(){ 16 test t; (gdb)
引数argが0x0で、それをobjにセットしているので、NULLに値をセットしようとしているので、エラーになっていると考えられます。念のためobjを見てみると、
(gdb) display obj 1: obj = (test *) 0x0
NULLのようです。
そこで呼び出し元の方を調べてみると、
(gdb) list main 10 test *obj; 11 obj = (test*)arg; 12 obj->a = 100; 13 } 14 15 int main(){ 16 test t; 17 test *p; 18 func(p); 19 return 0; (gdb)
pにアドレスをセットせずに渡しているのが原因のようです。
そこで、次のように直します。
#include <stdio.h> typedef struct { int a; int b; int *p; } test; void func(void* arg){ test *obj; obj = (test*)arg; obj->a = 100; } int main(){ test t; test *p = &t; func(p); return 0; }
pのアドレスを見ながら、1行ずつ実行していくと、
(gdb) start Temporary breakpoint 1 at 0x4004e8: file test.c, line 17. Starting program: /PATH/TO/PROJECT/a.out Temporary breakpoint 1, main () at test.c:17 17 test *p = &t; (gdb) display p 1: p = (test *) 0x0 (gdb) step 18 func(p); 1: p = (test *) 0x7fffffffe230 (gdb) func (arg=0x7fffffffe230) at test.c:11 11 obj = (test*)arg; (gdb) 12 obj->a = 100; (gdb) 13 } (gdb) main () at test.c:19 19 return 0; 1: p = (test *) 0x7fffffffe230 (gdb)
pにアドレスがセットされ、それがfuncの引数argに正しく渡されているようです。
実際に実行してみても
$ ./a.out $
エラーが発生しなくなりました。
なお、今回書いたコマンドはgdbのごく一部で、もっと多くのコマンドを知りたい場合は、gdbのコマンドでhelpを実行するとヘルプが表示されます。help allですべてのコマンドが表示されますので、それで調べるといいでしょう。
※メール、リンクは適当に。
Copyright (c) 2010 greencap