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ビルドする際には-lbfdをお忘れなく。実行結果は以下のようになります。#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; }
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は中々楽しそうな事が出来そうですね。暇が出来たらまたなんかやってみる事にします。以上課題からの現実逃避でした...。
- by
- at 02:43

comments