前言
关于什么是函数调用堆栈在上篇文章《windows环境下C++代码打印函数堆栈调用情况》中已经介绍过了,简单的来说就是可以展现出函数之间的调用关系,上篇文章展示了如何在windows上打印出函数调用堆栈,其中用到了windows系统上的API,这些接口在linux上是无法使用的,因为工作的关系,也常常需要在linux的调试程序,所以本文介绍一下如何在linux上打印出C++程序的调用堆栈。
实现打印堆栈信息的函数
在linux系统上想打印函数调用堆栈信息,需要引用头文件<execinfo.h>
,然后利用函数backtrace
、backtrace_symbols
来获取当时的函数调用堆栈信息,以下的代码实现了一个简单的打印堆栈新的函数,堆栈深度最大同样设置为12层。
1 |
|
显示堆栈调用信息
上面的测试代码中函数的调用逻辑为:main()
函数调用func1()
函数,然后func1()
函数调用func2()
函数,当func2()
中发生问题的时候打印当时的堆栈信息,然后我们编译一下查看运行结果
1 | [albert@localhost#10:59:03#/home/albert/test/backtrace]$g++ -rdynamic linuxtraceback.cpp -o linuxtraceback |
上面的运行结果已经展示了程序函数的调用关系,其中编译选项中的-rdynamic
是很重要的,它实际上是一个链接选项,作用是把所有符号(而不仅仅只是程序已使用到的外部符号)都添加到动态符号表里,以便那些通过 dlopen()
或 backtrace()
这样的函数使用,换句话说就是如果不加这个选项在调用堆栈中就可能看不到函数名。
上面的调用堆栈中函数名大致能看出来,但是有些奇怪的字母,可以通过工具c++fileter
来处理,处理之后就可以看到正常的函数名了,具体使用方式如下:1
2
3
4
5
6
7
8
9[albert@localhost#10:59:12#/home/albert/test/backtrace]$./linuxtraceback | c++filt
hello world
error in func2
./linuxtraceback(ShowTraceStack(char const*)+0x25) [0x400a19]
./linuxtraceback(func2()+0x1c) [0x400b06]
./linuxtraceback(func1()+0x32) [0x400b46]
./linuxtraceback(main+0x1e) [0x400b66]
/lib64/libc.so.6(__libc_start_main+0xfd) [0x7f73aea34d1d]
./linuxtraceback() [0x400939]
编译时无法添加-rdynamic选项
如果是自己写的小项目或者小程序,编译选项是可以随便改的,没有什么关系,需要查看堆栈信息加上-rdynamic
就可以了,但是如果是公司的大型项目,编译选项是不会随便改的,可能是直接使用automake
生成的,先来看一下不添加-rdynamic
选项编译之后的运行结果
1 | [albert@localhost#11:22:15#/home/albert/test/backtrace]$g++ linuxtraceback.cpp -o linuxtraceback |
可以看到不添加-rdynamic
选项编译之后运行虽然能显示出调用堆栈,但都是一些函数地址,无法看到函数名,这时可以通过工具addr2line
帮助我们定位问题,这个工具的作用就是将函数地址转换成函数所在的行,使用方法就是在命令行运行addr2line 0x4008c6 -e ./linuxtraceback
,具体使用时替换函数地址和可运行程序的名字即可
说实话这个小程序中使用运行addr2line 0x4008c6 -e ./linuxtraceback
没有看到我想要的,只显示了??:0
,仿佛被优化掉了,但是我在正式的项目中使用这个方法是可以得到函数所在行的,这也帮助我查到了一个隐藏很深的BUG。
总结
- linux平台下可以利用函数
backtrace
、backtrace_symbols
、backtrace_symbols_fd
来获取当时的函数调用堆栈信息 - 使用上述函数时,需要引用头文件
<execinfo.h>
,编译时最好加上-rdynamic
选项 - 如果实在无法添加
-rdynamic
,可以通过addr2line
辅助查找问题