2009/02/20

アプリケーションのバックトレース (execinfo)

ちょっと必要があって、実行中のアプリケーションプログラムから、特定の走行箇所でのバックトレースを取る方法が必要になって調べましたので、備忘録。

カーネル内であれば、show_stack()を呼び出すことで走行時のスタックトレースを簡単に得ることが出来ますが、アプリケーションプログラムでもexecinfoを使うと同様のことが出来ます。

具体的には、
  1. backtrace()関数でスタック情報を取得します。
    スタック情報はvoidのポインタへの配列として用意します。この配列の大きさが、取得できるスタックの深さ(ネスト)の最大値を規定することになります。backtrace()関数の戻り値が実際に取得したスタックの深さになります。
    void *trace[100];
    nr = backtrace(trace, 100);
  2. backtrace_symbol()関数で、取得したスタック情報をシンボル化します。
    char **ents;
    ents = backtrace_symbols(trace, nr);
  3. 取得したスタック情報を表示
      for(i=0; i<nr; i++) {
    printf("#%d : [%s]\n", i, ents[i]);
    }
  4. 後始末(シンボル配列の開放)
    backtrace_symbols()関数はchar *の配列を返却しますが、この配列はbacktrace_symbols()関数が内部的にmalloc(3)で確保したものなので、最後にfreeしてあげる必要があります。
以下にサンプルコードと実行例を乗せておきます。


#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>

#define MAXFRAME 64
void show_stack(void)
{
int i, nr;
void *trace[MAXFRAME];
char **ents;

nr = backtrace(trace, MAXFRAME);

if (nr>=MAXFRAME) {
printf("Stack too deep, some entries are stripped off\n");
}

ents = backtrace_symbols(trace, nr);
if (ents == NULL) {
perror("backtrace_symbols");
exit(1);
}

for(i=0; i<nr; i++) {
printf("#%d : [%s]\n", i, ents[i]);
}

free(ents);
}

void b(void)
{
show_stack();
}

void a(void)
{
b();
}

main()
{
a();
}

実行例です。正しくスタック情報が取れていることが判ります。
# cc execinfo.c -o execinfo
# ./execinfo
#0 : [./execinfo(show_stack+0x1f) [0x8048743]]
#1 : [./execinfo(b+0xb) [0x80487dd]]
#2 : [./execinfo(a+0xb) [0x80487ea]]
#3 : [./execinfo(main+0x16) [0x8048802]]
#4 : [/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe0) [0xb7e6b450]]
#5 : [./execinfo [0x80486c1]]

なお、エクスポートしていないシンボルは当然ながら表示されないので、確実に表示したい場合には ld に -rdynamic オプションを付与しなければなりません。

0 件のコメント: