初出:SoftwareDesign 2001/2月号
     第2特集 UNIXプログラミングツールをマスターせよ

          -- デバッガのしくみをしろう --

                                                くわむら じゅん
                                                juk@yokohama.email.ne.jp

========================================================================

●デバッガとは何か?

  デバッガを英語で書くと debugger です。de-bug-ger と書けばバグを無く
すための道具のことだと一目瞭然ですね。ところで、このバグを日本では虫と
呼びますが、プログラムの中あちこちに潜んでいる誤りのことです。プログラ
ムを書いているときに自然に紛れこんで、プログラムを走らせているときにそ
の虫にでくわすとエラーとなります。自然に紛れ込むとはおかしなことですが、
プログラムを書くのが人間であれば多かれ少なかれ必ず間違いはつきものです。
デバッガはその虫を捜すための道具です。今どき、デバッガを使わないでプロ
グラム開発をする人はほとんどいないでしょう。「××ビルダー」や「デベロッ
パー××」といったような開発環境には必ずデバッガも含まれていて、プログ
ラムのコンパイルから実行テストまでがユーザ・インターフェースから行なえ
るようになっているはずです。


●プログラムをしていく上での役割

  プログラムを書いて実際にそのプログラムを使う前に、そのプログラムが正
しく動作するかどうかを確かめるためにテストをします。テストをして期待し
た結果にならなければどこかが間違えています。どこかが間違えていることに
気がつくとその原因を捜します。実はこれが大変なのです。プログラムを書い
た本人は決して間違えていないと思っているものです。
  既に、彼は問題に対して苦心しながら最良のやり方を考え出し、何度かコン
パイラを走らせてコンパイル・エラーもすべて取り去ったのでした。いよいよ
結果を拝めるという期待を胸にプログラムを走らせて、そこで彼が見たものは 
"Segmentation Fault" というつれないメッセージでした。もちろん、彼には
そのメッセージの意味はなんとなくわかるのですが、まったく心あたりがあり
ませんでした。そこで、彼はプログラムの節々にプリント文を入れては、コン
パイルと実行を繰り返すのでした。ほどなく、"Segmentation Fault" の原因
は突き止められましたが、まだ、期待した結果にはなりません。結果が出ては
いるのですがその結果がおかしいのです。そこで、彼は既に4色くらいのマー
カで見辛くなったプログラムリストを捨てて新しいリストを印刷しなおしまし
た。そして、腰を据えてプログラムと睨めっこをするのでした。
  こんなときにデバッガは威力を発揮します。プログラムを走らせて、要所要
所で実行を止めて、そのときどきの変数の値を表示させて見ることができます。
プリント文を埋め込むためにコンパイルし直す必要もありません。デバッガを
使うためにはコンパイルのときにオプションの -g を付けておくだけでよいの
です。


●実際のデバッガ

  かつては DBX というデバッガが標準で付いてくるコンピュータもありまし
