GDBを使用したデバッグ手法(応用編)
前回はGDBの基本的な使い方について紹介させて頂きました。
今回はさらに掘り下げたGDBの使い方についてご紹介したいと思います。
マルチスレッドのデバッグ
GDBはスレッドの情報取得やデバッグするカレント・スレッドの切り替えをサポートしています。例えば、マルチスレッド環境のプログラム実行中に無限ループに陥ったとしてインタラプト(C-c)で停止しても、原因となる処理で停止するとは限りません。スレッドの情報が参照できれば原因となる処理がどのスレッドで実行されているのか知り得るきっかけとなります。
スレッドの情報を取得する
スレッドの情報を取得するには以下のコマンドを実行します。
1 |
(gdb) info threads |
スレッド情報の出力結果の例。
1 2 |
* 2 Thread 0xb7f33b90 (LWP 6388) threadSubMain (arg=0x0) at sample.c:98 1 Thread 0xb7f346c0 (LWP 6387) 0x00110402 in __kernel_vsyscall () |
「*」があるスレッドがデバッグ対象のスレッド(カレント・スレッド)
スレッドを切り替える
カレント・スレッドを切り替えるにはthreadコマンドを使用します。スレッド番号を指定することによりカレント・スレッドが切り替わります。
1 |
(gdb) thread threadno |
スレッド番号threadnoを割り当てられたスレッドをカレント・スレッドとします。 このコマンドの引数threadnoは、’info threads’コマンドの出力の最初のフィールドに表示される、GDB内部のスレッド番号です。
特定のスレッドにブレイクポイントを設定する
breakコマンドは停止させたいスレッド番号を指定することが可能です。これによりブレイクポイント位置で特定のスレッドの場合のみ停止させることができます。
1 |
(gdb) break linespec thread threadno |
linespecはソース行を指定します。 記述方法はいくつかありますが、 どの方法を使っても結果的にはソース行を指定することになります。 breakコマンドに修飾子’thread threadno’を使用することで、 ある特定のスレッドがこのブレイクポイントに到達したときだけGDBがプログラムを停止するよう指定することができます。 ここでthreadnoは、GDBによって割り当てられるスレッド識別番号で、’info threads’コマンドによる出力の最初の欄に表示されるものです。
GDBを使用した有用なデバッグ手法
特定の値の時だけプログラムを停止したい
繰り返し使用されるロジック内でブレイクポイントを設定しなければならない場合、目的の値が使用される以外にそのブレイクポイントで停止してしまうのは効率が非常に悪い。そこで、ブレイクポイントに停止条件を付加することにより効率化を図ることができる。
例えば、以下のような関数で「3」が指定された場合のみ動作が不正となるため、この関数にパラメータが3の場合の動作をデバッグしたいとする。
ブレイクポイントを設定する際に、以下のコマンド指定により停止条件を付加することができる。
1 |
(gdb) break ... if cond |
condで指定される条件式付きでブレイクポイントを設定します。そのブレイクポイントに達すると、 必ず条件式condが評価されます。評価結果がゼロでない場合、すなわち、評価結果が真である場合のみユーザ・プログラムを停止します。
breakpointSample1関数のパラメータが「3」の場合のみ停止するブレイクポイントを設定するには以下のようにbreakコマンドを指定する。
1 |
(gdb) break breakpointSample1 if key==3 |
ブレイクポイントを確認すると以下の様に表示される。
1 2 3 |
Num Type Disp Enb Address What 1 breakpoint keep y 0x080486d4 in breakpointSample1 at sample.c:159 stop only if key==3 |
逆に「3」以外の場合に停止したい場合は条件式に「!」を指定する
1 |
(gdb) break breakpointSample1 if key!=3 |
変数の値が変化したときにプログラムを停止したい
ある変数の値により不具合が発生する場合、どの処理がその変数を問題の値に設定しているか調べたい場合がある。対象の変数が複数の処理から設定されているようなケースでは、その全てにブレイクポイントを設定して地道に調べる方法もあるが、値の変化を検知してバックトレースを取得し、どの処理が設定しているのかを調べた方が早いこともあります。
値の変化を検知するにはウォッチポイントを使用します。
ウォッチポイントは以下の様に指定します。
1 |
(gdb) watch expr |
exprで指定される式に対してウォッチポイントを設定します。
ウォッチポイントはオート変数など保証されないメモリ領域は指定できません。グローバル変数などの静的にアドレスが決まっている変数で指定することができます。
ウォッチポイントは「watch」コマンド実行時に停止条件を指定することができません。ウォッチポイント設定後に以下のコマンドで停止条件を付けることができます。(これはブレイクポイントでも同様です)
1 |
(gdb) condition bnum expression |
bnumで指定される番号のブレイクポイント、 ウォッチポイント、 キャッチポイントの成立条件として、 expressionを指定します。
また、ポイントの通過カウントを条件に停止することも可能です。
1 |
(gdb) ignore bnum count |
bnumで指定される番号のブレイクポイントの通過カウントをcountで指定される値に設定します。
プログラムを停止することなく一時的に値を変更して実行したい
プログラムの実行時に通常の動作では再現するのが難しい処理があったとします。その時に一時的に値を変更することが出来れば容易に再現することができると思います。
ブレイクポイントでの停止時にアクションを設定することができます。この機能を利用して一時的に値を変更することが可能です。
アクションを設定するにはcommandコマンドを使用します。
1 2 3 |
(gdb) commands [bnum] ... command-list ... end |
bnumで指定される番号を持つブレイクポイントに対して一連のコマンドを指定します。 コマンド自体は次の行以下に記述します。コマンドの記述を終了するには、endだけから成る1行を記述します。
パラメータ「value」をもつ関数functionAがあったとして、functionAのパラメータ「value」が4の場合に0に書き換えて処理を継続するアクションをcommandコマンドで記載すると以下の様になります。
1 2 3 4 5 6 |
(gdb) break functionA if value==4 (gdb) commands 1 silent set value = 0 cont end |
コマンドリストの最初に「silent」を記述していると通常のブレイクポイントの停止時に出力するメッセージを表示させなくすることができます。これによりブレイクポイントに任意のメッセージを表示することができます。
コマンドリストの最後の「end」前に「cout」を記述することで、ブレイクポイントによる停止処理を継続します。これにより、ユーザは処理が停止したことを意識することなくプログラムの改変や出力を行うことができます。
停止時に毎回決まった変数を表示させたい
ブレイクポイントにて停止する度に特定の変数の値を参照したい場合、printコマンドを使用して毎回確認することもできるが、停止した時に自動で表示することもできる。
自動で表示させるにはdisplayを使用する。
1 |
(gdb) display exp |
プログラムが停止した時に表示するメッセージにて、式「exp」を表示します。
displayコマンドで式に変数「sec」と「min」を設定した時の出力例
1 2 3 |
Breakpoint 5, functionC () at sample.c:83 2: sec = 35 1: min = 1 |
設定したdisplayコマンドは以下のコマンドで確認することができます。
1 |
(gdb) info display |
上記コマンドの表示例
1 2 3 4 |
Auto-display expressions now in effect: Num Enb Expression 2: y sec 1: y min |
EMACSでGDBを実行する方法
GDBをEmacsのサブプロセスとして起動し、 新しく作成したEmacsバッファを通じて入出力を行います。Emacsのスクリーンは分割され、ソースを表示するバッファとGDBの入出力を行うバッファと別々に表示することが出来ます。
手順
1. Emacsの起動
$ emacs
2. Menu bar表示
F10キー押下
3. Menu bar→Tools
上下キーで「Tools」を選択しEnterキー押下
(または)tキー押下
4. Menu bar→Tools→GDB
上下キーで「Debugger (GUD)」を選択しEnterキー押下
(または)dキー押下
M-x gdbで2.から4.までの手順が省略出来ます。
5. GDB起動
Enterキー押下
6. プログラムにアタッチ
プロセスを表示しattachコマンドでアタッチする
1 2 3 4 5 6 7 8 |
(gdb) shell ps -a PID TTY TIME CMD 3623 pts/1 00:00:00 su 3624 pts/1 00:00:00 bash 14764 pts/3 00:11:22 program.exe 15800 pts/2 00:00:00 emacs-x 15805 pts/4 00:00:00 ps (gdb) at 14764 |
シンボルのロードが完了したらcontinue
1 |
(gdb) c |
7. デバッグを終了する
デバッグが実行中の場合は中断する
「C-c」「C-c」
アタッチを解除する
1 2 |
(gdb) q The program is running. Quit anyway (and detach it)? (y or n) y |
emacsを終了する
「C-x」「C-c」
応用編のまとめ
GDBにはクロスプラットフォームのリモートデバッグやGDBデバッガの環境設定といったことができ、ここで記載した内容以外にもたくさんの事ができるデバッガです。応用編はどちらかというと中級者向けの内容となっており、GDBを使ったデバッグでなくてはならないと思う内容をまとめて紹介させて頂きました。
◆WEB会議/セミナーシステム『Szia』
https://www.ois-yokohama.co.jp/szia/
◆サーバサイドで動作するミドルウェア『ReDois』
https://www.ois-yokohama.co.jp/redois/wp_redois/