gdbに構造体定義を読み込ませて使う

ヘッダファイルなどから構造体の定義などがわかっているとき、gdbにこれを読み込ませることでメモリ内容を構造体定義に合わせて表示させることができる。 これを行うには、gccデバッグ情報付きのオブジェクトを作った後、gdbのadd-symbol-fileコマンドで読み込ませればよい。 ここでは、link.hおよび内部でインクルードされるelf.hを使って、実行時のELFセクションの内容をたどってみる。

まずはヘッダファイルをC言語ソースとみなして、デバッグ情報付きでオブジェクトファイルにする。 ここでは、コンパイルまでを行いリンクは行わないので、-cオプションをつける。 さらに、-fno-eliminate-unused-debug-typesオプションを使い、変数宣言されていない型もデバッグ情報に含めるようにする。

$ gcc -g -fno-eliminate-unused-debug-types -x c -c /usr/include/link.h -o link.o

適当な実行ファイルをgdbで実行し、作成したオブジェクトファイルをadd-symbol-fileコマンドで読み込ませる。 add-symbol-fileは第2引数として対応するファイルのベースアドレスを指定する必要があるが、ここではシンボル情報は利用しないので適当でよい。

$ gdb -q a.out
Reading symbols from /home/user/tmp/a.out...(no debugging symbols found)...done.
(gdb) set print array on
(gdb) set print array-indexes on
(gdb) set output-radix 16.
Output radix now set to decimal 16, hex 10, octal 20.
(gdb) add-symbol-file link.o 0
add symbol table from file "link.o" at
        .text_addr = 0x0
(y or n) y
Reading symbols from /home/user/tmp/link.o...done.

ここでは、合わせて配列の整形表示およびインデックス表示と、数値出力の16進表示設定も行っている。 これで、link.hに定義された構造体情報は読み込まれたので、あとはメモリアドレスから構造体をたどっていくことができる。

(gdb) start
Temporary breakpoint 1 at 0x8048407
Starting program: /home/user/tmp/a.out

Temporary breakpoint 1, 0x08048407 in main ()
(gdb) i files
        0x08049f08 - 0x08049fe0 is .dynamic
(gdb) set $dynamic = (Elf32_Dyn *)0x08049f08
(gdb) p *$dynamic@10
$1 =   {[0x0] = {d_tag = 0x1, d_un = {d_val = 0x10, d_ptr = 0x10}},
  [0x1] = {d_tag = 0xc, d_un = {d_val = 0x80482d0, d_ptr = 0x80482d0}},
  [0x2] = {d_tag = 0xd, d_un = {d_val = 0x804851c, d_ptr = 0x804851c}},
  [0x3] = {d_tag = 0x6ffffef5, d_un = {d_val = 0x80481ac, d_ptr = 0x80481ac}},
  [0x4] = {d_tag = 0x5, d_un = {d_val = 0x804822c, d_ptr = 0x804822c}},
  [0x5] = {d_tag = 0x6, d_un = {d_val = 0x80481cc, d_ptr = 0x80481cc}},
  [0x6] = {d_tag = 0xa, d_un = {d_val = 0x50, d_ptr = 0x50}},
  [0x7] = {d_tag = 0xb, d_un = {d_val = 0x10, d_ptr = 0x10}},
  [0x8] = {d_tag = 0x15, d_un = {d_val = 0xb7fff904, d_ptr = 0xb7fff904}},
  [0x9] = {d_tag = 0x3, d_un = {d_val = 0x8049fe0, d_ptr = 0x8049fe0}}}
(gdb) set $r_debug = (struct r_debug *)$dynamic[8]->d_un->d_ptr
(gdb) p *$r_debug
$2 = {r_version = 0x1, r_map = 0xb7fff918, r_brk = 0xb7fed670, r_state = RT_CONSISTENT, r_ldbase = 0xb7fde000}
(gdb) p *$r_debug->r_map
$3 = {l_addr = 0x0, l_name = 0xb7ff7bc5 "", l_ld = 0x8049f08, l_next = 0xb7fdd858, l_prev = 0x0}
(gdb) p *$r_debug->r_map->l_next
$4 = {l_addr = 0xb7e29000, l_name = 0xb7fdd838 "/lib/i386-linux-gnu/libc.so.6", l_ld = 0xb7fced7c, l_next = 0xb7fff53c, l_prev = 0xb7fff918}

上の例では、dynamicセクションからDT_DEBUG (d_tag = 0x15) に対応するメモリアドレスを調べ、これをr_debug構造体として表示させている。 そして中にあるlink_map構造体の双方向リストをたどっている。 なお、set $foo=1GDB変数のセット、p *$ptr@nでポインタが指すアドレスからn個分の要素を表示することができる。

また、次のようにptype(pt)コマンドを使って型を調べると、変数$r_debug$r_debug->r_mapがそれぞれr_debug構造体、link_map構造体として解釈されていることが確認できる。

(gdb) pt *$r_debug
type = struct r_debug {
    int r_version;
    struct link_map *r_map;
    Elf32_Addr r_brk;
    enum {RT_CONSISTENT, RT_ADD, RT_DELETE} r_state;
    Elf32_Addr r_ldbase;
}
(gdb) pt *$r_debug->r_map
type = struct link_map {
    Elf32_Addr l_addr;
    char *l_name;
    Elf32_Dyn *l_ld;
    struct link_map *l_next;
    struct link_map *l_prev;
}

関連リンク