对于一个比较大的工程,函数的调用关系错综复杂,这时候仅仅靠IDE静态查看函数的调用关系已经远远不够,所以为了更好地理解代码,还是有必要去掌握一些方法和工具来跟踪函数的调用堆栈。
一、 设断点
在eclipse里可以设置断点查看函数的堆栈调用情况,这里比较方便的是可以直接定位到上级函数查看变量的值。
二、 使用backtrace
backtrace功能可以跟踪函数的调用堆栈,这个需要glibc库的支持,所以一般情况下是在linux平台使用。当然使用msys2、cygwin等工具在win32平台下模拟linux环境也是可以的,就是需要折腾一下。
要获取函数的调用堆栈信息,主要通过以下3个函数来实现:
#include
int backtrace(void **buffer, int size);
char **backtrace_symbols(void *const *buffer, int size);
void backtrace_symbols_fd(void *const *buffer, int size, int fd);
backtrace只是获得了函数的堆栈地址,保存到buffer指针数组里,每个数组元素保存一个堆栈地址,size为堆栈的最大深度。
backtrace_symbols把堆栈地址转换为函数符号保存到一个二维指针里并返回,static函数不能导出。
backtrace_symbols_fd的作用和backtrace_symbols类似,只是把函数符号导出的文件。
具体的例子可以参考:
http://man7.org/linux/man-pages/man3/backtrace.3.html
程序如下:
#include
#include
#include
#include
#define BT_BUF_SIZE 100
void
myfunc3(void)
{
int j, nptrs;
void *buffer[BT_BUF_SIZE];
char **strings;
nptrs = backtrace(buffer, BT_BUF_SIZE);
printf("backtrace() returned %d addresses\n", nptrs);
/* The call backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO)
would produce similar output to the following: */
strings = backtrace_symbols(buffer, nptrs);
if (strings == NULL) {
perror("backtrace_symbols");
exit(EXIT_FAILURE);
}
for (j = 0; j < nptrs; j++)
printf("%s\n", strings[j]);
free(strings);
}
static void /* "static" means don't export the symbol... */
myfunc2(void)
{
myfunc3();
}
void
myfunc(int ncalls)
{
if (ncalls > 1)
myfunc(ncalls - 1);
else
myfunc2();
}
int
main(int argc, char *argv[])
{
if (argc != 2) {
fprintf(stderr, "%s num-calls\n", argv[0]);
exit(EXIT_FAILURE);
}
myfunc(atoi(argv[1]));
exit(EXIT_SUCCESS);
}
注意编译的时候要加上-rdynamic选项,eclipse在工程选项里设置如下:
运行结果如下:
backtrace() returned 8 addresses
./prog(myfunc3+0x5c) [0x80487f0]
./prog [0x8048871]
./prog(myfunc+0x21) [0x8048894]
./prog(myfunc+0x1a) [0x804888d]
./prog(myfunc+0x1a) [0x804888d]
./prog(main+0x65) [0x80488fb]
/lib/libc.so.6(__libc_start_main+0xdc) [0xb7e38f9c]
./prog [0x8048711]
对于static函数,可以通过addr2line来获取对应的符号信息,对应以上的例子程序proc,myfunc2是static函数,backtrace取到的地址是0x8048871,使用addr2line的转换如下:
addr2line -e proc 0x8048871 –f
最后的结果应该是函数名、文件名和行号:
myfun2
./prog.c:37