gdbで標準ライブラリの中を探検する
Linuxの標準ライブラリの中の動きをデバッガで調べたいときには、デバッグ情報つきのライブラリを追加インストールし、パッケージのソースアーカイブを持ってきてその場所をデバッガのソース検索パスに追加すると便利です。
Ubuntu 12.04 (x86_64)を使用しています。
以下のようなa.cをサンプルプログラムとして
#include <stdlib.h> main() { abort(); }
デバッグ情報付きで(-g オプション)コンパイルしてgdbの中で実行させます。
$ gcc -g a.c $ gdb a.out
(gdb) r Starting program: /proj/koba/work/tmp/a.out Program received signal SIGABRT, Aborted. 0x00007ffff7a51425 in raise () from /lib/x86_64-linux-gnu/libc.so.6 (gdb) bt #0 0x00007ffff7a51425 in raise () from /lib/x86_64-linux-gnu/libc.so.6 #1 0x00007ffff7a54b8b in abort () from /lib/x86_64-linux-gnu/libc.so.6 #2 0x00000000004004fd in main () at a.c:5 (gdb)
バックトレースを見たときに、abortの呼んだのはa.cの5行目だとわかるのですが、そこから先は標準ライブラリの中の関数名しかわかりません。
デバッグ情報付きの標準ライブラリをインストールする
$ sudo apt-get install libc6-dbg
/usr/lib/debug にデバッグ情報つきのライブラリが置かれます。
これで先ほどと同じようにgdbの中で動かしてバックトレースを見ると
(gdb) r Starting program: /proj/koba/work/tmp/a.out Program received signal SIGABRT, Aborted. 0x00007ffff7a51425 in __GI_raise (sig=) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64 64 ../nptl/sysdeps/unix/sysv/linux/raise.c: No such file or directory. (gdb) bt #0 0x00007ffff7a51425 in __GI_raise (sig= ) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64 #1 0x00007ffff7a54b8b in __GI_abort () at abort.c:91 #2 0x00000000004004fd in main () at a.c:5 (gdb)
標準ライブラリの中もソースファイルとその行数がわかるようになりました。
Ubuntu標準のgdbでは追加設定無しでこうなりますが、自分でビルドしたgdbの場合は以下を確認してください。
(gdb) show debug-file-directory The directory where separate debug symbols are searched for is "/usr/lib/debug". (gdb)
このようになっていない場合は以下のコマンドでセットします。.gdbinitに書くとよいでしょう。
set debug-file-directory /usr/lib/debug
http://sourceware.org/gdb/current/onlinedocs/gdb/Separate-Debug-Files.html#Separate-Debug-Files
標準ライブラリのソースコードを持ってくる
$ pwd /proj/koba/work $ mkdir eglibc $ cd eglibc $ apt-get source libc6 $ ls eglibc-2.15 eglibc_2.15-0ubuntu10.3.dsc eglibc_2.15-0ubuntu10.3.diff.gz eglibc_2.15.orig.tar.gz
これで、アーカイブを展開してubuntuのパッチをあてるところまで自動でやってくれます。
$ cd eglibc-2.15 $ find . -name abort.c ./stdlib/abort.c
abort.c を探してその91行目を見てみます。
88 stage = 0; 89 __libc_lock_unlock_recursive (lock); 90 91 raise (SIGABRT); 92
バックトレースの結果と辻褄があってます。
gdbにソースコードのパスを追加
.gdbinitに以下を追加します。
dir /proj/koba/work/eglibc/eglibc-2.15/stdlib/
http://sourceware.org/gdb/current/onlinedocs/gdb/Source-Path.html#Source-Path
これでgdbを起動すると
(gdb) r Starting program: /proj/koba/work/tmp/a.out Program received signal SIGABRT, Aborted. 0x00007ffff7a51425 in __GI_raise (sig=) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64 64 return INLINE_SYSCALL (tgkill, 3, pid, selftid, sig); (gdb) list 59 if (__builtin_expect (pid <= 0, 0)) 60 pid = (pid & INT_MAX) == 0 ? selftid : -pid; 61 #endif 62 63 #if __ASSUME_TGKILL 64 return INLINE_SYSCALL (tgkill, 3, pid, selftid, sig); 65 #else 66 # ifdef __NR_tgkill 67 int res = INLINE_SYSCALL (tgkill, 3, pid, selftid, sig); 68 if (res != -1 || errno != ENOSYS) (gdb)
raise.c の中のソースを見ることができました。
これで標準ライブラリの中をgdbで探検することができるようになりました。
libc6のライブラリはディレクトリがたくさんわかれているので、その都度dirコマンドでソースパスを追加する必要があります。例えば、mallocの中を見たいときには以下を.gdbinitに追加します。
dir /proj/koba/work/eglibc/eglibc-2.15/malloc/
おまけ:バックトレースでmain以前を表示する
main以前のスタートアップのコードを調べたいときは
(gdb) set backtrace past-main on (gdb) bt #0 0x00007ffff7a51425 in __GI_raise (sig=) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64 #1 0x00007ffff7a54b8b in __GI_abort () at abort.c:91 #2 0x00000000004004fd in main () at a.c:5 #3 0x00007ffff7a3c76d in __libc_start_main (main=0x4004f4 , argc=1, ubp_av=0x7fffffffe528, init= , fini= , rtld_fini= , stack_end=0x7fffffffe518) at libc-start.c:226 #4 0x0000000000400439 in _start () (gdb)
http://sourceware.org/gdb/current/onlinedocs/gdb/Backtrace.html#Backtrace