backtrace调试程序段错误

1、backtrace的用处

一般察看函数运行时堆栈的方法是使用GDB(bt命令)之类的外部调试器,但是,有些时候为了分析程序的BUG,(主要针对长时间运行程序的分析),在程序出错时打印出函数的调用堆栈是非常有用的。主要用于程序异常退出时寻找错误原因。

通常情况下,程序发生段错误时系统会发送SIGSEGV信号给程序,缺省处理是退出函数。我们可以使用 signal(SIGSEGV, &your_function);函数来接管SIGSEGV信号的处理,程序在发生段错误后,自动调用我们准备好的函数,从而在那个函数里来获取当前函数调用栈。

2、提供的接口

int backtrace(void **buffer, int size);

backtrace()函数用来获取程序中当前函数的回溯信息,即一系列的函数调用关系,获取到的信息被放在参数buffer中。buffer是一个数组指针,数组的每个元素保存着每一级被调用函数的返回地址。参数size指定了buffer中可存放的返回地址的数量。如果函数实际的回溯层级数大于size,则buffer中只能存放最近的函数调用关系,所以,想要得到完整的回溯信息,就要确保size参数足够大。
backtrace()函数的返回值为buffer中的条目数量,这个值不一定等于size,因为如果为得到完整回溯信息而将size设置的足够大,则该函数的返回值为buffer中实际得到的返回地址数量。

char **backtrace_symbols(void *const *buffer, int size);

通过backtrace()函数得到buffer之后,backtrace_symbols()可以将其中的返回地址都对应到具体的函数名,参数size为buffer中的条目数。backtrace_symbols()函数可以将每一个返回值都翻译成“函数名+函数内偏移量+函数返回值”,这样就可以更直观的获得函数的调用关系。
经过翻译后的函数回溯信息放到backtrace_symbols()的返回值中,如果失败则返回NULL。需要注意,返回值本身是在backtrace_symbols()函数内部进行malloc的,所以必须在后续显式地free掉。

void backtrace_symbols_fd(void *const *buffer, int size, int fd);

backtrace_symbols_fd()的buffer和size参数和backtrace_symbols()函数相同,只是它翻译后的函数回溯信息不是放到返回值中,而是一行一行的放到文件描述符fd对应的文件中。

注意:

  • backtrace的实现依赖于栈指针(fp寄存器),在gcc编译过程中任何非零的优化等级(-On参数)或加入了栈指针优化参数-fomit-frame-pointer后多将不能正确得到程序栈信息;加上 -g 选项以后创建符号表,关闭所有的优化机制。
  • 在编译的时候需要加上-rdynamic选项让链接器将所有符号添加到动态符号表中,这样才能将函数地址翻译成函数名。另外,这个选项不会处理static函数,所以,static函数的符号无法得到。
  • 内联函数没有栈帧,它在编译过程中被展开在调用的位置;
  • 尾调用优化(Tail-call Optimization)将复用当前函数栈,而不再生成新的函数栈,这将导致栈信息不能正确被获取。

所以编译方法应该这样:  $ gcc -g  *.c   -rdynamic           -rdynamic需要放后面,不知道为什么?

3、示例:生成backtrace文件

/**   add.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
int add1(int num)
{
	int ret = 0x00;
	int *pTemp = NULL;
	
	*pTemp = 0x01;  /* 这将导致一个段错误,致使程序崩溃退出 */
	ret = num + *pTemp;
	return ret;
}
 
int add(int num)
{
	int ret = 0x00;
	ret = add1(num);
	return ret;
}
/**   dump.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>	    /* for signal */
#include <execinfo.h> 	/* for backtrace() */
 
#define BACKTRACE_SIZE   16
 
void dump(void)
{
	int j, nptrs;
	void *buffer[BACKTRACE_SIZE];
	char **strings;
	
	nptrs = backtrace(buffer, BACKTRACE_SIZE);
	
	printf("backtrace() returned %d addresses\n", nptrs);
 
	strings = backtrace_symbols(buffer, nptrs);
	if (strings == NULL) {
		perror("backtrace_symbols");
		exit(EXIT_FAILURE);
	}
 
	for (j = 0; j < nptrs; j++)
		printf("  [%02d] %s\n", j, strings[j]);
 
	free(strings);
}
 
void signal_handler(int signo)
{
	
#if 0	
	char buff[64] = {0x00};
		
	sprintf(buff,"cat /proc/%d/maps", getpid());
		
	system((const char*) buff);
#endif	
 
	printf("\n=========>>>catch signal %d <<<=========\n", signo);
	
	printf("Dump stack start...\n");
	dump();
	printf("Dump stack end...\n");
 
	signal(signo, SIG_DFL); /* 恢复信号默认处理 */
	raise(signo);           /* 重新发送信号 */
}
/**   backtrace.c  */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>	    /* for signal */
#include <execinfo.h> 	/* for backtrace() */
 
