Unix系コマンドラインユーザーのための、 gcc/g++/g77 による開発におけるデバッグ術を簡単に紹介します。
以下の内容は gcc 2.7.2.3 での動作は確認しています。 g++/g77 でも恐らくは通用すると思うのですが、 ひょっとすると異なる部分があるかもしれません。 筆者は g++/g77 の使用経験がないので、その場合は御容赦を願います。
実行前
キーワード「コンパイルオプション, -Wall, -O2, -O4」
まずは gcc にオプション opt
'-Wall'
を付けてコンパイルし、 警告がなくなるまでソースを修正します。 これは 常識 です。次に opt
'-O4 -Wall'
でコンパイルします。 「未初期化変数の使用」の警告 (`foo' might be used uninitialized in this function) は、 opt'-O4'
を付けないとチェックしてくれないようです。 でもコンパイルに時間がかかるんですよね。 「コンパイルなんかあっという間に終わっちゃうよ」という 高速なマシンを使っている幸せな人は、 いきなり opt'-O4'
をつけておいてもいいでしょう。さてさて、実際に動かす場合は opt
'-O4'
でも構いませんが、 これから紹介するデバッグには opt'-O2'
ぐらいが好都合です。 デバッグが終われば opt'-O4'
にしてもいいでしょうが、 「コンパイラの最適化バグ」に当たるのが恐い心配性の人は opt'-O2'
のままにしておきましょう。 「速く動けば何でもOK」というチャレンジャーな人は opt'-O5'
や opt'-O6'
を検討してもよいでしょうが、 かえって遅くなることもあります。 このあたりの動作はコンパイラのバージョンによってかなり違いますので、'man gcc'
などで調べてみて下さい。ようやく実行 -
"Segmentation fault!!"
キーワード「core, gdb, -ggdb」
ここまで来て、はじめてプログラムを実行させます。ちゃんと動きましたか? もし、
"Segmentation fault"
で止まってしまうようなら、 ソースのどの部分で止まるか調べたいですよね。"core"
というファイルができるなら、これを手がかりに調査しましょう。それにはコンパイルの時点からやり直した方が得策です。 opt
'-ggdb'
を付けてコンパイルしましょう。 後述 するように opt'-lefence'
も付けると、 なお良いでしょう。 opt'-O4'
はやめて opt'-O2'
ぐらいにしておきましょう。"core"
のサイズが0バイトの場合は、 sh/bash 系では'ulimit -c'
コマンド、 csh/tcsh 系では'limit -h'
コマンドで、"core"
のファイルサイズ制限を確認しましょう。そして再コンパイル&実行させて、 もう一度
"core"
を吐かせてから次を実行します。
% gdb a.out core
こうすると、止まった関数の呼び出された引数と、 関数の中での止まった場所がわかります。 gdb 中で
'list'
コマンドでソース表示、'where'
コマンドで関数の呼出し関係が表示されます。 これを手がかりに修正すると、非常に効率的です。opt
'-ggdb'
なしにコンパイルしていると、 ここで表示される情報が少なくなってしまいます。普通に
printf()
キーワード「デバッグライト, fflush, ANSI, エスケープシーケンス」
さてさて、一通り実行できるようになりました。 でも、挙動不審な点があるのはいつものことですね。 そんな時は、まずはデバッグライト。
printf()
で情報表示しましょう。 「そんなん常識やん」ですが、printf( "%s \r", message ); fflush( stdout );
なんて技をお教えしましょう。 改行せずに同じ位置に情報表示できます。即座に表示するのがfflush()
。 ANSI を使えば、色も付けられますし、表示位置も移動できます。 (内容の詳細は後日更新します。)ElectricFence を使おう
キーワード「efence, core, -lefence」
printf()
1つ入れるだけで"core"
を吐かなくなった、 なんてことはありませんか。 原因は、初期化してない変数を使ってるか、 配列あふれであることが多いです。こんな時には "A malloc(3) debugger" である ElectricFence を使いましょう。 いえ、初めから、何も起こらなくても使っておくべきです。 配列をオーバーフローすると、即座に止まってくれるようになります。 opt
'-lefence'
を付けてコンパイルしておくと、 問題の箇所で"core"
を吐いて止まるようになります。 これで、前述 のように gdb を使えば、 どこで止まるかわかります。ElectricFence が導入されていない場合は、システム管理者にお願いしましょう。 システム管理者の方は、以下の手順で導入して下さい。 (多くの Unix で使えるようですが、詳しくは付属の
"README"
で確認して下さい。) まずは 本体, パッチその1, パッチその2 をコピーして下さい。そして以下のように操作します。
% tar xzvf ElectricFence-2.0.5.tar.gz % cd ElectricFence-2.0.5/ % patch -p1 < ../ElectricFence-2.0.5-glibc.patch % patch -p1 < ../ElectricFence-2.0.5-longjmp.patch % vi Makefile --- OSに合わせて必要な編集を行う しかし install に $(MV) するのは いただけないなぁ ここは $(INSTALL) にしないとユーザー権限のファイルができてしまう --- % make --- 最後に "Electric Fence confidence test PASSED" が表示されれば成功 --- % make -n install --- 実行される内容を確認 --- % su --- ここで初めて root 権限に --- # make install # cd /usr/man/man3/ # ln -s libefence.3 efence.3 --- 'man libefence' 'man efence' 共に使えた方が親切でしょう ---
ソースとパッチはElectricFence-2.0.5-11.src.rpm (ソースパッケージ)から取り出しました。
参考文献:memory leak check高速化とデバッグに役立つ profiler
キーワード「gprof, -pg, 関数呼び出し回数, 計算時間」
「この関数は1度きりしか実行しないはずなのに、何度も実行されているようだ」 なんて時には profiler を使いましょう。 本来 profiler は、高速化のポイントを探るための道具ですが、 デバッグにも役立ちます。
それには、opt
'-pg'
を付けてコンパイルします。 最適化は opt'-O2'
ぐらいを指定します。 そしてプログラムを実行します。 すると"gmon.out"
というファイルができます。 できてないようだと、リンク時に opt'-pg'
が付いていないことが考えられます。'gprof a.out'
を実行すると、 実行した関数の一覧と関数の呼び出し回数が表示されます。 これでちょっとは参考になるでしょう。また、本来の使い方ですが、関数ごとの計算時間がわかります。 「1つの関数での計算時間が全体の50%」なんてのはザラなので、 高速化する場合はこういう関数から攻撃しましょう。
gcc の最適化オプションについて
キーワード「-O?, -pg, -ggdb」
コンパイラ一般には、デバッグオプションを指定すると 最適化オプションが無効になってしまうことが多いそうですが、 gcc では opt
'-pg'
や opt'-ggdb'
を付けても opt'-O?'
の最適化と両立します。opt
'-O4'
は関数のインライン展開を行うので、'gprof'
の表示で、あるはずの関数が表示されないことがあります。 これは、「関数がマクロかのように」呼び出し元に展開されて、 独立した関数として存在していないためです。 opt'-O2'
にするとそんなことは起こらなくなるので、 デバッグ中はこれを常用することになるのですが、 たまには opt'-O4'
にしてやらないと、前述のように 「未初期化変数の使用」警告を出してくれないんですよね。 opt'-O3'
はどうだったかなぁ?うーん、調べてみて下さい。(^^;)終わりに
一般に、デバッグのためのオプションを付けると、 実行時に計算時間が余計にかかります。 完成品には使わないようにしましょう。 デバッグライトは、後々のためにコメントで残しておくのは当然ですね。
効率の良いデバッグのための基本は、こんなところでしょうか。 もちろん gdb バリバリとか、 MS-Windows によくある統合環境のような開発ツールを使うのも良い方法ですが、 「コマンドラインから gcc、printf()(デバッグライト)しか使わないよ」 という人にとっては、このぐらいがちょうど良い、 いやいや、必要十分かつ最適な方法だと思います。 もっと便利な方法があったら、教えて下さいね。