Linux中backtrace()系列函数的应用实例

一、引言
backtrace()系列函数可用来输出代码出错时的函数调用关系。

A backtrace is the series of currently active function calls for the program. 

#include <execinfo.h>
int backtrace(void **buffer, int size);
char **backtrace_symbols(void *const *buffer, int size);
void backtrace_symbols_fd(void *const *buffer, int size, int fd);

二、示例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <strings.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <execinfo.h> 

#define SIZE	(100)
static sigset_t signals_handled;
	
static void dbg_backtrace(void)
{
	int i, nline;
	void *buffer[SIZE];
	char **strings;
	
	nline = backtrace(buffer, SIZE);
	printf("addr: %d = backtrace()\n", nline);
	
	strings = backtrace_symbols(buffer, nline);
	if (NULL == strings) {
		perror("backtrace_symbols()\n");
		exit(EXIT_FAILURE);
	}
	
	for (i = 0; i < nline; i ++) {
		printf("callback: %s\n", strings[j]);
	}
	/* backtrace_symbols() 内部有调用malloc函数 */
	free(strings);
}

static void sig_handler_hup(int sig)
{
	printf("%s() %d\n\n", __func__, sig);
}

static void sig_handler_debug(int sig)
{
	printf("%s() %d\n\n", __func__, sig);
}

static void sig_handler_usr2(int sig)
{
	printf("%s() %d\n\n", __func__, sig);
}

static void sig_handler_fatal(int sig)
{
	printf("Fatal signal %d\n\n", sig);
	
	dbg_backtrace();
	exit(127);
}

void signals_setup(void)
{
	struct sigaction sa;
	
	sigemptyset(&signals_handled);
	sigaddset(&signals_handled, SIGHUP);
	sigaddset(&signals_handled, SIGINT);
	sigaddset(&signals_handled, SIGTERM);
	sigaddset(&signals_handled, SIGUSR2);
	
#define SIGNAL(s, handler)	do { 	sa.sa_handler = handler;		if (sigaction(s, &sa, NULL) < 0) 		printf("could not set signal handler (%d): %m", s); 	} while(0);
	
	sa.sa_mask = signals_handled;
	sa.sa_flags = 0;
	
	SIGNAL(SIGHUP, sig_handler_hup);
	SIGNAL(SIGUSR1, sig_handler_debug);
	SIGNAL(SIGUSR2, sig_handler_usr2);
	SIGNAL(SIGABRT, sig_handler_fatal);
	SIGNAL(SIGALRM, sig_handler_fatal);
	SIGNAL(SIGFPE, sig_handler_fatal);
	SIGNAL(SIGILL, sig_handler_fatal);
	SIGNAL(SIGPIPE, sig_handler_fatal);
	SIGNAL(SIGQUIT, sig_handler_fatal);
	SIGNAL(SIGSEGV, sig_handler_fatal);
	
	signal(SIGPIPE, SIG_IGN);
}

void mem_err(void)
{
	char *prt = NULL;
	printf("%s()\n", __func__);
	
	*prt = '1';
}

int main(int argc, char **argv)
{
	signals_setup();
	int i = 0;
	
	for (; ;) {
		i ++;
		if (i > 10) {
			mem_err();
		}
		
		// printf("i: %d\n", i);
		sleep(2);
	}
		
	return 0;
}

运行结果:

mem_err()
Fatal signal 11

addr: 7 = backtrace()
callback: ./sig_setup() [0x804867b]
callback: ./sig_setup() [0x8048791]
callback: [0xb77af400]
callback: ./sig_setup() [0x8048ae7]
callback: ./sig_setup() [0x8048b13]
callback: /lib/i386-linux-gnu/i686/cmov/libc.so.6(__libc_start_main+0xe6) [0xb764ce66]
callback: ./sig_setup() [0x8048591]

三、backtrace()系列函数含义:
int backtrace(void **buffer, int size);
backtrace()返回程序调用的backtrace信息,结果(即地址:address from the corresponding stack frame)存放在buffer指针指向的数组中。
size参数指定了buffer指向的数组可保存的结果的最大个数;若结果个数大于size,数组将保存那些最近执行的函数调用。

返回值代表buffer指向的数组实际元素的个数。
char **backtrace_symbols(void *const *buffer, int size);
backtrace()返回的数组中存放的结果是地址值,backtrace_symbols()则把这些地址转化成对应的字符串。

每一个元素(字串)包含地址值对应的函数名(若不能获取,则不存在)、内部语句相对函数的偏移地址(hexadecimal)、语句的实际地址(hexadecimal)。
如:./prog(myfunc3+0x5c) [0x80487f0]
backtrace_symbols()函数内部进行了malloc操作,所以调用者必须free内存。

