我是靠谱客的博主 喜悦钢笔,最近开发中收集的这篇文章主要介绍记录:C++打印堆栈信息并优化打印结果,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

1.介绍打印堆栈信息函数
头文件:

#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()函数:获取函数调用堆栈帧数据,即回溯函数调用列表。数据将放在buffer中。参数size用来指定buffer中可以保存多少个void*元素(表示相应栈帧的地址,一个返回地址)。如果回溯的函数调用大于size,则size个函数调用地址被返回。为了取得全部的函数调用列表,应保证buffer和size足够大。backtrace函数返回通过buffer返回的地址个数,这个数目不会超过size。如果这个返回值小于size,那么所有的函数调用列表都被保存;如果等于size,那么函数调用列表可能被截断,此时,一些最开始的函数调用没有被返回。
backtrace_symbols()函数,参数buffer是从backtrace()函数获取的数组指针,size是该数组中的元素个数(backtrace()函数的返回值)。该函数主要功能:将从backtrace()函数获取的地址转为描述这些地址的字符串数组。每个地址的字符串信息包含对应函数的名字、在函数内的十六进制偏移地址、以及实际的返回地址(十六进制)。需注意的是,当前,只有使用elf二进制格式的程序才能获取函数名称和偏移地址,此外,为支持函数名功能,可能需要添加相应的编译链接选项如-rdynamic;否则,只有十六进制的返回地址能被获取。backtrace_symbols()函数返回值是一个字符串指针,是通过malloc函数申请的空间,使用完后,调用者必需把它释放掉。注:如果不能为字符串获取足够的空间,该函数的返回值为NULL。成功时,backtrace_symbols()函数返回一个由malloc分配的数组;失败时,返回NULL。
backtrace_symbols_fd()函数,与backtrace_symbols()函数具有相同的功能,不同的是它不会给调用者返回字符串数组,而是将结果写入文件描述符为fd的文件中,每个函数对应一行。它不会调用malloc函数,因此,它可以应用在函数调用可能失败的情况下。

2.示例
首先来一个C语言调用上述函数的例子:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <execinfo.h>

#define MAX_SIZE 1024

void print_trace(void)
{
    size_t i, size;
    void *array[MAX_SIZE];
    size = backtrace(array, MAX_SIZE);
   	char **strings = backtrace_symbols(array, size);
    for (i = 0; i < size; i++)
        printf("%d# %sn",i, strings[i]);
    free(strings);
}

void my_func_3(void) 
{
    print_trace();
}

void my_func_1(void)
{
    my_func_3();
}

int main(void) 
{
    my_func_1(); 
    return 0;
}

编译:

gcc -g  main.c -o out

执行结果:

0# ./out(+0x1210) [0x559e1547d210]
1# ./out(+0x12c9) [0x559e1547d2c9]
2# ./out(+0x12d9) [0x559e1547d2d9]
3# ./out(+0x12e9) [0x559e1547d2e9]
4# /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0x7fed731d8083]
5# ./out(+0x110e) [0x559e1547d10e]

这种打印存在两个问题:
问题1:没有打印出调用函数信息
解决方法:编译时候增加-rdynamic

gcc -g -rdynamic  main.c -o out

结果:

0# ./out(print_trace+0x47) [0x559283455210]
1# ./out(my_func_3+0xd) [0x5592834552c9]
2# ./out(my_func_1+0xd) [0x5592834552d9]
3# ./out(main+0xd) [0x5592834552e9]
4# /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0x7f3d1665c083]
5# ./out(_start+0x2e) [0x55928345510e]

问题2:我们可以用addr2line命令通过地址查看调用函数信息

addr2line -Cif -e out 0x559283455210

结果:产生??

??
??:0

原因:addr2line输入的地址并非其所接受的地址,addr2line接受的地址是相对偏移地址。
解决方法:编译的时候增加-no-pie

gcc -g -no-pie -rdynamic main.c -o out

结果:这里的地址和上面的地址就不同了,用这个地址输入到addr2line中

0# ./out(print_trace+0x47) [0x4011fd]
1# ./out(my_func_3+0xd) [0x4012b6]
2# ./out(my_func_1+0xd) [0x4012c6]
3# ./out(main+0xd) [0x4012d6]
4# /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0x7fca00584083]
5# ./out(_start+0x2e) [0x4010fe]
root@ubuntu:~# addr2line -Cif -e out 0x4011fd
print_trace
/root/zby/demo/backtrace_demo/main.c:13

注:(这里有个问题解释不明白,希望有会的大佬看到可以解释一下,以供学习)addr2line显示的函数行数是13,但是从代码中看到这个函数对应的行数不是13,为什么?

