以下是一些常用的方法:
使用调试器:使用调试器,如gdb
,可以更详细地调试应用程序,包括跟踪段错误。你可以在编译时使用-g
选项来生成调试信息,然后使用调试器启动应用程序。当应用程序发生段错误时,调试器会停止程序,并提供有关引发段错误的位置的信息,包括源代码位置、函数调用栈等。可查看GDB安装这章
内存检查工具:使用内存检查工具,如Valgrind
,可以帮助你发现内存错误,包括段错误。Valgrind
可以检测应用程序中的内存访问错误、内存泄漏等问题,并提供详细的报告以指导你进行修复。
编译选项:你可以在编译时使用一些特殊的编译选项来增加对段错误的检测和报告。例如,使用-fsanitize=address
编译选项可以在运行时检测内存访问错误,并提供详细的报告。
日志文件:在应用程序中添加适当的日志输出,例如在关键的代码路径中添加打印语句来跟踪段错误。你可以将日志输出到文件中,以便在发生段错误时查看日志信息。
使用signal(SIGSEGV, recvSignal)
和addr2line
结合的方法 本文重点说明
signal(SIGSEGV, recvSignal)
和addr2line
结合方法的优点
自动处理段错误:通过将signal(SIGSEGV, recvSignal)
函数与SIGSEGV
信号绑定,当应用程序发生段错误时,会自动跳转到recvSignal
函数进行处理。这避免了应用程序崩溃,使你能够捕获并处理段错误。
获取源代码位置:addr2line
命令可以将给定的地址转换为对应的源代码位置。这使得你可以快速定位引发段错误的具体代码行,便于进行调试和修复。
准确的代码跟踪:通过在recvSignal
函数中获取函数调用栈信息,并使用addr2line
将地址转换为源代码位置,可以获取引发段错误的完整函数调用路径,而不仅仅是最后的调用位置。这提供了更详细的代码跟踪信息,有助于更好地理解问题所在。
方便的日志记录:将信息写入日志文件中,可以留下有关引发段错误的详细记录。这对于问题的跟踪和分析非常有帮助,可以随时回顾和检查日志来定位和解决问题。
#include
#include
#include
#include
#include
void recvSignal(int sig)
{
printf("SIGSEGV fault sig = %d!!!!!\n", sig);
// 获取函数调用栈信息
void *callstack[32];
int frames = backtrace(callstack, sizeof(callstack) / sizeof(void *));
char **strs = backtrace_symbols(callstack, frames);
// 打开log.txt文件
FILE *fp = fopen("log.txt", "a+");
if (fp == NULL)
{
perror("Failed to open log.txt\n");
exit(EXIT_FAILURE);
}
// 遍历函数调用栈
for (int i = 0; i < frames; i++)
{
// 打印函数调用栈信息
fprintf(fp, "[%d] %s\n", i, strs[i]);
// 使用address2line获取源代码位置
char cmd[256];
snprintf(cmd, sizeof(cmd), "addr2line -e ./your_executable %p", callstack[i]);
FILE *pipe = popen(cmd, "r");
if (pipe != NULL)
{
char buf[256];
if (fgets(buf, sizeof(buf), pipe) != NULL)
fprintf(fp, " %s", buf);
pclose(pipe);
}
}
free(strs);
fclose(fp);
exit(0);
}
int main()
{
signal(SIGSEGV, recvSignal);
// nullptr访问空指针,引发段错误
int *ptr = NULL;
*ptr = 10;
return 0;
}
在recvSignal函数中,我通过使用snprintf和popen来执行address2line命令,并从命令的输出中获取源代码位置信息。然后,将源代码位置信息写入log.txt文件中。
如果不需要日志的保存,可以直接打印在调试串口,同样可以通过address2line命令来查看段错误的发生位置。
做如下修改即可
void recvSignal(int sig)
{
printf("SIGSEGV falut sig = %d!!!!!\n",sig);
void *buf[32] = {0};
size_t size = backtrace(buf, 32);
char **strings = backtrace_symbols(buf, size);
if (strings == NULL)
{
exit(EXIT_FAILURE);
}
for(int 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(0);
}
在linux系统或其他交叉编译链工具中都有addr2line这个工具如:
arm平台:
arm-linux-gnueabi-addr2line
RISC-V平台 :
riscv64-unknown-elf-addr2line
mips平台:
mips-addr2line
addr2line
命令的基本用法:
addr2line -e <可执行文件路径> <地址>
其中,-e
选项用于指定可执行文件的路径,<地址>
是要转换的地址。
具体使用方式如下:
打开终端。
定位到包含可执行文件的目录,或者将可执行文件的完整路径作为参数。
输入以下命令:
addr2line -e <可执行文件路径> <地址>
替换<可执行文件路径>
为你的可执行文件的实际路径,<地址>
为要转换的地址。
addr2line
将会输出转换后的源代码位置信息,包括文件名和行号。例如,假设你有一个可执行文件名为myprogram
,路径为/path/to/myprogram
,要将地址0x4005a6
转换为源代码位置,你可以执行以下命令:
addr2line -e /path/to/myprogram 0x4005a6
addr2line
将会输出对应的源代码位置信息。
注:可执行文件是经过编译并包含了调试信息的。如果没有调试信息,addr2line
可能无法提供准确的结果。在编译时,可以通过使用-g
选项来保留调试信息。