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
    评论
BackTrack Linux(现在已经更名为Kali Linux)是一款基于Debian的Linux发行版,主要用于网络安全和渗透测试。它集成了大量的渗透测试工具和安全审计工具,方便安全专业人员进行各种网络安全测试和攻击模拟。 安装BackTrack Linux(Kali Linux)可以按照以下步骤进行操作: 1. 下载BackTrack Linux(Kali Linux)的ISO镜像文件。您可以前往官方网站或者其他可靠的下载来源获取ISO镜像文件。 2. 创建一个可启动的USB闪存驱动器或者光盘。您可以使用软件如Rufus、UNetbootin或者Etcher等工具,将ISO镜像文件写入USB闪存驱动器或者刻录到光盘上。 3. 将USB闪存驱动器插入您的计算机或者将光盘放入光驱,并重启计算机。 4. 进入计算机的BIOS设置界面。您需要将计算机的启动顺序设置为从USB闪存驱动器或者光盘启动。 5. 保存设置并重新启动计算机。计算机将从BackTrack Linux(Kali Linux)的安装介质启动。 6. 在启动菜单中选择安装BackTrack Linux(Kali Linux)。您可以选择图形化安装或者文本安装,根据自己的喜好和需求进行选择。 7. 按照安装向导的提示,进行分区设置、用户创建、安装源选择等配置。 8. 等待安装程序完成,然后重新启动计算机。 9. 完成安装后,您可以登录到BackTrack Linux(Kali Linux)系统,根据需要进行进一步的配置和使用。 请注意,BackTrack Linux(Kali Linux)是针对网络安全专业人员和渗透测试人员设计的操作系统,使用时需遵循法律和道德规范,仅在合法授权和合规的情况下使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值