现在来一个c++例子:

#include <iostream>
#include <stdio.h>
#include <cxxabi.h>
#include <execinfo.h>
#include <memory>

using namespace std;

void backtrace()
{
	void* addresses[256];
	const int n = backtrace(addresses, extent< decltype(addresses) > ::value );
	const unique_ptr< char*, decltype(&free) > symbols(backtrace_symbols(addresses, n), &free);
	for(int i=0; i<n; ++i) {
		char* symbol = symbols.get()[i];
		char* end = symbol;
		cout << symbol << endl;
	}
}

void func2()
{
	backtrace();
}

void func1()
{
	func2();
}
int main()
{
	func1();
}

编译:

g++ main1.cpp -g -rdynamic -no-pie -o out

结果:

./out(_Z9backtracev+0x33) [0x403410]
./out(_Z5func2v+0xd) [0x403538]
./out(_Z5func1v+0xd) [0x403548]
./out(main+0xd) [0x403558]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0x7f0500e10083]
./out(_start+0x2e) [0x4031de]

上面的调用堆栈中函数名大致能看出来,但是有些奇怪的字母,这种情况需要做处理,c++中应用demangled来解决这个问题,具体操作见下面代码:

#include <stdio.h>
#include <cxxabi.h>
#include <execinfo.h>
#include <iostream>
#include <memory>

using namespace std;

static string demangle(const char* symbol)
{
    const unique_ptr< char, decltype( &free ) > demangled(abi::__cxa_demangle( symbol, 0, 0, 0 ), &free );
    if (demangled) {
        return demangled.get();
    }
    else {
        return symbol;
	}
}

void backtrace()
{
	void* addresses[256];
	const int n = backtrace(addresses, extent< decltype(addresses) > ::value );
	const unique_ptr< char*, decltype(&free) > symbols(backtrace_symbols(addresses, n), &free);
	for(int i=0; i<n; ++i) {
		char* symbol = symbols.get()[i];
		char* end = symbol;
		int p = 0;
		int q = 0;
		while (*end) {
			++end;
		}
		while (end != symbol && *end != '+') {
			--end;
		}
		char* begin = end;
		while(begin != symbol && *begin != '(') {
			--begin;
		}
		if (begin != symbol) {
			string str1 = string(symbol, ++begin - symbol);
			*end++ = '';
			string result = str1 + demangle(begin) + '+' + end;
			cout << "result ==== " << result << endl;
		}
		else {
			cout << symbol << endl;;
		}
	}
}

void func2()
{
	backtrace();
}

void func1()
{
	func2();
}
int main()
{
	func1();
}

相比于上面的代码增加了函数名处理,结果:

./out(backtrace()+0x33) [0x4034f0]
./out(func2()+0xd) [0x4038c4]
./out(func1()+0xd) [0x4038d4]
./out(main+0xd) [0x4038e4]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0x7f6a0c4e9083]
./out(_start+0x2e) [0x4032be]

注:中间指针操作可以优化。

总结
1.linux平台下可以利用函数backtrace、backtrace_symbols、backtrace_symbols_fd来获取当时的函数调用堆栈信息
2.使用上述函数时,需要引用头文件<execinfo.h>,编译时最好加上-rdynamic选项和-no-pie选项。
3.处理函数名格式c++中可以用demangled解决。
4.可以通过addr2line命令获取详细的函数信息。

码字不易,如有帮助,点赞收藏,如有错误,评论指正,谢谢。

END。

—————————————————追加指针操作优化———————————————————
用到了strsep()函数

void backtrace()
{
	void* addresses[256];
	const int n = backtrace(addresses, extent< decltype(addresses) > ::value );
	const unique_ptr< char*, decltype(&free) > symbols(backtrace_symbols(addresses, n), &free);
	for(int i=0; i<n; ++i) {
		char* symbol = symbols.get()[i];
		char *token = NULL;
		char *token1 = NULL;
		if ((token = strsep(&symbol, "(")) != NULL) {
			if ((token1 = strsep(&symbol, "+")) != NULL) {
				string result = (string)token + '(' + demangle(token1) + '+' + symbol;
				cout << "result === " << result << endl;
			} else {
				//如果没有+ 需要在这直接生成result
			}
		}
	}
}

END.

最后

以上就是喜悦钢笔为你收集整理的记录:C++打印堆栈信息并优化打印结果的全部内容,希望文章能够帮你解决记录:C++打印堆栈信息并优化打印结果所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(44)

评论列表共有 0 条评论

立即
投稿
返回
顶部