操作执行失败返回NULL。

void backtrace_symbols_fd(void *const *buffer, int size, int fd);
功能和backtrace_symbols()函数一样,只是它把返回结果按行写到了fd代表的文件里。
backtrace_symbols_fd函数内部没有进行malloc操作!

四、结果分析:
先执行反汇编:objdump -d test > dump.txt
callback: ./sig_setup() [0x804867b]:
	0804865c <dbg_backtrace>:
	 804865c:	55                   	push   %ebp
	 804865d:	89 e5                	mov    %esp,%ebp
	 804865f:	81 ec b8 01 00 00    	sub    $0x1b8,%esp
	 8048665:	c7 44 24 04 64 00 00 	movl   $0x64,0x4(%esp)
	 804866c:	00 
	 804866d:	8d 85 5c fe ff ff    	lea    -0x1a4(%ebp),%eax
	 8048673:	89 04 24             	mov    %eax,(%esp)
	 8048676:	e8 e5 fe ff ff       	call   8048560 <backtrace@plt>
	 804867b:	89 45 f0             	mov    %eax,-0x10(%ebp)

callback: ./sig_setup() [0x8048791]
	08048773 <sig_handler_fatal>:
	 8048773:	55                   	push   %ebp
	 8048774:	89 e5                	mov    %esp,%ebp
	 8048776:	83 ec 18             	sub    $0x18,%esp
	 8048779:	8b 45 08             	mov    0x8(%ebp),%eax
	 804877c:	89 44 24 04          	mov    %eax,0x4(%esp)
	 8048780:	c7 04 24 15 8c 04 08 	movl   $0x8048c15,(%esp)
	 8048787:	e8 14 fd ff ff       	call   80484a0 <printf@plt>
	 804878c:	e8 cb fe ff ff       	call   804865c <dbg_backtrace>
	 8048791:	c7 04 24 7f 00 00 00 	movl   $0x7f,(%esp)

callback: ./sig_setup() [0x8048ae7]
08048ac3 <mem_err>:
 8048ac3:	55                   	push   %ebp
 8048ac4:	89 e5                	mov    %esp,%ebp
 8048ac6:	83 ec 28             	sub    $0x28,%esp
 8048ac9:	c7 45 f4 00 00 00 00 	movl   $0x0,-0xc(%ebp)
 8048ad0:	c7 44 24 04 5b 8c 04 	movl   $0x8048c5b,0x4(%esp)
 8048ad7:	08 
 8048ad8:	c7 04 24 4e 8c 04 08 	movl   $0x8048c4e,(%esp)
 8048adf:	e8 bc f9 ff ff       	call   80484a0 <printf@plt>
 8048ae4:	8b 45 f4             	mov    -0xc(%ebp),%eax
 8048ae7:	c6 00 31             	movb   $0x31,(%eax)
至此可知,这里的函数调用,造成了非法的内存操作。

五、backtrace()系列函数注意事项:
这三个函数都假设函数的返回地址按它认为的方式保存在栈上,故使用时应注意:

1、帧指针(Frame  pointers)的屏蔽/忽略可能导致上述假设失效
2、inline关键字描述的函数没有栈帧(stack frames)
3、尾调用(Tail-call)优化会造成一个栈帧被另一个替换掉
4、对于特定编译器,应该指定链接选项,否则函数名字段可能是无效的;对于使用GNU linker的系统,使用-rdynamic链接项
5、static关键字描述的函数不会被显示,and won‘t be available in the backtrace

PS:
栈帧(stack frames):
In computer science, a stack frame is a memory management strategy used to create and destroy temporary (automatic) variables in some programming languages. Stack frames only exist at run-time.
在计算机科学里,栈帧是一种内存管理策略,在某些编程语言里它用来创建/销毁临时(自动)变量。栈帧只在运行时存在。
尾调用(Tail-call):
In computer science, a tail call is a subroutine call performed as the final action of a procedure.
在计算机科学里,尾调用是指一个函数里的最后一个动作是一个函数调用的情形:即这个调用的返回值直接被当前函数返回的情形。

参考资料:
帧指针(Frame  pointers):http://stackoverflow.com/questions/579262/what-is-the-purpose-of-the-frame-pointer
http://blog.chinaunix.net/uid-25871104-id-2938389.html
栈帧(stack frames):http://en.citizendium.org/wiki/Stack_frame
尾调用(Tail-call):http://en.wikipedia.org/wiki/Tail_call

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。