自己紹介

太田一樹。
東京の大学の情報科学科に通う大学生。moratorium満喫中。

お勧め書籍 [全部見る]

飾り

Search


Category Archives

Recent Entries

  1. 論文
  2. JJUG CCCでプレゼンします
  3. kzk's bookshelf
  4. En Google by Gulfweed
  5. PNUTS
  6. コメントスパム対策
  7. Hadoop + Luceneで分散インデクシング
  8. Hadoopの解析資料
  9. Cluster 2008
  10. SWoPP 2008

2006年07月30日

C言語で実行時バックトレース

glibcにはbacktrace()という便利な関数が有るらしいのですが、あえてその中身を自分で実装してしまおうという暇潰しをしてみました。
x86に限ると以下の様な感じでスタックフレームにアクセス出来てしまいます。
int
get_stack_trace(void **result)
{
    void **sp = (void**)&result - 2;

    // sp[0] : pointer to previous frame
    // sp[1] : caller address
    // sp[2] : first argument
    int n = 0;
    while (sp && n < 100) {
        result[n++] = *(sp + 1);

        void **new_sp = (void**)(*sp);
        if (!new_sp)
            break;

        sp = new_sp;
    }

    return n;
}

resultにcallerのアドレスをどんどん記録しています。こうするとスタックトレース出来るのですが、これだけだと全く面白くないので更に進めてこの関数で得たアドレスを元に、呼び出された関数の名前を表示させたいと思います。その為にはシンボルテーブルにアクセスする必要が有るのですが、そこは軟弱バイナリアンらしくlibbfdを使用しました。

libbfdはオブジェクト内の色々な情報にアクセス出来るバイナリアン御用達の便利なライブラリです。Reading Symbolsの所を見ると丁寧にシンボルテーブルの取得方法が乗っています。基本的にはこれに沿ってやれば良いのですが、bfd_get_symtab_upper_boundを呼ぶ前にbfd_check_formatを呼ぶ必要が有るという部分で少しはまりました。そうすると以下のような情報が得られます。

....
(nil) = bfd_check_format
0x8049a40 = __dso_handle
0x8048831 = __libc_csu_fini
0x8048470 = _init
(nil) = malloc@@GLIBC_2.0
0x8049a50 = abfd
0x8049a48 = stderr@@GLIBC_2.0
0x80485b8 = get_stack_trace
0x8048738 = func1
0x8048518 = _start
(nil) = bfd_close
(nil) = bfd_openr
0x80487e0 = __libc_csu_init
0x8049a48 = __bss_start
0x804874c = main
0x8048717 = func2
...
後はこのデータと、先ほどのスタックトレースで得たアドレスを元に処理を行います。
#include 
#include 
#include 
#include 
#include 

#include 

bfd* abfd;

int
get_stack_trace(void **result)
{
    void **sp = (void**)&result - 2;

    // sp[0] : pointer to previous frame
    // sp[1] : caller address
    // sp[2] : first argument
    int n = 0;
    while (sp && n < 100) {
        result[n++] = *(sp + 1);

        void **new_sp = (void**)(*sp);
        if (!new_sp)
            break;

        sp = new_sp;
    }

    return n;
}

void
stack_trace(void)
{
    void* trace[100];
    int num_frames = get_stack_trace(trace);

    long storage_needed = bfd_get_symtab_upper_bound(abfd);
    if (storage_needed < 0) {
        fprintf(stderr, "bfd_get_symtab_upper_bound failed\n");
        return;
    }
    if (storage_needed == 0)
        return;

    asymbol **symbol_table = (asymbol**)malloc(storage_needed);
    long num_symbols = bfd_canonicalize_symtab(abfd, symbol_table);
    long i, j;
/*
    for (i = 0; i < num_symbols; i++) {
        asymbol *sym = symbol_table[i];
        printf("%p = %s\n",
               (void*)bfd_asymbol_value(sym),
               bfd_asymbol_name(sym));
    }
*/
    for (i = 0; i < num_frames; i++) {
        void *addr = trace[i];
        asymbol *ret = NULL;
        uintptr_t far = INT_MAX;
        for (j = 0; j < num_symbols; j++) {
            asymbol *sym = symbol_table[j];
            if (bfd_asymbol_value(sym) == 0)
                continue;
            if (bfd_asymbol_name(sym)[0] == '.')
                continue;
            if (addr < ((void*)(uintptr_t)bfd_asymbol_value(sym)))
                continue;
            if (addr - ((void*)(uintptr_t)bfd_asymbol_value(sym)) < far) {
                far = addr - ((void*)(uintptr_t)bfd_asymbol_value(sym));
                ret = sym;
            }
        }

        if (ret)
            printf("%p %15s %p\n",
                   addr,
                   bfd_asymbol_name(ret),
                   (void*)(uintptr_t)bfd_asymbol_value(ret));
    }

    free(symbol_table);
}

int
func2(int n)
{
    if (n == 0) {
        stack_trace();
        return 0;
    }

    return func2(n - 1);
}

void
func1(void)
{
    func2(5);
}

int
main(int argc, char **argv)
{
    abfd = bfd_openr(argv[0], NULL);
    assert(abfd != NULL);
    bfd_check_format(abfd, bfd_object);

    func1();

    bfd_close(abfd);
    return 0;
}
ビルドする際には-lbfdをお忘れなく。実行結果は以下のようになります。
dylan% gcc -Wall -g stacktrace.c -lbfd; ./a.out
0x804862b     stack_trace 0x8048613
0x804881a           func2 0x8048809
0x8048831           func2 0x8048809
0x8048831           func2 0x8048809
0x8048831           func2 0x8048809
0x8048831           func2 0x8048809
0x8048831           func2 0x8048809
0x804884b           func1 0x8048839
0x80488ca            main 0x804884d
0x8048539          _start 0x8048518
まぁこんな事も出来ますよという事で。それにしてもlibbfdは中々楽しそうな事が出来そうですね。暇が出来たらまたなんかやってみる事にします。以上課題からの現実逃避でした...。

trackbacks

trackbackURL:

『C言語で実行時バックトレース』の関連記事

comments

comment form
comment form