- 2006-07-30 (Sun) 2:43
- Unix

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 <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <limits.h>
#include <stdint.h>
#include <bfd.h>
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は中々楽しそうな事が出来そうですね。暇が出来たらまたなんかやってみる事にします。以上課題からの現実逃避でした…。
Similar Posts:
- Newer: タモリ倶楽部 20060729 「初潜入世界最高峰のIT企業独占公開!超リアルで遊べる地図」
- Older: BroomFightの高速化
Comments:0
Trackbacks:0
- Trackback URL for this entry
- http://kzk9.net/blog/2006/07/c_1.html/trackback
- Listed below are links to weblogs that reference
- C言語で実行時バックトレース from moratorium
