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