GDBでC言語のプログラムを効率的にデバッグする方法を教えて!
こういった悩みにお答えします.
本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Verse(UEFN), Assembler (x64,ARM).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
こういった私が解説していきます.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
目次
GNU Debugger(GDB)
GNU Debugger(GDB)は,GNUプログラムの実行の変更や追跡する機能を提供するデバッガです.
また,プログラムの変数の値を修正したり,監視したりすることもできます.
GDBがサポートしているプログラミング言語は,Ada,C/C++言語,Objective-C,Pascal,FORTRAN,FreeBASIC,Goです.
これまでにC言語で開発したプログラムを実行する時に,「セグメンテーション違反(segmentation fault)」と出力されて強制終了したことがあると思います.
その際,printfデバッグ(printf関数でセグメンテーション違反した場所を探すこと)は面倒ですよね.
そのセグメンテーション違反した場所を簡単に特定できるのがGDBなのです.
※マルチスレッドプログラミングでは,printfデバッグが便利なことがあります.
UbuntuでGDBをインストールする場合は以下のコマンドを実行して下さい.
1 |
$ sudo apt-get install gdb |
GDBの簡単な利用例
GDBの簡単な利用例を紹介します.
GDBで実行するC言語のサンプルコードは以下になります.
このコードでは,NULL番地のアドレスに123を代入します.
もちろん実行するとセグメンテーション違反になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> int main(void) { int *p = NULL; /* do something. */ *p = 123; /* do something. */ return 0; } |
gdb.cファイルをGDBのオプション「-g -ggdb」を付けてビルドします.
1 |
$ gcc gdb.c -g -ggdb |
GDBなしで実行
GDBなしで実行します.
セグメンテーション違反(segmentation fault)が発生しました...
1 2 |
$ a.out segmentation fault (core dumped) a.out |
GDBありで実行
GDBありで実行します.
18行目でrunコマンドを入力して実行すると,21行目にセグメンテーション違反が発生し,22~23行目に発生した場所がgdb.cファイルの13行目であることがわかります.
24行目でquitコマンドを入力して終了します.
このようにGDBを利用するとセグメンテーション違反した場所が簡単に特定できます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
$ gdb a.out GNU gdb (Ubuntu 12.0.90-0ubuntu1) 12.0.90 Copyright (C) 2022 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 "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <https://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from a.out... (gdb) run Starting program: /home/chishiro/c-language/gdb/a.out qui[Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". tProgram received signal SIGSEGV, Segmentation fault. 0x000055555555513d in main () at gdb.c:13 13 *p = 123; (gdb) quit |
このコードは短いのでセグメンテーション違反が発生する場所を簡単に見つけられます.
しかし,数万行以上の大規模なソフトウェアの場合,全部読んでセグメンテーション違反を発生する場所を見つけるのはとても大変ですよね.
GDBは大規模なソフトウェアで効果を発揮します!
GDBによるステップ実行
GDBによるステップ実行を解説します.
ステップ実行とは,プログラムを1行ずつ(または1命令ずつ)実行することで変数等の値がどのように変化するのかを分析することができます.
ステップ実行を利用することで,効率的にデバッグすることができます.
ステップ実行するコードは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #define square(x) ((x) * (x)) #define DEFAULTMAX 5 int func_a(int x) { int y; y = square(x) + x + 1; return y; } int main(int argc, char *argv[]) { int x, y, maxnum; if (argc > 2) { fprintf(stderr, "Usage: %s [maxnum]\n", argv[0]); exit(1); } else if (argc == 2) { maxnum = atoi(argv[1]); } else { maxnum = DEFAULTMAX; } for (x = 0; x < maxnum; x++) { y = func_a(x); printf("x = %d, y = %d\n", x, y); } return 0; } |
以下のようにビルドします.
1 |
$ gcc gdb2.c -g -ggdb |
GDBなしで実行
まず,GDBなしで実行します.
引数は3を設定すると以下の結果になりました.
1 2 3 4 |
$ a.out 3 x = 0, y = 1 x = 1, y = 3 x = 2, y = 7 |
GDBありで実行
次に,GDBありで実行します.
19行目のrunコマンドの引数に3を設定するとプログラムの引数に3が設定されます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
$ gdb a.out GNU gdb (Ubuntu 12.0.90-0ubuntu1) 12.0.90 Copyright (C) 2022 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 "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <https://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from a.out... (No debugging symbols found in a.out) (gdb) run 3 Starting program: /home/chishiro/c-language/gdb/a.out 3 [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". x = 0, y = 1 x = 1, y = 3 x = 2, y = 7 [Inferior 1 (process 27925) exited normally] |
GDBありでステップ実行
GDBでステップ実行するためにGDBを起動した直後に,以下のようにmain関数にブレークポイントを設定してします.
ブレークポイントを設定すると,プログラムの実行が,そのブレークポイントを設定した場所で中断します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$ gdb a.out GNU gdb (Ubuntu 12.0.90-0ubuntu1) 12.0.90 Copyright (C) 2022 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 "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <https://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from a.out... (gdb) break main Breakpoint 1 at 0x11df: file gdb2.c, line 24. |
以下のようにコマンドラインから3を与えて実行してみましょう.
するとブレークポイントが設定されるので,プログラムの実行が始まり,main関数で実行が中断します.
1 2 3 4 5 6 7 |
(gdb) run 3 Starting program: /home/chishiro/c-language/gdb/a.out 3 [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Breakpoint 1, main (argc=2, argv=0x7fffffffdb88) at gdb2.c:24 24 if (argc > 2) { |
ブレークポイントは以下の場所で設定できます.
- 関数名
- 絶対行数
- 相対行数
- アドレス
上記の例では,関数名で指定しています.
設定されているbreak pointは以下のようにすれば確認できます.
1 2 3 4 |
(gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x00005555555551df in main at gdb2.c:24 breakpoint already hit 1 time |
また,現在実行中のプログラムリストを表示するためには,以下のようにlistコマンドを実行します.
1 2 3 4 5 6 7 8 9 10 11 |
(gdb) list 19 20 int main(int argc, char *argv[]) 21 { 22 int x, y, maxnum; 23 24 if (argc > 2) { 25 fprintf(stderr, "Usage: %s [maxnum]\n", argv[0]); 26 exit(1); 27 } else if (argc == 2) { 28 maxnum = atoi(argv[1]); |
listはデフォルトでは10行分(現在実行中の行の±5行分)の表示を行います.
引数として行数を指定すれば,その行から10行表示します.
また,以下のようにカンマで区切って表示行の最初と最後を指定できます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
(gdb) list 10,25 10 11 int func_a(int x) 12 { 13 int y; 14 15 y = square(x) + x + 1; 16 17 return y; 18 } 19 20 int main(int argc, char *argv[]) 21 { 22 int x, y, maxnum; 23 24 if (argc > 2) { 25 fprintf(stderr, "Usage: %s [maxnum]\n", argv[0]); |
次にnextコマンドを利用してステップ実行します.
nextコマンドを利用すると,このように1行だけ実行できます.
1 2 |
(gdb) next 27 } else if (argc == 2) { |
argcが2と等しいかどうかのif文の判定式になりました.
引数に3と入力したのでargcの個数は2(実行ファイル名と引数1個)になり,このif文は偽になるはずです.
実際のargcの値をprintコマンドで表示してみましょう.
以下のようにargcは2に正しく設定されていることがわかります.
1 2 |
(gdb) print argc $1 = 2 |
以下のように実行してみましょう.
maxnumに値が代入する前(実行される前)は不定値(この場合は0,$2=0から確認)が入っています.
28行目の実行後には正しくmaxnumに3が代入されていることが確認できます.
1 2 3 4 5 6 7 8 |
(gdb) next 28 maxnum = atoi(argv[1]); (gdb) print maxnum $1 = 1431654592 (gdb) next 33 for (x = 0; x < maxnum; x++) { (gdb) print maxnum $2 = 3 |
次に,以下のように34行目(y = func_a(x)の行)にブレークポイントを設定し,continueコマンドで継続実行してみます.
以下のように34行目に設定されたブレークポイントまで継続実行します.
1 2 3 4 5 6 7 8 9 10 11 12 |
(gdb) break 34 Breakpoint 2 at 0x555555555242: file gdb2.c, line 34. (gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x00005555555551df in main at gdb2.c:24 breakpoint already hit 1 time 2 breakpoint keep y 0x0000555555555242 in main at gdb2.c:34 (gdb) continue Continuing. Breakpoint 2, main (argc=2, argv=0x7fffffffdb88) at gdb2.c:34 34 y = func_a(x); |
さらに,以下のように実行してみましょう.
このように,ステップ実行をするコマンドには,これまで利用してきたnextコマンドの他にstepコマンドがあります.
nextコマンドとstepコマンドの違いは関数を呼ぶ際にあります.
nextコマンドは,その関数の内部に入ることなく,その関数を全て実行します(ステップアウト実行).
これに対して,stepコマンドは,その関数の内部に入って実行します(ステップイン実行).
以下の例では,func_a関数を実行する際に,nextコマンドとstepコマンドのそれぞれを利用します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
(gdb) next 35 printf("x = %d, y = %d\n", x, y); (gdb) print x $3 = 0 (gdb) print y $4 = 1 (gdb) next x = 0, y = 1 33 for (x = 0; x < maxnum; x++) { (gdb) next Breakpoint 2, main (argc=2, argv=0x7fffffffdb88) at gdb2.c:34 34 y = func_a(x); (gdb) step func_a (x=1) at gdb2.c:15 15 y = square(x) + x + 1; (gdb) list 10 11 int func_a(int x) 12 { 13 int y; 14 15 y = square(x) + x + 1; 16 17 return y; 18 } 19 (gdb) next 17 return y; (gdb) next 18 } (gdb) print x $5 = 1 (gdb) print y $6 = 3 |
ブレークポイントの設定
上記の例でも紹介しましたが,ブレークポイントは「info breakpoints」で表示できます.
ブレークポイントは,設定した順に番号(Num)が付くようになっています.
1 2 3 4 5 6 7 |
(gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x00005555555551cc in main at gdb2.c:21 breakpoint already hit 1 time 2 breakpoint keep y 0x000055555555523f in main at gdb2.c:34 breakpoint already hit 2 times (gdb) |
例えば,「delete 2」とすると2番目の(34行目に設定した)ブレークポイントを削除できます.
1 |
(gdb) delete 2 |
ブレークポイントを削除するのではなく,一時的に無効化するには以下のようにします.
1 |
(gdb) disable 2 |
無効にした場合,「info breakpoints」と入力すると以下のようにEnb(Enable)がnになっている(disableになっている)ことがわかります.
1 2 3 4 5 6 |
(gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x00005555555551df in main at gdb2.c:24 breakpoint already hit 1 time 2 breakpoint keep n 0x0000555555555242 in main at gdb2.c:34 breakpoint already hit 2 times |
もし有効にしたい場合は以下のコマンドを実行して下さい.
1 |
(gdb) enable 2 |
再度「info breakpoints」を入力するとEnb(Enable)がyになっていることがわかります.
1 2 3 4 5 6 |
(gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x00005555555551df in main at gdb2.c:24 breakpoint already hit 1 time 2 breakpoint keep y 0x0000555555555242 in main at gdb2.c:34 breakpoint already hit 2 times |
ループの途中等でブレークポイントを設定すると必ずそこで中断してしまいます.
ブレークポイントには条件を設定できます.
例えば,以下のように設定した場合,xが2の場合にのみ34行目で中断するようになります.
1 2 3 4 5 6 7 8 9 10 11 |
(gdb) break 34 if x == 2 Note: breakpoint 2 also set at pc 0x555555555242. Breakpoint 3 at 0x555555555242: file gdb2.c, line 34. (gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x00005555555551df in main at gdb2.c:24 breakpoint already hit 1 time 2 breakpoint keep y 0x0000555555555242 in main at gdb2.c:34 breakpoint already hit 2 times 3 breakpoint keep y 0x0000555555555242 in main at gdb2.c:34 stop only if x == 2 |
GDBの短縮コマンド
GDBのコマンドには短縮コマンドがありますので,使いこなせるようにしましょう(下表).
コマンド | 短縮コマンド | 説明 |
---|---|---|
break | b | ブレークポイントの追加 |
continue | c | プログラムの再開 |
delete | d | ブレークポイントの削除 |
list | l | コードを表示 |
next | n | ステップアウト実行 |
p | 式を評価して結果を表示 | |
quit | q | GDBの終了 |
run | r | プログラムの実行 |
step | s | ステップイン実行 |
まとめ
GDBでC言語のプログラムを効率的にデバッグする方法を紹介しました.
GDBは初見では使いこなすのが難しい開発ツールですが,使いこなせばC言語のプログラミングがとても楽になりますので,是非身につけましょう!
GDBを深く理解したいあなたは,以下のサイトが参考になります.
- 日本語:gcc+gdbによるプログラムのデバッグ
- 英語:GDBの公式マニュアル
プログラムの実行を記録,再生できるLinux用デバッグツール「rr」を知りたいあなたはこちらからどうぞ.
デバッグに有用な言語のアサーション機能,静的コード解析ツールと動的プログラム解析ツールを知りたいあなたはこちらからどうぞ.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!