2004年01月27日
Cで書かれたCGIをGDBでデバッグする
C言語で書かれたCGIをGDBでデバックする方法についてまとめる。
CGIとして動作するプログラムにGDBを使うための要点をまとめる。
ここでは、実際にWebサーバから起動されるプロセスの動作を、直接GDBを使って解析する方法を紹介する。CGI環境を整えるためのデバック用プログラムを書く必要がなく、オープンソースで配布されている大きなCGIプログラムの動作解析をする場合などに有効である。
1.準備
CGIプログラムにGDBを使えるようにするための変更を行う。
まず、プログラムのソースコードを修正し、起動後に一時停止するように変更する。一時停止するためには、C言語のsleep()関数を利用する。
一時停止を行う場所は、解析対象の前になければならない。main () 関数の先頭や注目している関数の先頭が良い。停止時間は20秒から30秒が経験的に良い。
例えば、main()の先頭に置く場合に、ソースコードは次のようになる。
void main (int argc, char *argv[])
{
sleep(20);
...
}
次に、デバック用オプション(-gオプション)をつけて再コンパイルする。GDBがバイナリとソースコードを対応づけるために必要である。
このとき、最適化オプション(-O, -Oxオプション)はつけないようにしておく。最適化オプションをつけると、ソースコード通りに実行されず、解析が分かりにくくなるためである。
典型的なGNU autoconfを使ったパッケージの場合では、次のようになる。
% ./configure --some-options "CFLAGS=-g"
% make
# make install
そして、GDBを起動しておく。シェルから直接GDBを起動することもできるが、emacsの上でGDBを呼び出して使うと大変便利である。他に、多くのデバッガのGUIインターフェースを提供するdddからGDBを使う方法がある。
CGIを解析中にHTTPコネクションがタイムアウトを起こし、プログラムにSIGPIPEシグナルが到着することがよくある。このシグナルでデバック作業が止まらないように、無視するための設定をしておく。GDBのプロンプトで次のコマンドを実行する。
(gdb) handle SIGPIPE nostop nopass
Signal Stop Print Pass to program Description
SIGPIPE No Yes No Broken pipe
以上で、解析の準備は完了。
2.解析
GDBを使ってCGIプログラムがどう動いているか解析する。
まず、Webブラウザを使って通常通りにCGIにアクセスする。このとき、前記の一時停止が機能すれば、すぐにプロセスは処理を行わず、しばらく結果は返ってこない。
CGIプログラムが一時停止で動作している間に、そのプロセスのプロセス番号を取得し、GDBでそのプロセスを捕捉する。
プロセス番号を取得するためには、シェルのプロンプトで次のコマンドを実行する。
% ps -auxw | grep [CommandName]
GDBでプロセスを捕捉すために、GDBのプロンプトで次のコマンドを実行する。
(gdb) attach [pid]
GDBがプロセスを捕捉することに成功すれば、あとは通常のプログラムのデバックと同じである。next コマンドを2回程度実行すれば、一時停止時間に関らず次の行へ動作を進めることができる。
例えば、動作を調べたい関数が output_form() であれば、
(gdb) break output_form
(gdb) continue
とすれば、その関数が呼び出されるまで実行される。
最後に、デバックが済んだら、
(gdb) detach
として、プロセスをGDBから解放する。
3. おまけ
よく使うGDBのコマンドの例
(gdb) p x
変数xの値を表示
(gdb) *ci->str
構造体ciのstrメンバが示すアドレスの値を表示
(つまりxの部分にC言語型式の評価式が書ける)
(gdb) n
次の行へ進む(関数の内部に入らない)
(gdb) s
次の行へ進む(関数の内部に入る)
(gdb) until
一番内側の関数やループの終わりまで実行する
(gdb) x/8xb encode_buf
ポインタencode_bufが指すメモリの値を8バイト分16進数で表示する
(gdb) p &cgi_args[200]
文字列配列cgi_argsを201バイト目から表示する
(gdb) display x
xの評価値をgdbのコマンドが実行される毎に表示する
(gdb) undisplay
displayの設定を無効にする
(gdb) info signals
シグナルに対するGDBの振舞いの設定を表示
(gdb) up
ひとつ上のスタックに移動する
(gdb) down
upの逆
(gdb) where
スタックを表示する