2006年7月14日

gdb tips

gdb を使う上で便利な tips を紹介します。基本的な使い方をマスターしている人向けです。

 

.gdbinit の設定

ホームディレクトリに .gdbinit を置いておくと、gdb の起動の際に読み込まれます。私の場合は次のような設定をしています。

set history save on
set history size 10000
set history filename ~/.gdb_history
set print pretty on
set print static-members off
set charset ASCII

set history から始まる最初の 3行は履歴に関する設定です。それぞれ、 gdb のコマンドラインの履歴をファイルに保存する、保存する行は最大 10000 行、ファイル名は ~/.gdb_history 、という意味になります。デフォルトでは gdb を実行したカレントディレクトリに履歴が保存されますが、私の好みでは履歴がいろいろなディレクトリに散らばっているよりも一箇所にまとまっていた便利なので、そのようにしています。

set print から始まる 2 行は表示に関する設定です。以下のようなクラスがあるとき、

class Foo {
public:
  Foo() : a_(100), b_(200) {};
private:
  int a_;
  int b_;
  static int c_;
};
int Foo::c_ = 300;

デフォルトでは、Foo のインスタンスは次のように表示されますが、

(gdb) p foo
$1 = {a_ = 100, b_ = 200, static c_ = 300}

上記の設定をしておくことで、次のような表示になります。

(gdb) p foo
$1 = {
  a_ = 100,
  b_ = 200
}

最後の 1行は gdb が文字列を表示する際の文字コードを指定しています。現状の gdb (6.5で確認) のデフォルトは ISO-8859-1 であるため、 UTF-8 の端末では文字化けが起きることがあります。これを防ぐために ASCII に設定しています。将来的に gdb が UTF-8 をサポートしたらそれに切り替える予定です。

コマンドライン引数つきでデバッギを起動

通常、プログラムにコマンドライン引数をつけて gdb 上で実行したい場合、以下のように実行しますが、

% gdb program
(gdb) run --foo --bar

--args オプションを用いると、次のように実行できます。この方がシェルの履歴を再利用できるので何かと便利です。

% gdb --args program --foo --bar
(gdb) run

print の活用

print は変数をそのまま表示するだけでなく、基数を変換して表示したり、式の評価にも使えます。次の例では基数の変換を行っています。print には他にもいくつか書式があります。詳しくは gdb から help print を実行してください。

% gdb
(gdb) p 100
$1 = 100
(gdb) p/x 100   # 16進数で表示
$2 = 0x64
(gdb) p/o 100   # 8進数で表示
$3 = 0144
(gdb) p/t 100   # 2進数で表示
$4 = 1100100

式の評価では関数呼び出しも行えます。危険な関数呼び出しを行った場合、デバッギが死んでしまうこともあります。

% gdb /usr/bin/gdb   # libc の関数を呼ぶために何かバイナリが必要
(gdb) start
(gdb) p 1 + 2
$1 = 3
(gdb) p abs(-100)   # abs() 関数を呼び出している
$2 = 100
(gdb) p puts(0)  # 関数を危険な方法で関数を呼び出すと...

Program received signal SIGSEGV, Segmentation fault.
0x40126c7f in strlen () from /lib/libc.so.6
The program being debugged was signaled while in a function called from GDB.
GDB remains in the frame where the signal was received.
To change this behavior use "set unwindonsignal on"
Evaluation of the expression containing the function (puts) will be abandoned.

なお、関数の呼び出しは call コマンドでも行えます。

gdb の変数の活用

print で式を評価して表示すると、値の履歴が $1, $2, $3, ... という gdb の変数に格納されます。これらの変数は名前が短くタイプしやすいのがポイントです。

(gdb) p getenv("SHELL")
$1 = -1073742700
(gdb) x/s $1    # -1073742700 をコピペするより楽
0xbffffc94:      "/bin/zsh"
(gdb) x/s $     # 最後の値は $ でも参照できる
0xbffffc94:      "/bin/zsh"
(gdb) p (char)*$1    # * をつけてデリファレンスしたりもできる
$2 = 47 '/'

