下面是一个小例子,说明了程序出现段错误时,如何打印程序的堆栈信息。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <signal.h>
static void WidebrightSegvHandler(int signum)
{
void *array[10];
size_t size;
char **strings;
size_t i, j;
signal(signum, SIG_DFL); /* 还原默认的信号处理handler */
size = backtrace (array, 10);
strings = (char **)backtrace_symbols (array, size);
fprintf(stderr, "widebright received SIGSEGV! Stack trace:\n");
for (i = 0; i < size; i++) {
fprintf(stderr, "%d %s \n",i,strings[i]);
}
free (strings);
exit(1);
}
int invalide_pointer_error(char * p)
{
*p = 'd'; //让这里出现一个访问非法指针的错误
return 0;
}
void error_2(char * p)
{
invalide_pointer_error(p);
}
void error_1(char * p)
{
error_2(p);
}
void error_0(char * p)
{
error_1(p);
}
int main()
{
//设置 信好的处理函数
signal(SIGSEGV, WidebrightSegvHandler); // SIGSEGV 11 Core Invalid memory reference
signal(SIGABRT, WidebrightSegvHandler); // SIGABRT 6 Core Abort signal from
char *a = NULL;
error_0(a);
exit(0);
}
然后为了定位错误,我们需要加上-g参数编译一个带调试信息的版本,程序运行后打印堆栈信息如下
widebright received SIGSEGV! Stack trace:
0 ./a.out [0x8048580]
1 [0xb7fb3400]
2 ./a.out [0x8048636]
3 ./a.out [0x8048649]
4 ./a.out [0x804865c]
5 ./a.out [0x80486a9]
6 /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe5) [0xb7e52775]
7 ./a.out [0x80484c1]
知道了函数的地址可以有三种方法获取函数所在的文件和名称等相关信息。
- 使用GDB调试工具定位和调试错误。
info line *0x8048580 打印地址0x8048580处的相关信息。
list *0x8048580 获取地址0x8048580相关的上下文代码信息。
然后便可以确定行号,进行gdb调试了。 - 使用GCC编译选项生成MAP文件查找地址对应的函数信息。
使用GCC编译选项生成MAP文件查找地址对应的函数信息。在通过gcc/g++间接调用链接程序ld时,所有的ld选项前必须加上“-Wl,”,因为-Map是ld的选项。所以,要让g++生成mapfile,需要增加编译参数“ -Wl,-Map,mapfile”。
例:gcc -o helloworld helloworld.c -Wl,-Map,helloworld.map
然后通过map文件来查看函数的地址和函数名的对应关系了。 - 使用 Addr2line 将函数地址解析为函数名。
Addr2line 工具(它是标准的 GNU Binutils 中的一部分)是一个可以将指令的地址和可执行映像转换成文件名、函数名和源代码行数的工具。这种功能对于将跟踪地址转换成更有意义的内容来说简直是太棒了。
注意编译程序时需要添加-g选项才可以,也可以添加-Wl 和-map选项。
在调用 Addr2line 工具时,要使用 -e 选项来指定可执行映像是 test。通过使用 -f 选项,可以告诉工具输出函数名。
例如:addr2line 0x08048258 -e test -f