linux中实现堆栈跟踪

2013年6月21日 由 Creater 留言 »

堆栈跟踪主要和三个函数相关,分别为backtrace, backtrace_symbols,以及backtrace_symbols_fd.关于这三个函数的信息如下:

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

我们知道函数调用的过程是嵌套的,在存储器中一般将每个函数对应为一个堆栈帧,每个堆栈帧保存相应函数的相关信息,如:参数信息,返回地址信息,函数正文段,动态链表,词法链表等。函数调用的过程就是堆栈帧动态变化的过程,每调用一个函数,堆栈中就为该函数建立一块堆栈帧,堆栈帧的具体实现对应为一个堆栈帧数据结构,数据结构中保存前面提到的信息。

backtrace函数返回backtrace函数被调用时的堆栈信息。这些信息存放在缓冲区buffer中。backtrace函数有两个参数:

buffer:用于存放堆栈信息的缓冲区
size:用于指示buffer的大小。

要充分考虑到buffer的大小,因为如果函数调用的层次过深可能导致buffer空间不够,这样就只能保存一部分堆栈信息,靠近main函数这端的信息会因为空间不够而被裁掉。buffer是一个指向二维数组的指针,类型为void. buffer中所保存的是一系列堆栈帧的返回地址。traceback函数将返回堆栈中跟踪到的函数的个数。

将trace函数返回的buffer和返回的函数个数分别作为backtrace_symbols就可以解析出这些跟踪到的函数的符号名称,确切的说,backtrace_symbols函数将针对buffer中每一个函数返回地址进行解析,解析后的格式为:./程序名(<函数名+>函数的在程序中的十六进制格式偏移地址) [函数实际的返回十六进制格式的地址],解析后的结果保存为二维字符数组,返回值为二维数组的起始地址。

设返回的二维数组为strings,由于在backtrace_symbols内部调用了malloc开辟空间,所以需要用户来释放空间。不过用户只需要释放strings所指向的字符串指针数组即可,对于每个字符串不用释放,也不应该释放,内部会自动维护。

函数traceback_symbols_fd将输出堆栈信息到fd所说明的文件中。这样有一个好处就是不用调用malloc函数了,避免了内存分配失败的可能性。
这里提供一个已经实现的程序代码:

#ifdef LINUX
#include <cxxabi.h>
#include <execinfo.h>
#include <dlfcn.h>
#endif
const std::string Utility::Stack()
{
#if defined LINUX
#define BFSIZE 255
	void *buffer[BFSIZE];
	int n = backtrace(buffer, BFSIZE);
	char **res = backtrace_symbols(buffer, n);
	std::string tmp;
	for (int i = 0; i < n; ++i)
	{
		std::string x = res[i];
		std::string plus;
		std::string addr;
		size_t pos = x.find("(");
		if (pos != std::string::npos)
		{
			x = x.substr(pos + 1); // skip executable name

			pos = x.find(")");
			if (pos != std::string::npos)
			{
				addr = x.substr(pos + 1);
				x = x.substr(0, pos);
			}

			pos = x.find("+");
			if (pos != std::string::npos)
			{
				plus = x.substr(pos);
				x = x.substr(0, pos);
			}
		}
		char zz[1000];
		{
			size_t sz = 1000;
			int status = 0;
			abi::__cxa_demangle( x.c_str(), zz, &sz, &status);

			if (!status)
			{
				tmp += zz;
				tmp += plus;
				tmp += addr;
			}
			else
			{
				tmp += res[i];
			}
			tmp += "\n";
		}
		// dladdr() test
		if (0)
		{
			Dl_info info;
			int n = dladdr(buffer[i], &info);
			if (!n)
				printf("%d: dladdr() failed\n", i);
			else
			{
				size_t sz = 1000;
				int status = 0;
				abi::__cxa_demangle( info.dli_sname, zz, &sz, &status);

				printf("%d: %s: %s\n", i, info.dli_fname, info.dli_sname);
				if (!status)
					printf("		%s\n", zz);
			}
		} // end of dladdr() test
	} // for (i)
	free(res);
	return tmp;
#else
	return "Not available";
#endif
}

总结:使用以下几个函数既可完成堆栈信息的打印
int backtrace (void **buffer, int size)
char ** backtrace_symbols (void *const *buffer, int size)
abi::__cxa_demangle:
http://www.ib.cnea.gov.ar/~oop/biblio/libstdc++/namespaceabi.html

1. backtrace可以在程序运行的任何地方被调用,返回各个调用函数的返回地址,可以限制最大调用栈返回层数。

2. 在backtrace拿到函数返回地址之后,backtrace_symbols可以将其转换为编译符号,这些符号是编译期间就确定的

3. 根据backtrace_symbols返回的编译符号,abi::__cxa_demangle可以找到具体地函数方法

根据以上三个函数我们就可以非常方便的在程序运行的任何时间点拿到调用信息从而辅助调试。
参考文档:
http://www.gnu.org/software/libc/manual/html_node/Backtraces.html
http://linux.die.net/man/3/dlopen
http://www.ib.cnea.gov.ar/~oop/biblio/libstdc++/namespaceabi.html

广告位

发表评论

你必须 登陆 方可发表评论.