backtrace函数是callstack调试器的基本功能之一,利用此功能,可以看到各级函数的调用关系。在gdb中,这一功能被称为backtrace,输入bt命令就可以看到当前函数的callstack。它的实现多少有些有趣,这里研究一下。
我们先看看栈的基本模型
参数N |
↓高地址 |
参数… |
函数参数入栈的顺序与具体的调用方式有关 |
参数 3 |
|
参数 2 |
|
参数 1 |
|
eip |
返回本次调用后,下一条指令的地址 |
ebp |
这里保存调用者的ebp,然后ebp寄存器会指向此时的栈顶。 |
临时变量1 |
|
临时变量2 |
|
临时变量3 |
|
临时变量… |
|
临时变量n |
↓低地址 |
栈一直随着函数调用的深入,一直向栈顶方向压下去。每次调用函数时候,先压函数参数(从右往左顺序压),再压入函数调用下条指令的地址(由call完成)。接着进入调用函数体中先执行"pushl %ebp"和"movl %esp, %ebp"(一般已经由编译器加入到函数头中了),接着就是把函数体中的局部变量压入栈中。再遇到函数的调用的嵌套则依此类推。
"pushl %ebp"和"movl %esp, %ebp"这两条指令实在大有深意:首先将EBP入栈,然后将栈顶指针ESP赋值给EBP。"movl %esp, %ebp"这条指令表面上看是用esp把ebp原来的值覆盖了,其实不然——因为给ebp赋值之前,原ebp值已被压栈(位于栈顶),而新的ebp又恰恰指向栈顶。
此时ebp寄存器就已处于一个很重要的地位,该寄存器中存储着栈中的一个地址(原ebp入栈后的栈顶),从该地址为基准,向上(栈底方向)能获取返回地址、参数值,向下(栈顶方向)能获取函数局部变量值,而该地址处又存储着上一层函数调用时的ebp值!
要实现callstack我们需要知道以下信息:
1.调用函数时的指令地址(即当时的eip,也就是上一个(int *)ebp+1的位置存放的内容)。
2.指令地址对应的源代码代码位置。
关于第一点,从上表中,可以看出,栈中存有各级eip的值,我们取出来就行了。用下面的代码可以实现:
#include
#include
#include
#define LEN 4
#define EXEFILE "bt"
int backtrace_m(void **buffer, int size)
{
int i = 0;
unsigned int _ebp = 0;
unsigned int _eip = 0;
char cmd[size][64];
__asm__ __volatile__(" \
movl %%ebp, %0"
:"=g" (_ebp)
:
:"memory"
);
for(i = 0; i < size; i++)
{
_eip = (unsigned int)((unsigned int*)_ebp + 1);
_eip = *(unsigned int*)_eip;
_ebp = *(unsigned int*)_ebp;
buffer[i] = (void*)_eip;
fprintf(stderr, "%p -> ", buffer[i]);
memset(cmd[i], 0, sizeof(cmd[i]));
sprintf(cmd[i], "addr2line %p -e ", buffer[i]);
strncat(cmd[i], EXEFILE, strlen(EXEFILE));
system(cmd[i]);
}
return size;
}
static void test2(void)
{
int i = 0;
void *buffer[LEN] = {0};
backtrace_m(buffer, LEN);
return;
}
static void test1(void)
{
test2();
}
static void test(void)
{
test1();
}
int main(int argc, char *argv[])
{
test();
return 0;
}
gcc 4.4.0, Ubuntu 9.04编译通过
程序输出:
0x80486b2 -> /home/steven/ctest/bt.c:44
0x80486bf -> /home/steven/ctest/bt.c:49
0x80486cc -> /home/steven/ctest/bt.c:54
0x80486d9 -> /home/steven/ctest/bt.c:59
关于如何把指令地址与行号对应起来,这也很简单。可以从map文件或者ELF中查询。Binutil带有一个addr2line的小工具,可以帮助查出地址在源文件中对应的代码位置,前提是编译的时候需要加上-ggdb的编译选项。
[root@linux bt]# addr2line 0x804849c -e bt
/root/test/bt/bt.c:42
转自: http://hi.chinaunix.net/?uid-1825075-action-viewspace-itemid-40672