输出函数调用的堆栈信息

通过该方法可快速定位问题以及找出函数的调用者

Posted by VK on August 19, 2019

输出函数调用的堆栈信息

1.1 引言

GDB的backtrace命令可以查看堆栈信息。但很多时候,GDB根本用不上。比如说,在线上环境中可能没有GDB,即使有,也有时候堆栈消息是乱的。如果能让程序自己输出调用栈,是最好不过的。另外该方法也不一定只有进程挂掉才能看,正常情况也是可以使用的,比如当你想知道你提供的接口到底是谁在调用的时候。

1.2 接口说明及使用

#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);

以上内容源自man手册。

先简单介绍一下这几个函数的功能:

  • backtrace:获取当前的调用栈信息,结果存储在buffer中,返回值为栈的深度,参数size限制栈的最大深度,即最大取size步的栈信息。
  • backtrace_symbols:把backtrace获取的栈信息转化为字符串,以字符指针数组的形式返回,参数size限定转换的深度,一般用backtrace调用的返回值。
  • backtrace_symbols_fd:它的功能和backtrace_symbols差不多,只不过它不把转换结果返回给调用方,而是写入fd指定的文件描述符。

Man手册里,有一个简单的实例,自己也写了简单的来测试一下:

/*************************************************************************
	> File Name: backtrace.cpp
	> Author: Veitch
	> Created Time: 2019年08月19日 星期一 14时25分45秒
 ************************************************************************/

#include<iostream>
#include <execinfo.h> 
#include <stdio.h>
using namespace std;


void func1()
{
	void *addr[32];
	int size = backtrace(addr,100);
	char **array = backtrace_symbols(addr,size);
	//printf("array[0]:%s",array[0]);
	if (array == NULL) {
		 perror("backtrace_symbols");
		 exit(EXIT_FAILURE);
	}
	
	for(int i = 0;i<size;i++)
	{
		printf("addr[%d]:%p\n",i,(char*)(addr[i]));
		printf("array[%d]:%s\n",i,array[i]);
	}
	free(array);
}


void func2()
{
	func1();
}



int main()
{
	func2();
	return 0;
}

结果输出如下:

array[0]:./backtrace() [0x400914]
array[1]:./backtrace() [0x4009da]
array[2]:./backtrace() [0x4009e6]
array[3]:/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7ffad3f7c830]
array[4]:./backtrace() [0x400819]

这样,是输出了调用栈,不过只是以十六进制输出函数地址而已,可读性很差。仔细看下man手册,原来很简单,编译时加上个参数 rdynamic:

 g++ backtrace.cpp -o backtrace -std=c++11 -rdynamic

重新编译运行:

./backtrace
addr[0]:0x400b24
array[0]:./backtrace(_Z5func1v+0x2e) [0x400b24]
addr[1]:0x400c11
array[1]:./backtrace(_Z5func2v+0x9) [0x400c11]
addr[2]:0x400c1d
array[2]:./backtrace(main+0x9) [0x400c1d]
addr[3]:0x7f09c6de0830
array[3]:/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f09c6de0830]
addr[4]:0x400a29
array[4]:./backtrace(_start+0x29) [0x400a29]

这样就能看到函数名了