RubyやPythonなどのスクリプト言語では実行中に例外が発生するとバックトレースを出力してくれます。バックトレースがあるとどこで問題が発生したかがわかるためデバッグに便利です。一方、CやC++では不正なメモリアクセスをすると、バックトレースではなくcoreを残して1終了します2。デバッガーでcoreを解析するとバックトレースを確認できます。
このように、CやC++でデバッグするときにデバッガーはなくてはならない存在です。スクリプト言語にもデバッガーはありますが、デバッガーを使わなくてもデバッグできる範囲が広いため、CやC++をデバッグするときのほうがデバッガーのありがたさがわかります。
この記事では、広く使われているデバッガーであるGDBをもっと便利に使うためのGCCのコンパイルオプション-g3を紹介します。
サンプルプログラム
まず、この記事で使うサンプルプログラムを示します。マクロと関数があることがポイントです。
max.c:
/* gcc -o max max.c */
#include <stdlib.h>
#include <stdio.h>
#define MAX(a, b) (((a) < (b)) ? (b) : (a))
static int
max(int a, int b)
{
int max_value;
max_value = MAX(a, b);
return max_value;
}
int
main(int argc, char **argv)
{
int a = 10;
int b = -1;
int max_value;
max_value = max(a, b);
printf("a=%d, b=%d, max=%d\n", a, b, max(a, b));
return EXIT_SUCCESS;
}
次のようにビルドします。
% gcc -o max max.c
次のように実行します。
% ./max
a=10, b=-1, max=10
GDBでバックトレースを表示してみましょう。「(gdb)
」がGDBのプロンプトで「(gdb)
」の後にある「break max
」や「run
」が入力したGDBのコマンドです。
% gdb ./max
GNU gdb (GDB) 7.4.1-debian
Copyright (C) 2012 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".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /tmp/max...(no debugging symbols found)...done.
(gdb) break max
Breakpoint 1 at 0x400510
(gdb) run
Starting program: /tmp/max
Breakpoint 1, 0x0000000000400510 in max ()
(gdb) backtrace
#0 0x0000000000400510 in max ()
#1 0x0000000000400554 in main ()
(gdb) continue
Continuing.
Breakpoint 1, 0x0000000000400510 in max ()
(gdb) continue
Continuing.
a=10, b=-1, max=10
[Inferior 1 (process 12138) exited normally]
(gdb) quit
%
バックトレースは次の部分です。
(gdb) backtrace
#0 0x0000000000400510 in max ()
#1 0x0000000000400554 in main ()
main()
関数の中からmax()
関数を呼び出していることがわかります。
-gオプション
GCCにはデバッグ情報を追加する-gオプションがあります。-gオプションをつけるとファイル名や行番号、引数の値もバックトレースに含まれます。
ビルド方法:
% gcc -g -o max max.c
バックトレース:
(gdb) backtrace
#0 max (a=10, b=-1) at max.c:10
#1 0x0000000000400554 in main (argc=1, argv=0x7fffffffe608) at max.c:21
max()
関数がmax.cの10行目で定義されていることがわかるようになりました。
-O0オプション
デバッグをするときは最適化を無効にしたほうが便利です。これは、最適化をするとソースコードに書かれている処理の順番とコンパイル後の処理の順番が変わることがあるからです。ソースコードに書かれている処理の順番と実際に動く順番が異なると処理を追いにくくなります。そのため、デバッグをするときは最適化を無効にしましょう3。
GCCで最適化を無効にするには-O0オプションを使います。「Optimiaze」の「O」(アルファベット)に最適化レベルの「0」(数字)の組合せです。どちらも似たような字型なので気をつけてください。
ビルド方法:
% gcc -g -O0 -o max max.c
-g3オプション
-gオプションを指定するとファイル名などもわかるようになりますが、GDB内ではマクロを使えません。
マクロを使えないことを確認:
(gdb) print MAX(a, b)
No symbol "MAX" in current context.
ここで使っているMAX()
マクロくらいであれば手で展開しても耐えられますが、groongaのGRN_BULK_HEAD()マクロぐらいになるとやってられません。GDB内でもマクロを使えると便利です。
-g3オプションを指定するとGDB内でマクロを使えるようになります。
ビルド方法:
% gcc -g3 -O0 -o max max.c
マクロを使えることを確認:
(gdb) print MAX(a, b)
$1 = 10
「-g3」は「-g」にデバッグレベル「3」を指定するという意味です。
-ggdb3オプション
多くの環境(パソコン上で動いているLinuxやFreeBSDなど4)では-g3オプションを使うとGDBで便利になります。そのため、本当は-ggdb3オプションのことは気にしなくても構いません。もし、デバッガーとしてGDBを使うということがわかっている場合は-g3オプションの代わりに-ggdb3オプションを使えます。
-ggdb3オプションは「gdb」用のデバッグ情報をデバッグレベル「3」で出力する、という意味です。-ggdb3オプションを使っていると「あぁ、GDB用にビルドしているんだな」ということがわかりやすいという利点があります。一方、GCC以外のコンパイラーとの互換性の問題があります。
Clang 3.2以降では-ggdb3オプションも受け付けますが、それ以前では警告になります。
% clang -ggdb3 -o max max.c
clang: warning: argument unused during compilation: '-ggdb3'
%
ただ、Clangでは-gオプションでも-g3オプションでも動作は変わらず、-g3オプションを指定してもGDB内でマクロを使うことはできません。
改めて書きますが、多くの環境では-g3も-ggdb3も同じ動作になるため、通常は-g3オプションを使っていれば十分です。
ファイルサイズ
ビルド済みのバイナリファイルを配布するときはファイルサイズに気をつけてください。-gオプションでは1.X倍くらいファイルサイズが大きくなるくらいですが、-g3オプションを指定すると倍以上ファイルサイズが大きくなります。
% gcc -O0 -o max-no-g max.c
% gcc -g -O0 -o max-g max.c
% gcc -g3 -O0 -o max-g3 max.c
% ls -lhS max-*
-rwxr-xr-x 1 kou kou 35K 5月 9 10:55 max-g3
-rwxr-xr-x 1 kou kou 8.3K 5月 9 10:55 max-g
-rwxr-xr-x 1 kou kou 6.8K 5月 9 10:55 max-no-g
ユーザーはGDBでデバッグしないはずなので、そのために倍以上ファイルサイズが大きくなるとうれしくありません。バイナリファイルを配布するときは-g3オプションなしでビルドするか、ビルドしたファイルからデバッグ情報を取り除いてから配布してください。参考までにstrip
コマンドでデバッグ情報を取り除いたときのファイルサイズを示します。-gオプションなしでビルドした時よりもさらに小さくなっています。
% strip -o max-g3-stripped max-g3
% ls -l --human-readable --sort=size max-*
-rwxr-xr-x 1 kou kou 35K 5月 9 10:55 max-g3
-rwxr-xr-x 1 kou kou 8.3K 5月 9 10:55 max-g
-rwxr-xr-x 1 kou kou 6.8K 5月 9 10:55 max-no-g
-rwxr-xr-x 1 kou kou 4.5K 5月 9 10:57 max-g3-stripped
ただ、-gあり・なし・strip済みは、-g3指定のときに比べればそれほど違いがないので、多くのケースでは-g3を指定するとき以外はファイルサイズをそんなに気にしなくてもよいでしょう。
configureでの指定の仕方
configureを使ってビルドするシステムでは以下のようにconfigure
の引数にCFLAGSを指定することでデバッグ情報付きでビルドできます。
% ./configure CFLAGS="-g3 -O0" ...
次のように環境変数ではなくconfigure
の引数としてCFLAGSを指定していることがポイントです。
% CFLAGS="-g3 -O0" ./configure ...
環境変数として指定するとconfigure.ac
を編集するなどして自動でconfigure
が再実行されたときにCFLAGSは指定されません。一方、configure
の引数と指定しておくと再実行されたときでもCFLAGSが指定されます。そのため、環境変数ではなく引数として指定しておいた方がよいです。
まとめ
GDBで便利にデバッグするためのGCCの-g3オプションを紹介しました。
CやC++でGDBを使ってデバッグするときは使ってみてください。
-
limit
やulimit
でコアファイルのサイズを制限している場合はcoreを残さないこともあります。 ↩ -
catchsegv ./a.out
というようにcatchsegv
コマンド経由で実行するとC/C++で書いたプログラムでもバックトレースを出力します。 ↩ -
最適化を有効にしたときだけ発生する問題もあるため、最適化を無効にすると問題が再現しなくなることがあります。そのときは最適化を有効にしたままデバッグしましょう。 ↩
-
もっと興味がある場合はGCCのソースを確認してください。gcc/opts.cの
set_debug_level()
やgrep -r "#define PREFERRED_DEBUGGING_TYPE" gcc/config | grep -v DWARF2_DEBUG
などを確認するとよいでしょう。 ↩