一、addr2line
用addr2line可以将函数地址解析为函数名,在Linux下用C/C++的程序,可以使用addr2line根据地址获取到对应的代码行。
这是一个示例程序,func函数返回参数a除以参数b的结果。这里使用0作为除数,结果就是程序因为除以0导致错误,直接中断了。
#include <stdio.h>
int func(int a, int b)
{
return a / b;
}
int main()
{
int x = 10;
int y = 0;
printf("%d / %d = %d\n", x, y, func(x, y));
return 0;
}
使用
$ gcc -g -o test1 -g test1.c
编译程序,test1.c是程序文件名。执行程序,结果程序异常中断。查看系统dmesg信息,发现系统日志的错误信息:
[54106.016179] test1[8352] trap divide error ip:400506 sp:7fff2add87e0 error:0 in test1[400000+1000]
这条信息里的ip字段后面的数字就是test1程序出错时所程序执行的位置。使用addr2line就可以将400506转换成出错程序的位置:
$ addr2line -e test1 400506 /home/hanfoo/code/test/addr2line/test1.c:5
这里的test1.c:5指的就是test1.c的第5行
return a / b;
也正是这里出现的错误。addr2line帮助我们解决了问题。
addr2line如何找到的这一行呢。在可执行程序中都包含有调试信息,其中很重要的一份数据就是程序源程序的行号和编译后的机器代码之间的对应关系Line Number Table。DWARF格式的Line Number Table是一种高度压缩的数据,存储的是表格前后两行的差值,在解析调试信息时,需要按照规则在内存里重建Line Number Table才能使用。
Line Number Table存储在可执行程序的.debug_line域,使用命令
$ readelf -w test1
可以输出DWARF的调试信息,其中有两行
Special opcode 146: advance Address by 10 to 0x4004fe and Line by 1 to 5
Special opcode 160: advance Address by 11 to 0x400509 and Line by 1 to 6
这里说明机器二进制编码的0x4004fe位置开始,对应于源码中的第5行,0x400509开始就对应与源码的第6行了,所以400506这个地址对应的是源码第5行位置。
addr2line通过分析调试信息中的Line Number Table自动就能把源码中的出错位置找出来,
二、backtrace()、backtrace_symbol() API
通过API获取程序奔溃时的函数调用栈信息
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <signal.h>
#include <execinfo.h>
// 0: GENERATE COREDUMP FILE
// 1: PRINT STACK BY SELF
int g_iTestFlag = 1;
#define ADDR_MAX_NUM 100
void CallbackSignal (int iSignalNo) {
printf ("CALLBACK: SIGNAL:\n", iSignalNo);
void *pBuf[ADDR_MAX_NUM] = {0};
int iAddrNum = backtrace(pBuf, ADDR_MAX_NUM);
printf("BACKTRACE: NUMBER OF ADDRESSES IS:%d\n\n", iAddrNum);
char ** strSymbols = backtrace_symbols(pBuf, iAddrNum);
if (strSymbols == NULL) {
printf("BACKTRACE: CANNOT GET BACKTRACE SYMBOLS\n");
return;
}
int ii = 0;
for (ii = 0; ii < iAddrNum; ii++) {
printf("%03d %s\n", iAddrNum-ii, strSymbols[ii]);
}
printf("\n");
free(strSymbols);
strSymbols = NULL;
exit(1); // QUIT PROCESS. IF NOT, MAYBE ENDLESS LOOP.
}
void FuncBadBoy() {
void* pBadThing = malloc(1024*1024*256);
free (pBadThing);
free (pBadThing);
}
void FuncBadFather() {
FuncBadBoy();
}
int main(int argc, char **argv){
if (g_iTestFlag) {
signal(SIGSEGV, CallbackSignal);
}
FuncBadFather();
return 0;
}
gcc -g test.c -o test
./test
CALLBACK: SIGNAL:
BACKTRACE: NUMBER OF ADDRESSES IS:8
008 ./test_backtrace_addr2line.elf(CallbackSignal+0x5a) [0x400a0e]
007 /lib64/libc.so.6() [0x3f2c4326a0]
006 /lib64/libc.so.6(cfree+0x1c) [0x3f2c47b93c]
005 ./test_backtrace_addr2line.elf(FuncBadBoy+0x2e) [0x400afa]
004 ./test_backtrace_addr2line.elf(FuncBadFather+0xe) [0x400b0a]
003 ./test_backtrace_addr2line.elf(main+0x49) [0x400b55]
002 /lib64/libc.so.6(__libc_start_main+0xfd) [0x3f2c41ed5d]
001 ./test_backtrace_addr2line.elf() [0x4008f9]
对于函数栈每个函数所在的源代码文件名、行号的信息,可以使用 addr2line 工具获取。对上述函数栈尾部的[0xHHHHHHHH],分别执行 addr2line -Cif -e test_backtrace_addr2line.elf 0xHHHHHHHH 即可获得具体的源文件名、行号。
addr2line -Cif -e test 0x400afa
FuncBadBoy
/root/prog/src/test2/test.c:36
addr2line -Cif -e test 0x400b0a
FuncBadFather
/root/prog/src/test2/test.c:40
addr2line -Cif -e test 0x400b55
main
/root/prog/src/test2/test.c:50