栈调用关系跟踪
在发生段错误的时候,打印函数的调用栈信息是定位问题很好的手段,一般来讲,我们可以捕获SIGSEGV信号,在信号处理函数中将函数调用栈的关系打印出来。gdb调试中的backtrace,简称bt就是这个作用。 CU的二娃子前两天写了个Linux下进程崩溃时定位源代码位置,这篇文章写的很好,调用的GNU的backtrace函数,打印了函数的调用栈信息。我想补充一些内容,把这
CU的二娃子前两天写了个 Linux下进程崩溃时定位源代码位置,这篇文章写的很好,调用的GNU的backtrace函数,打印了函数的调用栈信息。我想补充一些内容,把这个话题补充的更加丰富一些。
我们遇到的很多难题,前辈都会遇到,很多有分享精神的前辈会写很多精彩的总结。国外布法罗大学的一个牛人总结了跟踪栈调用关系的文章,写了一篇博客。英文水平高的筒子,不要听我JJYY,直接跳转到http://www.acsu.buffalo.edu/~charngda/backtrace.html,去看这篇博文,当然本文提到的第二 种方法还是值得一看的。作者提到了4种方法来解决栈调用关系其中二娃子用的是第二种方法。我阅读了self-service linux这本书,这本书也很详尽的描述了栈的结构。我们补充一种方法,自己实现backtrace。
我的栈调用关系如下:
- int foo()
- {
- do_backtrace();
- }
- int bar( void )
- {
- foo();
- return 0;
- }
- int boo( void )
- {
- bar();
- return 0;
- }
- int baz( void )
- {
- boo();
- return 0;
- }
- int main( void )
- {
- baz();
- return 0;
- }
第一种方法 : glibc提供的backtrace函数
先说二娃子的方法: GNU提供的backtrace函数
-
#include <execinfo.h>
- void do_gnu_backtrace()
- {
- #define BACKTRACE_SIZ 100
- void *array[BACKTRACE_SIZ];
- size_t size, i;
- char **strings;
- size = backtrace(array, BACKTRACE_SIZ);
- strings = backtrace_symbols(array, size);
- for (i = 0; i < size; ++i) {
- printf("%p : %s\n", array[i], strings[i]);
- }
- printf("---------------------------------------------------------\n");
- free(strings);
- }
- 0x8048ae4 : ./bt_walk(do_gnu_backtrace+0x1f) [0x8048ae4]
- 0x8048c4f : ./bt_walk(foo+0x15) [0x8048c4f]
- 0x8048c66 : ./bt_walk(bar+0xb) [0x8048c66]
- 0x8048c78 : ./bt_walk(boo+0xb) [0x8048c78]
- 0x8048c8a : ./bt_walk(baz+0xb) [0x8048c8a]
- 0x8048c9c : ./bt_walk(main+0xb) [0x8048c9c]
- 0xb758e4d3 : /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0xb758e4d3]
- 0x8048901 : ./bt_walk() [0x8048901]
- 0x8048894 : ./bt_walk() [0x8048894]
- 0x80489ff : ./bt_walk() [0x80489ff]
- 0x8048a16 : ./bt_walk() [0x8048a16]
- 0x8048a28 : ./bt_walk() [0x8048a28]
- 0x8048a3a : ./bt_walk() [0x8048a3a]
- 0x8048a4c : ./bt_walk() [0x8048a4c]
- 0xb75144d3 : /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0xb75144d3]
- 0x80486b1 : ./bt_walk() [0x80486b1]
下面的图来自雨夜听声的博客,函数调用如下图所示。如果有N个参数,将N个参数压栈(顺序也很有意思,希望了解这个的可以看程序员的自我修养),然后是将返回地址压栈,最后是将ebp压栈保存起来。
如果我们只传递一个参数个某个函数,那么我们完全可以根据参数的地址推算出ebp存放的地址,进而得到ebp的值。参数地址-4(32位系统指针的长度为4Byte)可以得到返回地址的位置。参数的地址-8 得到ebp在栈存放的地址。我们一旦得到ebp,我们就可以回朔出整个栈调用。
先看第一步:getEBP
- void **getEBP( int dummy )
- {
- void **ebp = (void **)&dummy -2 ;
- return( *ebp );
- }
第二步,有了ebp, 我们可以一步一步前回退,得到调用者的栈的ebp,调用者的调用者的栈的ebp,。。。。直到NULL
- while( ebp )
- {
- ret = ebp + 1;
- dladdr( *ret, &dlip );
- printf("Frame %d: [ebp=0x%08x] [ret=0x%08x] %s\n",
- frame++, *ebp, *ret, dlip.dli_sname );
- ebp = (void**)(*ebp);
- /* get the next frame pointer */
- }
- (gdb) i b
- Num Type Disp Enb Address What
- 1 breakpoint keep y 0x0804858a in main at bt_walk.c:49
- 2 breakpoint keep y 0x08048578 in baz at bt_walk.c:44
- 3 breakpoint keep y 0x08048566 in boo at bt_walk.c:39
- 4 breakpoint keep y 0x08048554 in bar at bt_walk.c:34
- 5 breakpoint keep y 0x08048542 in foo at bt_walk.c:29
- 6 breakpoint keep y 0x080484ac in print_walk_backtrace at bt_walk.c:12
- 7 breakpoint keep y 0x0804849a in getEBP at bt_walk.c:6
- (gdb) r
- Starting program: /home/manu/code/c/self/calltrace/bt_walk
- Breakpoint 1, main () at bt_walk.c:49
- 49 baz();
- (gdb) p $ebp
- $1 = (void *) 0xbffff6d8
- (gdb) c
- Continuing.
- Breakpoint 2, baz () at bt_walk.c:44
- 44 boo();
- (gdb) p $ebp
- $2 = (void *) 0xbffff6c8
- (gdb) c
- Continuing.
- Breakpoint 3, boo () at bt_walk.c:39
- 39 bar();
- (gdb) p $ebp
- $3 = (void *) 0xbffff6b8
- (gdb) c
- Continuing.
- Breakpoint 4, bar () at bt_walk.c:34
- 34 foo();
- (gdb) p $ebp
- $4 = (void *) 0xbffff6a8
- (gdb) c
- Continuing.
- Breakpoint 5, foo () at bt_walk.c:29
- 29 print_walk_backtrace();
- (gdb) p $ebp
- $5 = (void *) 0xbffff698
- (gdb) c
- Continuing.
- Breakpoint 6, print_walk_backtrace () at bt_walk.c:12
- 12 int frame = 0;
- (gdb) p $ebp
- $6 = (void *) 0xbffff688
- (gdb) c
- Continuing.
- Breakpoint 7, getEBP (dummy=0x9ca212c) at bt_walk.c:6
- 6 void **ebp = (void **)&dummy -2 ;
- (gdb) p $ebp
- $7 = (void *) 0xbffff638
- (gdb) n
- 7 return( ebp );
- (gdb) p ebp
- $8 = (void **) 0xbffff638
- (gdb) x/40x 0xbffff638
- 0xbffff638: 0xbffff688 0x080484be 0x09ca212c 0xbffff68f
- 0xbffff648: 0x00000001 0xb7eac269 0xbffff68f 0xbffff68e
- 0xbffff658: 0x00000000 0xb7ff3fdc 0xbffff714 0x00000000
- 0xbffff668: 0x00000000 0xb7e47043 0x00000000 0x00000000
- 0xbffff678: 0x09ca212c 0x00000001 0xb7fb9ff4 0x00000000
- 0xbffff688: 0xbffff698 0x08048547 0x080485a0 0x08049ff4
- 0xbffff698: 0xbffff6a8 0x08048559 0xb7fba3e4 0x00008000
- 0xbffff6a8: 0xbffff6b8 0x0804856b 0xffffffff 0xb7e47196
- 0xbffff6b8: 0xbffff6c8 0x0804857d 0xb7fed270 0x00000000
- 0xbffff6c8: 0xbffff6d8 0x0804858f 0x080485a0 0x00000000
- (gdb) p &dummy
- $9 = (int **) 0xbffff640
- #ifdef __USE_GNU
- /* Structure containing information about object searched using
- ‘dladdr’. */
- typedef struct
- {
- __const char *dli_fname; /* File name of defining object. */
- void *dli_fbase; /* Load address of that object. */
- __const char *dli_sname; /* Name of nearest symbol. */
- void *dli_saddr; /* Exact value of nearest symbol. */
- } Dl_info;
/* Fill in *INFO with the following information about ADDRESS.
Returns 0 iff no shared object’s segments contain that address. */
extern int dladdr (__const void *__address, Dl_info *__info) __THROW;
- #include <dlfcn.h>
- void **getEBP( int dummy )
- {
- void **ebp = (void **)&dummy -2 ;
- return( *ebp );
- }
- void print_walk_backtrace( void )
- {
- int dummy;
- int frame = 0;
- Dl_info dlip;
- void **ebp = getEBP( dummy );
- void **ret = NULL;
- printf( "Stack backtrace:\n" );
- while( ebp )
- {
- ret = ebp + 1;
- dladdr( *ret, &dlip );
- printf("Frame %d: [ebp=0x%08x] [ret=0x%08x] %s\n",
- frame++, *ebp, *ret, dlip.dli_sname );
- ebp = (void**)(*ebp);
- /* get the next frame pointer */
- }
- printf("---------------------------------------------------------\n");
- }
1 头文件 dlfcn.h
2 编译的时候加上-rdynamic ,同时链接libdl.so 即加上-ldl选项
执行效果如下:
- Stack backtrace:
- Frame 0: [ebp=0xbfdbea38] [ret=0x08048c42] foo
- Frame 1: [ebp=0xbfdbea48] [ret=0x08048c63] bar
- Frame 2: [ebp=0xbfdbea58] [ret=0x08048c75] boo
- Frame 3: [ebp=0xbfdbea68] [ret=0x08048c87] baz
- Frame 4: [ebp=0xbfdbea78] [ret=0x08048c99] main
- Frame 5: [ebp=0x00000000] [ret=0xb75594d3] __libc_start_main
3 第三种是libunwind。
- #include <libunwind.h>
- void do_unwind_backtrace()
{
unw_cursor_t cursor;
unw_context_t context;
unw_getcontext(&context);
unw_init_local(&cursor, &context);
while (unw_step(&cursor) > 0) {
unw_word_t offset, pc;
char fname[64];
unw_get_reg(&cursor, UNW_REG_IP, &pc);
fname[0] = '\0';
(void) unw_get_proc_name(&cursor, fname, sizeof(fname), &offset);
printf ("%p : (%s+0x%x) [%p]\n", pc, fname, offset, pc);
}
printf("---------------------------------------------------------\n");
}
优点是不需要-rdynamic选项,不需要-g选项。
执行结果如下:
- 0x8048a01 : (foo+0x1a) [0x8048a01]
- 0x8048a13 : (bar+0xb) [0x8048a13]
- 0x8048a25 : (boo+0xb) [0x8048a25]
- 0x8048a37 : (baz+0xb) [0x8048a37]
- 0x8048a49 : (main+0xb) [0x8048a49]
- 0xb75c14d3 : (__libc_start_main+0xf3) [0xb75c14d3]
- 0x80486b1 : (_start+0x21) [0x80486b1]
- ---------------------------------------------------------
参考文献1 提到了改进的backtrace,同时给出了cario的相关代码,很有意思,感兴趣的可以去读一下。
参考文献
1 http://www.acsu.buffalo.edu/~charngda/backtrace.html (强烈推荐)
2 程序员的自我修养
3 CU 二娃子的博客
4 Self-Service Linux chapter 5 :Stack(推荐)
原文地址http://blog.chinaunix.net/uid-24774106-id-3457205.html
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)