gdb とは GNU が提供しているデバッガ。
地味だが、使える。
今回は例として、
#include <stdio.h> #include <stdlib.h> #include <ctype.h> #define SIZE 15 #define CHRMAX 80 char banmen[SIZE][SIZE]; void view_board(char* data, int size); void init(char* data, int size); char* get_place(char* data, int size); int main() { int winflag = 0; int turn = 0; init(&(banmen[0][0]), SIZE); while(winflag == 0) { system("clear"); view_board(&(banmen[0][0]), SIZE); if(turn % 2 == 1) { printf("It\'s White turn (0). \n"); } else { printf("It\'s Black turn (@). \n"); } *get_place(&(banmen[0][0]), SIZE) = (turn % 2 == 1) ? '0' : '@'; turn ++; if(turn - 1 > SIZE * SIZE) winflag = 3; } switch(winflag) { case 1: printf("White (0) Won!\n"); break; case 2: printf("Black (@) Won!\n"); break; case 3: printf("Draw Game...\n"); break; default: printf("Unknown Error...\n"); break; } return 0; } void init(char* data, int size) { int i, j; for(i = 0; i < size; i++) { for(j = 0; j < size; j++) { *(data + i * size + j) = '+'; } } } void view_board(char* data, int size) { int i, j; char cr, cl; cr = 'A'; for(i = -1; i < size; i++) { if(i == -1) { cl = 'a'; printf(" "); for(j = 0; j < size; j++) { printf("%c ", cl); cl++; } } else { printf("%c ", cr); cr++; for(j = 0; j < size; j++) { printf("%c ", *(data + i * size + j)); } } printf("\n"); } printf("\n"); } char* get_place(char* data, int size) { char tmp[CHRMAX]; int correct_code = 1; while(correct_code == 1) { printf("Plase input place code (aa, ab, ..., oo): "); if(fgets(tmp, CHRMAX, stdin)) { printf("EOF Error.\n"); exit(1); } tmp[0] = tolower(tmp[0]); tmp[1] = tolower(tmp[1]); correct_code = 0; if(!((tmp[0] >= 'a' && tmp[0] <= 'o') && (tmp[1] >= 'a' && tmp[1] <= 'o'))) { correct_code = 1; printf("Invalid format. [Rawcharactor][Colcharactor]\n"); } else if(*(data + (tmp[0] - 'a') * size + tmp[1] - 'a') != '+') { correct_code = 1; printf("There is already placed. Choose another.\n"); } } return data + (tmp[0] - 'a') * size + tmp[1] - 'a'; }
をデバッグしてみましょう。
行番号で参照しますので、自分の好きなエディタなどにコピーして行番号を確認してください。
さて、コンパイルします。コンパイルする時は -g オプションを付けてデバッグ情報を生成してもらいます。
$ gcc -o gomoku gomoku.c -g
では、デバッグを開始しましょう。
$ gdb gomoku
で開始します。gomoku は実行ファイル名です。
実行すると、メッセージとともにプロンプトが登場します。
[lugia@lugia-castle gomoku]$ gdb gomoku GNU gdb Momonga Linux 5 (6.8-7m.mo5) Copyright (C) 2008 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i686-momonga-linux"... (gdb)
ブレークポイントがなにもないと普通に実行するのとおなじですので、とりあえずブレークポイントを設定しましょう。
(gdb) break main
これで main 関数の先頭に達した時(実は main 関数の前にもいくらか処理がある)、プログラムがポーズします。
設定すると
(gdb) break main Breakpoint 1 at 0x8048515: file gomoku.c, line 14.
となり、このブレークポイントの番号(1)、メモリアドレス、ソースファイル名、そして行番号が表示されます。
では実行しましょう。
(gdb) run Starting program: /home/lugia/Projects/class/gomoku/gomoku warning: linux_test_for_tracefork: unexpected result from waitpid (8160, status0x57f) Breakpoint 1, main () at gomoku.c:14 14 int winflag = 0; (gdb)
実行する時は run を使います。run に続けてプログラムに渡す引数を指定できます。
さて、main 関数の先頭で止まりました。
表示されている14行目の
int winflag = 0;
は次に実行する内容です。では、いまの winflag の中身を確認してみましょう。
(gdb) print winflag $1 = 134514889
変数の中身を知りたい時は print [変数名] でできます。0 じゃないので、まだ実行されていないことがわかりますね。
変数は型を気にせずに表示できます。
では、先に進めましょう。
コマンド名 | 意味 |
---|---|
continue | 次のブレークポイントにぶつかるまで実行する |
step | 1行実行する。サブルーチン(関数)がある場合は中へはいる。ループの2回目以降も1行ずつ実行する。 |
until | 1行以上実行する。サブルーチン(関数)には入らずその次の行まで実行する。ループの2回目以降はループを抜けるまで一度に実行する。 |
とりあえず、step で先に進めていきましょう。
(gdb) step 15 int turn = 0; (gdb) print winflag $2 = 0
0が代入されましたね。
先に進めます。
(gdb) step 17 init(&(banmen[0][0]), SIZE); (gdb) init (data=0x8049c20 "", size=15) at gomoku.c:54 54 for(i = 0; i < size; i++) {
なにも打たずにリターンキーを押すと前に実行したコマンドが実行されます。
いちいち step とか打つのは面倒ですからね。
init というサブルーチンに入ったのがわかりますか?
このとき引数はポインタ data は空の文字列、size は 15 が渡されたことを示しています。
また、このサブルーチンは gomoku.c の 54 行目にあるものであることがわかります。
(gdb) init (data=0x8049c20 "", size=15) at gomoku.c:54 54 for(i = 0; i < size; i++) { (gdb) step 55 for(j = 0; j < size; j++) { (gdb) step 56 *(data + i * size + j) = '+'; (gdb) 55 for(j = 0; j < size; j++) { (gdb)
2重ループの内側の2回目に来ています。ここで until するとどこまで実行するでしょうか。
よく見てみてください。
55 for(j = 0; j < size; j++) { (gdb) until 54 for(i = 0; i < size; i++) { (gdb)
つまり、一番内側のループを抜けるまで一気に実行されました。
もう一度 until します。
54 for(i = 0; i < size; i++) { (gdb) until 59 } (gdb)
ループを全部抜けるまで一度に実行されました。
ここで step か until すれば、main 関数に戻るはずです。
59 } (gdb) step main () at gomoku.c:19 19 while(winflag == 0) { (gdb)
gomoku.c にある main 関数の 19 行目に戻ってきました。
(gdb) until 20 system("clear"); (gdb) 21 view_board(&(banmen[0][0]), SIZE); (gdb)
さて、ここで until するとどこまで実行するでしょうか。
21 view_board(&(banmen[0][0]), SIZE); (gdb) until a b c d e f g h i j k l m n o A + + + + + + + + + + + + + + + B + + + + + + + + + + + + + + + C + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + F + + + + + + + + + + + + + + + G + + + + + + + + + + + + + + + H + + + + + + + + + + + + + + + I + + + + + + + + + + + + + + + J + + + + + + + + + + + + + + + K + + + + + + + + + + + + + + + L + + + + + + + + + + + + + + + M + + + + + + + + + + + + + + + N + + + + + + + + + + + + + + + O + + + + + + + + + + + + + + + 23 if(turn % 2 == 1) { (gdb)
view_board 関数には入らず、main 関数で次の行まで実行されます。
(gdb) until 26 printf("It\'s Black turn (@). \n"); (gdb) It's Black turn (@). 29 *get_place(&(banmen[0][0]), SIZE) = (turn % 2 == 1) ? '0' : '@'; (gdb) Plase input place code (aa, ab, ..., oo): aa EOF Error. Program exited with code 01. (gdb)
Please input ... には aa, ab, ac, ..., ao, ba, bb, bc, ..., ..., oo のどれかを入れてください。
「Program exited with code 01.」というメッセージが見えますね。
プログラムは終了した・・・ということ見たいですが、その後で step を実行すると
Program exited with code 01. (gdb) step The program is not being run.
と、プログラムは実行されていませんと叱られます。
さて、では EOF Error. というメッセージを頼りに、ソースコードを修正してみましょう。
どこかわからない場合には、もう一度 run して get_place に step で入っていきましょう。
get_place にブレークポイントを設定しても良いかもしれません。
fgets 関数は
while(fgets(...)) {
と言う形で実行した時 EOF にぶち当たるまでループします。なので*1、92行目は
if(fgets(tmp, CHRMAX, stdin)) {
ではなく
if(!fgets(tmp, CHRMAX, stdin)) {
と言うことがわかりますね。
エディタで修正したら一度 gdb を抜けましょう。
(gdb) quit [lugia@lugia-castle gomoku]$
quit で抜けられます。
コンパイルしなおして再度 gdb へ。
[lugia@lugia-castle gomoku]$ gdb gomoku GNU gdb Momonga Linux 5 (6.8-7m.mo5) Copyright (C) 2008 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i686-momonga-linux"... (gdb) break get_place Breakpoint 1 at 0x804877c: file gomoku.c, line 88. (gdb) run Starting program: /home/lugia/Projects/class/gomoku/gomoku warning: linux_test_for_tracefork: unexpected result from waitpid (8483, status 0x57f) Detaching after fork from child process 8485. a b c d e f g h i j k l m n o A + + + + + + + + + + + + + + + B + + + + + + + + + + + + + + + C + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + F + + + + + + + + + + + + + + + G + + + + + + + + + + + + + + + H + + + + + + + + + + + + + + + I + + + + + + + + + + + + + + + J + + + + + + + + + + + + + + + K + + + + + + + + + + + + + + + L + + + + + + + + + + + + + + + M + + + + + + + + + + + + + + + N + + + + + + + + + + + + + + + O + + + + + + + + + + + + + + + It's Black turn (@). Breakpoint 1, get_place (data=0x8049c20 '+' <repeats 200 times>..., size=15) at gomoku.c:88 88 int correct_code = 1; (gdb)
main 関数では無く get_place 関数にブレークポイントを設定したので、そこまで実行されました。
さて、修正した内容で正しいかどうか確認してみましょう。
88 int correct_code = 1; (gdb) step 90 while(correct_code == 1) { (gdb) 91 printf("Plase input place code (aa, ab, ..., oo): "); (gdb) 92 if(!fgets(tmp, CHRMAX, stdin)) { (gdb) Plase input place code (aa, ab, ..., oo): aa 96 tmp[0] = tolower(tmp[0]); (gdb)
EOF Error にはいかず、正しい方向に行きますね。
step で、サブルーチンがあっても、デバッグ情報の無い関数には立ち入りません。なので、printf にも fgets にも入っていかなかったのです。
なお、printf などの出力はフラッシュされるまで出てきません。
フラッシュされるのは以下の時。
- '\n'を出力した時。
- 入力をもらおうとする時。
while 文の場合は条件が先頭にありますので、一度先頭に戻ってから条件を満たさなかったらループの最後に飛びます。
96 tmp[0] = tolower(tmp[0]); (gdb) 97 tmp[1] = tolower(tmp[1]); (gdb) 98 correct_code = 0; (gdb) 99 if(!((tmp[0] >= 'a' && tmp[0] <= 'o') && (tmp[1] >= 'a' && tmp[1] <= 'o'))) { (gdb) 102 } else if(*(data + (tmp[0] - 'a') * size + tmp[1] - 'a') != '+') { (gdb) 90 while(correct_code == 1) { (gdb) 108 return data + (tmp[0] - 'a') * size + tmp[1] - 'a'; (gdb)
ここで、bt というコマンドを実行してみましょう。
なんと出てきますかね?
(gdb) bt #0 get_place (data=0x8049c20 '+' <repeats 200 times>..., size=15) at gomoku.c:108 #1 0x080485a7 in main () at gomoku.c:29
bt は backtrace の略で、いままでに関数を呼び出された経歴を表示します。
始めに main 関数が呼ばれ、その次に get_place 関数が呼び出されています。
大きいプログラムをデバッグしたりすると、たまに step で中に入れない関数があったりします。
しかし、その中で Segmentation Fault が起きていて原因を突き止めたい場合には、Segmentation Fault (SIGSEGV *2と表記される)になってから(まだ実行されている)、bt を実行すると、呼出履歴がわかるので、より深いところにブレークポイントを設定できて、Segmentation Fault の根元がわかる場合があり、活躍しますので覚えておくと良いでしょう。
では、続けましょう。
一度プログラムを終了させます。(強制)終了させる時は kill を使います。
(gdb) kill Kill the program being debugged? (y or n) y
そしたら、設定したブレークポイントを一度削除して、引数に
< list.csv
(list.csv ファイルの中身は後述)
を付けます。
(gdb) delete breakpoint 1
でブレークポイント1 が削除されました。
では引数を付けて実行します。
(gdb) run < list.csv ...(中略)... Plase input place code (aa, ab, ..., oo): a b c d e f g h i j k l m n o A @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ B 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 C @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ D 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 E @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ F 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 G @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ H 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 I @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ J 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 K @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ L 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 M @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ N 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 O @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ It's White turn (0). Plase input place code (aa, ab, ..., oo): EOF Error. Program exited with code 01. (gdb)
このように、リダイレクトも OK ですのでデータの打ち込みが面倒な時などに活躍します。
さて、このプログラムは五目並べをする書きかけのプログラムですが、全部埋まった場合には EOF Error ではなく、「引き分け」のはずです。
31 行目と 32行目で全部埋まったかどうかをターン数をカウントすることで調べようとしています。
ですから、turn が 224 になったあたりで止めたいですね。
そんな事もできますよ。
まず、gomoku.c の 31行目にブレークポイントを設定します。
gomoku.c 以外の特定の行番号にブレークポイントを設定するには一度そのファイルのところまで実行する必要があります。
(gdb) break 31 Breakpoint 1 at 0x80485d7: file gomoku.c, line 31.
これで設定されました。では実行しましょう。
(gdb) run < list.csv Starting program: /home/lugia/Projects/class/gomoku/gomoku < list.csv warning: linux_test_for_tracefork: unexpected result from waitpid (9186, status 0x57f) Detaching after fork from child process 9188. a b c d e f g h i j k l m n o A + + + + + + + + + + + + + + + B + + + + + + + + + + + + + + + C + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + E + + + + + + + + + + + + + + + F + + + + + + + + + + + + + + + G + + + + + + + + + + + + + + + H + + + + + + + + + + + + + + + I + + + + + + + + + + + + + + + J + + + + + + + + + + + + + + + K + + + + + + + + + + + + + + + L + + + + + + + + + + + + + + + M + + + + + + + + + + + + + + + N + + + + + + + + + + + + + + + O + + + + + + + + + + + + + + + It's Black turn (@). Breakpoint 1, main () at gomoku.c:31 31 turn ++; (gdb)
そしたら、一度31行目のブレークポイントを削除して、turn が 224 になったらブレークを発動するように変えます。
(gdb) delete breakpoint 1 (gdb) break 31 if turn == 224 Breakpoint 2 at 0x80485d7: file gomoku.c, line 31.
そしたら、continue しましょう。
(gdb) continue ...(中略)... Plase input place code (aa, ab, ..., oo): a b c d e f g h i j k l m n o A @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ B 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 C @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ D 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 E @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ F 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 G @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ H 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 I @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ J 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 K @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ L 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 M @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ N 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 O @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 + It's Black turn (@). Breakpoint 2, main () at gomoku.c:31 31 turn ++; (gdb) print turn $1 = 224
となり、224 になった時にブレークしました。
ここから実行の様子を見ていくことにしましょう。
(gdb) step 32 if(turn - 1 > SIZE * SIZE) winflag = 3; (gdb) 19 while(winflag == 0) { (gdb) print winflag $2 = 0
今回はまだ抜けなくて大丈夫ですね。
(gdb) step 20 system("clear"); (gdb) Detaching after fork from child process 9465. 21 view_board(&(banmen[0][0]), SIZE); (gdb) until Plase input place code (aa, ab, ..., oo): a b c d e f g h i j k l m n o A @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ B 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 C @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ D 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 E @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ F 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 G @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ H 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 I @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ J 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 K @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ L 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 M @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ N 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 O @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ 23 if(turn % 2 == 1) { (gdb) print turn $3 = 225 (gdb) step 24 printf("It\'s White turn (0). \n"); (gdb) It's White turn (0). 29 *get_place(&(banmen[0][0]), SIZE) = (turn % 2 == 1) ? '0' : '@'; (gdb) get_place ( data=0x8049c20 "@00@@0@0@0@0@0@0@@00@0@0@0@0@0@00@@0@0@0@0@0@0@@00@0@0@0@0@0@00@@0@0@0@0@0@0@@00@0@0@0@0@0@00@@0@0@0@0@0@0@@00@0@0@0@0@0@00@@0@0@0@0@0@0@@00@0@0@0@0@0@00@@0@0@0@0@0@0@@00@0@0@0@0@0@00@@0@0@0@0@0@0@@00"..., size=15) at gomoku.c:88 88 int correct_code = 1; (gdb) 90 while(correct_code == 1) { (gdb) 91 printf("Plase input place code (aa, ab, ..., oo): "); (gdb) 92 if(!fgets(tmp, CHRMAX, stdin)) { (gdb) 93 printf("EOF Error.\n"); (gdb)
となってしまいます。
つまり、224の時点では見掛け上はまだ空きがありますが、その回に入力したもので盤面はいっぱいになります。
したがって turn が 225 になった時点で抜ければよいことになります。
(最初の print は turn++ をまだ実行していませんから。)
つまり、32行目を
if(turn == SIZE * SIZE) winflag = 3;
と直せばよいのでは無いでしょうか。
これを修正すると
(gdb) run < list.csv ...(中略)... Plase input place code (aa, ab, ..., oo): a b c d e f g h i j k l m n o A @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ B 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 C @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ D 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 E @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ F 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 G @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ H 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 I @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ J 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 K @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ L 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 M @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @ N 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0 O @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 + It's Black turn (@). Plase input place code (aa, ab, ..., oo): Draw Game... Program exited normally. (gdb)
となり、oo に @ が入って引き分けですと表示されます。(実際にはもっと先に決着が付いているが、そこはまだ作成されていない。)
お疲れ様でした。これでひとまず gomoku.c は期待通りの動作をするプログラムになりました。
以下再現用の list.csv
aa ba ca da ea fa ga ha ia ja ka la ma na oa ab bb cb db eb fb gb hb ib jb kb lb mb nb ob ad bd cd dd ed fd gd hd id jd kd ld md nd od ac bc cc dc ec fc gc hc ic jc kc lc mc nc oc ae be ce de ee fe ge he ie je ke le me ne oe af bf cf df ef ff gf hf if jf kf lf mf nf of ag bg cg dg eg fg gg hg ig jg kg lg mg ng og ah bh ch dh eh fh gh hh ih jh kh lh mh nh oh ai bi ci di ei fi gi hi ii ji ki li mi ni oi aj bj cj dj ej fj gj hj ij jj kj lj mj nj oj ak bk ck dk ek fk gk hk ik jk kk lk mk nk ok al bl cl dl el fl gl hl il jl kl ll ml nl ol am bm cm dm em fm gm hm im jm km lm mm nm om an bn cn dn en fn gn hn in jn kn ln mn nn on ao bo co do eo fo go ho io jo ko lo mo no oo