backtrace()是glibc(>=2.1)提供的函数,用于跟踪函数的调用关系。
以下对backtrace()函数的说明以及实例,都来自其man page。
函数定义
#include <execinfo.h>
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中。buffer是一个数组指针,数组的每个元素保存着每一级被调用函数的返回地址。参数size指定了buffer中可存放的返回地址的数量。如果函数实际的回溯层级数大于size,则buffer中只能存放最近的函数调用关系,所以,想要得到完整的回溯信息,就要确保size参数足够大。
backtrace()函数的返回值为buffer中的条目数量,这个值不一定等于size,因为如果为得到完整回溯信息而将size设置的足够大,则该函数的返回值为buffer中实际得到的返回地址数量。
通过backtrace()函数得到buffer之后,backtrace_symbols()可以将其中的返回地址都对应到具体的函数名,参数size为buffer中的条目数。backtrace_symbols()函数可以将每一个返回值都翻译成“函数名+函数内偏移量+函数返回值”,这样就可以更直观的获得函数的调用关系。
经过翻译后的函数回溯信息放到backtrace_symbols()的返回值中,如果失败则返回NULL。需要注意,返回值本身是在backtrace_symbols()函数内部进行malloc的,所以必须在后续显式地free掉。
backtrace_symbols_fd()的buffer和size参数和backtrace_symbols()函数相同,只是它翻译后的函数回溯信息不是放到返回值中,而是一行一行的放到文件描述符fd对应的文件中。
注意,在编译的时候需要加上-rdynamic选项让链接器将所有符号添加到动态符号表中,这样才能将函数地址翻译成函数名。另外,这个选项不会处理static函数,所以,static函数的符号无法得到。
示例:
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void
myfunc3(void)
{
int j, nptrs;
#define SIZE 100
void *buffer[100];
char **strings;
nptrs = backtrace(buffer, 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);
}
进行编译:
cc -rdynamic prog.c -o prog
代码运行结果为:
$ ./prog 3
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]
采用信号量进行崩溃日志打印:
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void trace(int signo)
{
int j, nptrs;
#define SIZE 100
void *buffer[100];
char **strings;
printf("signo: %d\n", signo);
nptrs = backtrace(buffer, 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);
if (SIGSEGV == signo || SIGQUIT == signo) {
exit(0);
}
}
void segfault(void)
{
int *p = NULL;
*p = 1;
}
int main(int argc, char *argv[])
{
signal(SIGSEGV, trace);
signal(SIGINT, trace);
signal(SIGQUIT, trace);
while (1) {
sleep(1);
if (time(0) % 7 == 0) {
segfault();
}
}
return 0;
}
编译方法 gcc -rdynamic seg.c -o seg -g
段错信息
signo: 11
backtrace() returned 6 addresses
./seg(trace+0x3c) [0x400aac]
/lib64/libc.so.6() [0x3c39635690]
./seg(segfault+0x10) [0x400b64]
./seg(main+0x83) [0x400bef]
/lib64/libc.so.6(__libc_start_main+0xf5) [0x3c39621b45]
./seg() [0x4009a9]
使用 objdump -d seg 查看出错地址 0x400b64 是一个赋值操作
0000000000400b54 <segfault>:
400b54: 55 push %rbp
400b55: 48 89 e5 mov %rsp,%rbp
400b58: 48 c7 45 f8 00 00 00 movq $0x0,-0x8(%rbp)
400b5f: 00
400b60: 48 8b 45 f8 mov -0x8(%rbp),%rax
400b64: c7 00 01 00 00 00 movl $0x1,(%rax)
400b6a: 5d pop %rbp
400b6b: c3 retq
使用 addr2line 0x400b64 -e seg -afs 查看段错函数和对应的代码行数
0x0000000000400b64
segfault
seg.c:41