extern void dump(void);
extern void signal_handler(int signo);
extern int add(int num);
 
int main(int argc, char *argv[])
{
	int sum = 0x00;
	
	signal(SIGSEGV, signal_handler);  /* 为SIGSEGV信号安装新的处理函数 */
	
	sum = add(sum);
	
	printf(" sum = %d \n", sum);
	
	return 0x00;
}

输出如下:

gm@gm-pc:~/test/backtrace
$ gcc -g -rdynamic backtrace.c add.c dump.c -o backtrace      
gm@gm-pc:~/test/backtrace
$ ./backtrace 
563ef7e06000-563ef7e07000 r--p 00000000 08:02 5389091                    /home/gm/test/backtrace/backtrace
563ef7e07000-563ef7e08000 r-xp 00001000 08:02 5389091                    /home/gm/test/backtrace/backtrace
563ef7e08000-563ef7e09000 r--p 00002000 08:02 5389091                    /home/gm/test/backtrace/backtrace
563ef7e09000-563ef7e0a000 r--p 00002000 08:02 5389091                    /home/gm/test/backtrace/backtrace
563ef7e0a000-563ef7e0b000 rw-p 00003000 08:02 5389091                    /home/gm/test/backtrace/backtrace
7f3b68fff000-7f3b69021000 r--p 00000000 08:04 659962                     /usr/lib/libc-2.28.so
7f3b69021000-7f3b6916c000 r-xp 00022000 08:04 659962                     /usr/lib/libc-2.28.so
7f3b6916c000-7f3b691b8000 r--p 0016d000 08:04 659962                     /usr/lib/libc-2.28.so
7f3b691b8000-7f3b691b9000 ---p 001b9000 08:04 659962                     /usr/lib/libc-2.28.so
7f3b691b9000-7f3b691bd000 r--p 001b9000 08:04 659962                     /usr/lib/libc-2.28.so
7f3b691bd000-7f3b691bf000 rw-p 001bd000 08:04 659962                     /usr/lib/libc-2.28.so
7f3b691bf000-7f3b691c5000 rw-p 00000000 00:00 0 
7f3b691f9000-7f3b691fb000 r--p 00000000 08:04 659917                     /usr/lib/ld-2.28.so
7f3b691fb000-7f3b6921a000 r-xp 00002000 08:04 659917                     /usr/lib/ld-2.28.so
7f3b6921a000-7f3b69222000 r--p 00021000 08:04 659917                     /usr/lib/ld-2.28.so
7f3b69222000-7f3b69223000 r--p 00028000 08:04 659917                     /usr/lib/ld-2.28.so
7f3b69223000-7f3b69224000 rw-p 00029000 08:04 659917                     /usr/lib/ld-2.28.so
7f3b69224000-7f3b69225000 rw-p 00000000 00:00 0 
7fffb3e86000-7fffb3ea7000 rw-p 00000000 00:00 0                          [stack]
7fffb3f07000-7fffb3f0a000 r--p 00000000 00:00 0                          [vvar]
7fffb3f0a000-7fffb3f0c000 r-xp 00000000 00:00 0                          [vdso]

=========>>>catch signal 11 <<<=========
Dump stack start...
backtrace() returned 8 addresses
  [00] ./backtrace(dump+0x2e) [0x563ef7e072d2]
  [01] ./backtrace(signal_handler+0xac) [0x563ef7e07458]
  [02] /usr/lib/libc.so.6(+0x37e00) [0x7f3b69036e00]
  [03] ./backtrace(add1+0x1a) [0x563ef7e07267]
  [04] ./backtrace(add+0x1c) [0x563ef7e0729c]
  [05] ./backtrace(main+0x34) [0x563ef7e0722d]
  [06] /usr/lib/libc.so.6(__libc_start_main+0xf3) [0x7f3b69023223]
  [07] ./backtrace(_start+0x2e) [0x563ef7e0712e]
Dump stack end...
[1]    11205 segmentation fault (core dumped)  ./backtrace
gm@gm-pc:~/test/backtrace

4、backtrace文件分析方法

由此可见在调用完函数add1后就开始调用段错误信号处理函数了,所以问题是出在函数add1中。这似乎还不够,更准确的位置应该是在地址0x563ef7e07267处,这好像是一个虚拟地址,addr2line需要的估计是错误信息在elf文件中的地址,所以还需要减去文件的加载地址

但这到底是哪一行呢,我们使用addr2line命令来得到,执行如下:

我们可以通过 /proc 观察到一个正在运行着的Linux系统的内核数据信息以及各进程相关的信息。所以我们如果要查看某一个进程的内存空间情况,也可以通过它来进行。

也可以用gcc编译生成map文件分析错误的位置。立个flag方便以后查找,不写了。

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值