なお、gdb には $pc (プログラムカウンタ), $sp (スタックポインタ) といった特殊な変数も用意されています。

continue で n 回進める

ループの中にブレークポイントを設定した場合など、デバッギが頻繁に停止しすぎて鬱陶しいことがあります。この場合、 continue の引数に数字 (n) を渡せば n -1回だけ停止をスキップします (つまり、continue を n 回実行したのと同じ)。次の例では continue 5 でループを 5周進めています。

(gdb) list
1       #include <stdio.h>
2
3       int main() {
4           int i;
5           for (i = 0; i < 100; ++i) {
6               printf("%d\n", i);
7           }
8           return 0;
9       }
(gdb) b 6
Breakpoint 1 at 0x804835e: file test.c, line 6.
(gdb) c
(gdb) c 5
Will ignore next 4 crossings of breakpoint 1.  Continuing.
0
1
2
3
4

Breakpoint 1, main () at test.c:6
6               printf("hello, world\n");

同様のことは ignore コマンドを使ってブレークポイントに ignore カウントを付加してもできます。

比較的マイナーなコマンド

通常、 backtrace, break, watch, continue, step, next, finish, print といった一般的なコマンドだけで十分ですが、次のようなコマンドも役に立つことがあります。

display

ブレークポイントやステップ実行などでデバッギが停止するたびに特定の式を評価して表示する機能です。特定の変数の変化を監視したいときに使います。値の変化を表示し続けると、ログとして残るので便利です。

(gdb) display foo  # 変数 foo を監視

commands

ブレークポイントでデバッギが停止するたびに特定のコマンドを実行する機能です。display でものたりないときに使えます。

(gdb) command 1  # ブレークポイント 1 にコマンドを関連づける
Type commands for when breakpoint 3 is hit, one per line.
End with a line saying just "end".
>p (char *)ctime(&t)   # unix time を見やすく表示
>end

condition

ブレークポイントに特定の条件を関連付ける機能です。指定した条件が真のときのみデバッギが停止します。

(gdb) condition 1 i % 5 == 0   # i % 5 == 0 のときに停止

x

x は特定のアドレスから始まるデータを表示するコマンドです。以下の例では getenv() が返すアドレスから環境変数 SHELL を表示しています。p getenv("HOME") が整数を返すのは gdb が getenv() の型を知らないためです。(char *) に cast すれば p でも表示できます。

% gdb /usr/bin/gdb
(gdb) start
(gdb) p getenv("SHELL")
$1 = -1073742700
(gdb) x/s $1
0xbffffc94:      "/bin/zsh"
(gdb) p (char *)getenv("SHELL")  # cast しても OK
$2 = 0xbffffc94 "/bin/zsh"
(gdb) printf "%s\n", getenv("SHELL")   # printf でも OK
/bin/zsh
(gdb) x/s getenv("SHELL")  # これも OK
0xbffffc94:      "/bin/zsh"

x にはデータ表示に関するさまざまな書式があります。詳しくは gdb から help x を実行してください。

まとめ

gdb のコツを紹介しました。端末から生の gdb を使う機会はいまどき減ってきているかもしれませんが、グラフィカルなデバッガが使えない場面ではまだまだ現役です。他にも便利なテクニックがあったらぜひ教えてください。

参考文献

ほげめも: gdb hacks アーカイブ は gdb に関するおもしろい話題がたくさんあります。

gdb のリファレンスとしては次の本が役立ちます。機能の一覧をざっと読んで理解するのにも便利です。

GDBハンドブック
GDBハンドブック
posted with amazlet on 06.07.14
アーノルド ロビンス Arnold Robbins 千住 治郎
オライリージャパン (2005/09)
売り上げランキング: 28,991