た。10年くらい前には GNU プロジェクトのおかげで、それより高機能な GDB
(GNU symbolic debugger) も利用可能になっていました。今では、Emacs の他、
xxgdb や DDD(http://www.gnu.org/software/ddd/) のような GUI からも GDB 
が使えるようになっています。もちろん、GDB 以外にも PerlやTcl/Tkのよう
なインタープリタ言語用のデバッガもあります。(*)
  GDBはさすがに GNU製品だけあってEmacsとの親和性もよく、Emacsと一緒
に使うと快適な開発環境になります。この開発環境については4章の方にゆず
ることとにしまして、ここでは、オーソドックスにC言語の簡単なプログラム
を使って、 GDB の最も基本的な使い方について端末上での操作を例に説明し
ます。
  GDBについての詳細は info コマンドなどを参照下さい。日本語の info も
GNUjdoc(http://www.gnu.org/software/gnujdoc/gnujdoc.html) にてみつかり
ます。

(*) 各種デバッガについては、
	 http://www.ee.ryerson.ca:8080/~elf/xapps/Q-IV.html
   にポインタがあります。


 ○GDBデバッガのためのコンパイル

  プログラムの実行に gdb が使えるようにするためには、コンパイルをする
ときに -g オプションを付けてコンパイルしておきます(*1)。ここでは、
List.1 のプログラムを例に説明します。

(*1) gcc の -g オプションは -O オプションと一緒にも使えます。

List.1
--
#include 
#include 
#define TOOBIG 5000

void set_array( int *a );

int main(void)
{
  int ia[10];
  set_array(ia);
  exit(EXIT_SUCCESS);
}

void set_array( int *a )
{
  int i;
  for (i = 0; i < TOOBIG; ++i) {
    a[i] = i;
  }
}
--

このプログラムを myprog.c というファイルに保存して、myprog という実行
形式を作ってみます。

	% gcc -g myprog.c -o myprog^M

そして、このプログラムを走らせると、

	% ./myprog^M
	Segmentation fault (core dumped)

となります。"Segmentation fault" は「領域侵害」とも訳されています。
"core dumped" を「ゲロ吐いた」などという人もいますが、プログラムが異常
終了したときのメモリー状態が core というファイルに出力されていることを
意味します(*2)。

(*2) シェルのパラメータで、tcsh では coredumpsize が 0 のとき、
    bash では core file size が 0 のときは core ファイルが出力
    されません。

  tcsh は 

	% unlimit coredumpsize

で、bash は

	$ ulimit -c unlimited

で制限を外すことができます。


gdb はこのコア・ファイルから情報を得ることができます。

	% gdb -q myprog core^M

このコマンド行で確認できます(*3)。

(*3) -q は --silent オプションと同じで gdb の著作権情報の出力を抑制します。
   core はコア・ファイルを指定していますが、かわりにプロセス番号で実行中の
   プロセスを指定することも可能です。

	% gdb -q myprog core^M
	Core was generated by `      '.
	Program terminated with signal 11, Segmentation fault.
	Reading symbols from /lib/libc.so.6...done.
	Reading symbols from /lib/ld-linux.so.2...done.
	#0  0x8048440 in set_array ()
	(gdb) 

この出力を見ると set_array ファンクションでエラーがおきたことが #0 か
ら始まる行によってわかります(*4)。List.1 を見ると set_array ファンクショ
ンの中の for ループで、実際に確保されている配列の要素数(10個)を越えて
代入を(5000回)行なおうとしています。


(*4) 実行モジュールを -g オプションでコンパイルしていなくても、strip 
    コマンドで実行プログラム・ファイルからシンボルを捨てさらない限りは、
    エラーのおきたファンクションを調べることができます。



 ○GDBデバッガによる実行

  デバッガのために -g オプション付きでコンパイルして作った実行モジュー
ルをデバッガから起動してみます。まず、プログラム名を引数に gdb を起動
します。

	% gdb myprog^M
	GNU gdb 5.0
	Copyright 2000 Free Software Foundation, Inc.
	GDB is free software, covered by the GNU General Public License, and you are
	welcome to change it and/or distribute copies of it under certain conditions.
	Type "show copying" to see the conditions.
	There is absolutely no warranty for GDB.  Type "show warranty" for details.
	This GDB was configured as "i686-pc-linux-gnu"...
	(gdb) 

そして、デバッガの中で r(run) コマンドを実行します(*5)。

(*5) プログラムに引数を与える場合は、run コマンドに続けてその引数を指定します。

	(gdb) r^M
	Starting program: /home/juk/myprog 

	Program received signal SIGSEGV, Segmentation fault.
	0x8048440 in set_array (a=0xbffff384) at myprog.c:22
	22          a[i] = i;
	(gdb) 

するとプログラムは異常終了しますので、その異常終了した場所を示してgdb 
のプロンプトに戻ります。もう少し詳細にそれが呼び出された場所などを知り
たければ、bt(back trace) コマンドを実行します。

	(gdb) bt^M
	#0  0x8048440 in set_array (a=0xbffff384) at myprog.c:22
	#1  0x8048402 in main () at myprog.c:12
	#2  0xb in ?? ()
	Cannot access memory at address 0xa
	(gdb) 


l(list)コマンドでソースコードを表示して、set_array ファンクション呼び
出しの前に実行を停止してみます(*6)。b(break)コマンドでファンクションの行
を指定します(*7)。

(*6) ソースコードがgdbを起動したディレクトリにない場合は、
    dir(directory) コマンドでソースコードのあるディレクトリへパスを設
    定する必要があります。

(*7) break コマンドの指定には行番号の他にファンクション名も使えます。
    ファイル名と':'で繋いで、"break ファイル名:行番号"
     や "break ファイル名:ファンクション名" のように指定できます。

	(gdb) l^M
	3
	4       #define TOOBIG 5000
	5
	6       void set_array( int *a );
	7
	8       int main(void)
	9       {
	10        int ia[10];
	11
	12        set_array(ia);
	(gdb) b 12^M
	Breakpoint 1 at 0x80483f6: file myprog.c, line 12.
	(gdb) 

プログラムを再実行してみます。gdb からは、既に実行しているプログラムを
中断するのかと聞かれますので y を入力するとプログラムが再起動されます。

	(gdb) r^M
	Starting program: /home/juk/myprog 
	
	Breakpoint 1, main () at myprog.c:12
	12        set_array(ia);
	(gdb) 

ファンクション実行前で停止している場合は、s(step)コマンドを入力すると
そのファンクションの中に入ります。(このときに、step コマンドの代わり
に next コマンドを入力すると、そのファンクションは実行されて、その次
の行まで進みます。)

	(gdb) s^M
	set_array (a=0xbffff384) at myprog.c:21
	21        for (i = 0; i < TOOBIG; ++i) {
	(gdb) s^M
	22          a[i] = i;
	(gdb) 



次に、b(break)コマンドで22行目に休止点(ブレークポイント)を設定して、
プログラムをもう一度走らせてみましょう。

	(gdb) b 22^M
	Breakpoint 2 at 0x8048430: file myprog.c, line 22.
	(gdb) r^M
	The program being debugged has been started already.
	Start it from the beginning? (y or n) y
	Starting program: /home/juk/myprog 
	
	Breakpoint 2, set_array (a=0xbffff384) at myprog.c:22
	22          a[i] = i;
	(gdb) 


gdb は22行目の文を実行する手前で停止します。p(print)コマンドで変数 i 
の値を表示してみます(*8)。そして、n(next)コマンドで、次の行に実行を進
めて、i の値が変わるまで 1ステップずつ先に進めてみましょう。

(*8) printコマンドにスラッシュで書式文字を繋げて出力フォーマットの指定
    ができます。"print/'書式文字' 変数名" のようにコマンドを入力します。
    書式文字には x(16進整数),d(符号付き10進整数),u(符号なし10進整数),
    o(8進整数),t(2進整数),c(文字定数),f(浮動小数点数)などがあります。

	(gdb) n^M
	21        for (i = 0; i < TOOBIG; ++i) {
	(gdb) p i^M
	$1 = 0
	(gdb) n^M

	Breakpoint 2, set_array (a=0xbffff384) at myprog.c:22
	22          a[i] = i;
	(gdb) p i^M
	$2 = 1
	(gdb) 

実はブレークポイントが設定されているので、次のブレークポイントまで
c(continue)コマンドで続けることができます。ここでは、ループの中なので
再び同じ場所で停止することになります。いちいち、print コマンドで値を見
るのは面倒なので、disp(display) コマンドを使って値を自動表示させながら、
continue をしばらく続けてみましょう。(コマンドなしでリターンすると1
つ前のコマンドが再実行されます。)

	(gdb) disp i^M
	1: i = 1
	(gdb) c^M
	Continuing.
	
	Breakpoint 2, set_array (a=0xbffff384) at myprog.c:22
	22          a[i] = i;
	1: i = 2
	(gdb) ^M
	
	Breakpoint 2, set_array (a=0xbffff384) at myprog.c:22
	22          a[i] = i;
	1: i = 3
	(gdb) ^M

	…省略…

	Breakpoint 2, set_array (a=0xbffff384) at myprog.c:22
	22          a[i] = i;
	1: i = 10
	(gdb) ^M
	Continuing.
	
	Breakpoint 2, set_array (a=0xbffff384) at myprog.c:22
	22          a[i] = i;
	1: i = 11
	(gdb) ^M
	Continuing.

	…省略…

不思議なことに、配列aに割り当てていた要素数を越えても処理は続いてます。
これはメモリー配置によるものです。どこまで進むのか、ブレークポイントを
外してみてみます。 i b(info breakpoints) コマンドでブレークポイントの
番号を確認して、d(delete) コマンドで削除してから処理を続けてみます。

	(gdb) i b^M
	Num Type           Disp Enb Address    What
	1   breakpoint     keep y   0x080483f6 in main at myprog.c:12
	        breakpoint already hit 1 time
	2   breakpoint     keep y   0x08048430 in set_array at myprog.c:22
	        breakpoint already hit 16 times
	(gdb) d 2^M
	(gdb) c^M
	Continuing.
	
	Program received signal SIGSEGV, Segmentation fault.
	0x8048440 in set_array (a=0xbffff384) at myprog.c:22
	22          a[i] = i;
	1: i = 799
	(gdb) 



ついでに、条件付きブレークポイントの設定をやってみましょう。変数の値を
条件として実行を停止することができます。現在のブレークポイントと自動表
示の指定を削除して、22行目で i の値が 10 を越えたところで止めてみましょ
う。

	(gdb) del break 1^M
	(gdb) del disp 1^M
	(gdb) b 22 if i > 10^M
	Breakpoint 3 at 0x8048430: file myprog.c, line 22.
	(gdb) r^M
	The program being debugged has been started already.
	Start it from the beginning? (y or n) y
	Starting program: /home/juk/myprog 
	
	Breakpoint 3, set_array (a=0xbffff384) at myprog.c:22
	22          a[i] = i;
	(gdb) p i^M
	$1 = 11
	(gdb) 


ここで、処理を1行進めてから、set var(set variable)コマンドで i に値
5000を設定してみましょう。このこと自体は無意味ですが、プログラムは
エラーを起こさずにforループを抜けられます。


	(gdb) n^M
	21        for (i = 0; i < TOOBIG; ++i) {
	(gdb) set var i= 5000^M
	(gdb) n^M
	24      }
	(gdb) 

基本的な gdb のコマンドはだいたい以上です。最後に gdb を終了するには、
q(quit) コマンドか、Control+D です。プログラムが実行中であれば中断の
確認がありますので、y を入力します。

	(gdb) q^M
	The program is running.  Exit anyway? (y or n) y


  メモリーに関する問題のデバッグに対しては、ここでは触れませんが、
Electric Fence や mpr といったようなプログラムが、
ftp://metalab.unc.edu/pub/Linux/devel/lang/c/ にあるようです。



●GDBデバッガの特徴

  デーモン(daemon)プロセスなどのサーバ・プログラムでは、起動されたプロ
セスが子プロセスをフォーク(fork)して、親プロセスは終了してしまいます。
このようなプログラムでも gdb を使えばデバッグが可能です。gdb は、実行
中のプロセスをアタッチ(attach)して、そのプログラムをデバッグすることが
できます。
  また、クロス開発といわれて、実際にgdbが稼働しているところのマシンと
は異なるアーキテクチャのマシン上のプログラムの開発をターゲットを指定し
て行なったり、リモート・デバッグ機能を使って通常では gdbを実行させるこ
とのできないマシン上で実行中のプログラムをデバッグすることも可能です。

  最後に、gdb の起動時のオプションで初期化ファイルの指定ができますが、
デフォルトでは、ホームディレクトリの .gdbinit になっています。起動時に 
-nx オプションを指定することにより、これを読まないようにすることもでき
ます。逆に、-x オプションを使えば起動時に指定のコマンドファイルを読ま
せることができます。複数のプログラムを同時に動かしてデバグをする必要が
あるときは、プログラム毎にブレークポイントなどをあらかじめ設定したコマ
ンドファイルを用意しておき、シェルから複数のデバグプロセスを1度に立ち
上げると便利なこともあります。参考までに、思いついたサンプルリストを以
下に載せておきます。


myclt.gdbinit
--
exec-file myclt
symbol-file myclt
directory /projects/src/share /projects/src/util
directory ~/src/myclt
set prettyprint on
set unionprint on
--

mysvr.gdbinit
--
exec-file mysvr
symbol-file mysvr
directory /projects/src/share /projects/src/util
directory ~/src/mysvr
--

swat.sh
--
mysvr &
(cat myclt.gdbinit; echo "breake doConnect\nrun") > tmp_myclt.gdbinit
xxgdb -x tmp_myclt.gdbinit &
(cat mysvr.gdbinit; echo "attach `pidof mysvr`\nbr doEstablish") > tmp_mysvr.gdbinit
gdb -x tmp_mysvr.gdbinit
--




参考文献

GNUjdoc GDBバージョン4.18 info
(ftp://duff.kuicr.kyoto-u.ac.jp/pub/gnujdoc/)

"Linux Programing BY EXAMPLE", Kurt Wall, QUE(ISBN 0-7897-2215-1)