前言:
平时出现段错误多数是必现的,自己通常通过打印日志来定位,最近项目中出现一个偶现的段错误,由于没有方向加日志的方法来复现效率太低,所以痛定思痛决定搞一个高效跟踪段错误的手段,经过大量查阅资料,写了一个backtrace的功能加入项目,利用addr2line或nm从而高效定位段错误的问题!!!
必要知识介绍:
nm 是一个 Linux 系统下的命令行工具,用于显示二进制目标文件(可执行文件或库文件)中的符号(Symbol),包括函数和变量名等。
nm 可以用于检查二进制文件的符号表信息,它可以列出二进制文件中包含的所有符号、这些符号所处的节(节是编译后二进制文件中包含的一个段)以及它们的类型(如函数、变量等)。nm 常用于调试和检查程序的二进制文件。
nm 命令的常见用法如下:
nm <file>:查看文件(如可执行文件或库文件)中包含的所有符号。
nm -u <file>:列出未定义符号的名称,这些符号是需要在连接时动态解析的符号,通常需要通过静态或共享库来定义。
nm -g <file>:只列出全局符号的名称,全局符号是可被其他模块所引用的符号,多次定义时会引发重定义错误。
nm -D <file>:只列出动态符号的名称,动态符号是在动态链接时解析的符号,如共享库中导出的符号。
nm -C <file>:将 C++ 操作符和函数名还原为可读的形式。
T:该符号是一个函数,并且是在该文件中定义的。
t:该符号是一个函数,并且是在该文件中局部定义的(即只能在该文件中访问)。
D:该符号是一个已初始化的全局变量或静态变量,并且是在该文件中定义的。
d:该符号是一个已初始化的局部变量或静态变量,并且是在该文件中定义的。
B:该符号是一个未初始化的全局变量或静态变量,并且是在该文件中定义的。
b:该符号是一个未初始化的局部变量或静态变量,并且是在该文件中定义的。
U:该符号是一个未定义的符号,需要在链接时解析,通常是在其他文件或库文件中被定义的符号
add2line
addr2line是一个GNU调试工具,用于将程序计数器(PC)地址转换为对应的源文件名、函数名和行号。addr2line可通过调试信息,将内存地址映射到源代码行号,并在开发人员调试应用程序时帮助找到问题所在。addr2line通常与交叉编译器一起使用,用于在代码嵌入式或远程设备上汇报错误的处理信息。其中,参数说明如下:
-C: 此选项用于修复函数名。addr2line可以对编译器生成的能够适应C++的名称进行反向演绎,使名称更易于读取和理解。
-f: 此选项用于输出函数名称。
-p: 此选项用于调解指针地址为C++名称。
-H: 此选项用于输出全部文件名、函数名称、和行号。
-s: 此选项用于输出文件名和行号。
-v: 此选项用于详细输出,比如唯一的文件和否则可能无法报告的错误。
-V: 此选项用于输出程序版本信息。
-e filename: 此选项用于指定可执行文件。
-j section: 此选项用于指定要查找的节。
-a address: 此选项用于指定要查找的内存地址
实例代码:
#include "mg_backtrace.h"
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>#define BACKTRACE_MAX_NUM 32 //嵌套数,大型项目一般也不会超过50个,32个够用
void mg_signal_handle(int sig)
{
int i = 0;
void *buf[32] = {0};size_t size = backtrace(buf, BACKTRACE_MAX_NUM);
printf("sig = %d, size:%d!!!!!\n", sig, size);
/*
说明:
1. backtrace_symbols将从backtrace函数获取的信息转化为一个字符串数组。2. 参数buffer应该是从backtrace函数获取的指针数组,size是该数组中的元素个数(backtrace的返回值)。
3. 函数返回值是一个指向字符串数组的指针,它的大小同buffer相同。每个字符串包含了一个相对于
buffer中对应元素的可打印信息。它包括函数名、函数的偏移地址、实际的返回地址。4. 目前,只有使用ELF二进制格式的程序才能获取函数名称和偏移地址。在其他系统,只有16进制的返回地址能被
获取。另外,你可能需要传递相应的符号给链接器,以能支持函数名功能。(比如,在使用GNU ld链接器的系统
中,器支持-rdynamic的话,建议将其加上!)5. 该函数的返回值是通过malloc函数申请的空间,因此调用者必须使用free函数来释放指针。
6. 如果不能为字符串获取足够的空间函数的返回值将会为NULL。
*/char **strings = backtrace_symbols(buf, size);
if (strings == NULL)
{
exit(EXIT_FAILURE);
}
for (i = 0; i < (int)size; i++)
{
printf("[%d] %s\n", i, strings[i]);
printf("backtrace : [%d] %x\n", i, (unsigned int)buf[i]);
}free(strings);
exit(-1);}
void mg_signal_register(void)
{
signal(SIGSEGV, mg_signal_handle);
}
问题一:
编译后模拟尝试 结果 backtrace返回0
解决方案:
arm平台编译选项加上 -rdynamic -funwind-tables -ffunction-sections
问题二:
还不是最终崩溃的地方,第4和第5行是最开始的调用处
利用addr2line定位最终崩溃处,如没有addr2line,可以用nm定性分析,定位到崩溃函数内
addr2line 0x50250 -e /moorgen/bin/moorgenService/moorgenService -f
addr2line 0x4b510 -e /moorgen/bin/moorgenService/moorgenService -f
最终定位到shcpProtocol.c 504行就是段错误处!
跟踪结果体现了调用过程shcp_client_recv_cb->shcpSocketEvent->schpDataEvent->shcpConnectProc(504行最终崩溃的地方)
nm /moorgen/bin/moorgenService/moorgenService
崩溃的地址0x4b510处于0x0004b4e8和0x0004e540之间则说明崩溃函数为shcpConnectProc