glibc でバックトレースを表示する方法

2017年11月6日追記.demangle 処理を追加したコードを公開しています.詳細は http://d.hatena.ne.jp/pyopyopyo/edit?date=20171106 で.

http://0xcc.net/blog/archives/000067.html にて知ったのだが,glibc はバックトレースを表示するための関数を提供しているとのこと.

関数名は backtrace(), backtrace_symbols_fd()で,execinfo.h というヘッダファイルで定義されている.使い方は以下の通り.

int 
foo(int a)
{
    void *trace[128];
    int n = backtrace(trace, sizeof(trace) / sizeof(trace[0]));
    backtrace_symbols_fd(trace, n, 1);
}

int
main(int argc, char *argv[])
{
    foo(123);
    return 0;
}

backtrace_symbols_fd()の最後の引数はバックトレースの出力先のfile descriptor で,上記サンプルは stdout に出すために 即値で 1 を指定してある.

気をつけないといけないのはコンパイル方法で,(少なくともglibc-2.5な環境では)コンパイルする際に "-rdynamic" を付けるか付けないかで出力される情報が異なった.具体的には "-rdynamic" をつけた場合は,シンボル名まで表示された.

$ gcc -rdynamic bt.c
$ ./a.out
./a.out(foo+0x1f)[0x8048573]
./a.out(main+0x1d)[0x80485b2]
/lib/libc.so.6(__libc_start_main+0xdc)[0xb7e27f2c]
./a.out[0x80484d1]

一方,"-rdynamic" をつけなかった場合は,シンボル名は表示されない.

$ gcc bt.c
$ ./a.out
./a.out[0x80483d3]
./a.out[0x8048412]
/lib/libc.so.6(__libc_start_main+0xdc)[0xb7e1ef2c]
./a.out[0x8048331]

このようにfooとかmainのようなシンボルが表示されない.

バックトレースを見るはめになる状況を想定すると,execinfo.h 一派を使う時は -rdynamic オプションは必須だと思われる.