linux c 打印堆栈 函数名,用C或C ++打印调用堆栈

本文探讨了在C++和C中如何利用不同方法进行堆栈跟踪,包括Boost库、glibc的backtrace、addr2line以及libunwind。通过实例展示了优化级别对堆栈跟踪的影响,强调了-O0级别可以获得完整的回溯信息,而在-O3优化时,回溯可能会因内联优化而丢失关键信息。文章还提到了demangling和addr2line工具在解析符号和行号上的作用。
摘要由CSDN通过智能技术生成

提升堆栈跟踪

这是我到目前为止看到的最方便的选项,因为它:实际上可以打印出行号。

addr2line然而,它只是调用,这很难看,如果你的痕迹太多,可能会很慢。

demangles默认情况下

Boost只是标题,因此最不需要修改构建系统

main.cpp中#include #define BOOST_STACKTRACE_USE_ADDR2LINE#include void my_func_2(void) {

std::cout <

my_func_2();}void my_func_1(int i) {

my_func_2();}int main() {

my_func_1(1);   /* line 19 */

my_func_1(2.0); /* line 20 */}

不幸的是,它似乎是一个更新的添加,并且libboost-stacktrace-devUbuntu 16.04中没有包,只有18.04:sudo apt-get install libboost-stacktrace-dev

g++ -fno-pie -ggdb3 -O0 -no-pie -o main.out -std=c++11 \  -Wall -Wextra -pedantic-errors main.cpp -ldl

我们必须-ldl在最后添加,否则编译失败。

然后:./main.out

得到:0# my_func_2() at /root/lkmc/main.cpp:7

1# my_func_1(int) at /root/lkmc/main.cpp:16

2# main at /root/lkmc/main.cpp:20

3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6

4# _start in ./main.out

0# my_func_2() at /root/lkmc/main.cpp:7

1# my_func_1(double) at /root/lkmc/main.cpp:12

2# main at /root/lkmc/main.cpp:21

3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6

4# _start in ./main.out

并与-O3:0# my_func_2() at /usr/include/boost/stacktrace/stacktrace.hpp:217

1# my_func_1(double) at /root/lkmc/main.cpp:11

2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6

3# _start in ./main.out

0# my_func_2() at /usr/include/boost/stacktrace/stacktrace.hpp:217

1# main at /root/lkmc/main.cpp:21

2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6

3# _start in ./main.out

请记住,回溯通常会被优化无可挽回地毁掉。尾调用优化是一个值得注意的例子:什么是尾调用优化?

输出并在下面的“glibc backtrace”部分进一步解释,这是类似的。

在Ubuntu 18.04,GCC 7.3.0上测试,提升1.65.1。

glibc回溯

main.c中#include #include /* Paste this on the file you want to debug. */#include #include void print_trace(void) {

char **strings;

size_t i, size;

enum Constexpr { MAX_SIZE = 1024 };

void *array[MAX_SIZE];

size = backtrace(array, MAX_SIZE);

strings = backtrace_symbols(array, size);

for (i = 0; i 

printf("%s\n", strings[i]);

puts("");

free(strings);}void my_func_3(void) {

print_trace();}void my_func_2(void) {

my_func_3();}void my_func_1(void) {

my_func_3();}int main(void) {

my_func_1(); /* line 33 */

my_func_2(); /* line 34 */

return 0;}

编译:gcc -fno-pie -ggdb3 -O3 -no-pie -o main.out -rdynamic -std=c99 \  -Wall -Wextra -pedantic-errors main.c

-rdynamic 是关键的必要选项。

跑:./main.out

输出:./main.out(print_trace+0x2d) [0x400a3d]./main.out(main+0x9) [0x4008f9]/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f35a5aad830]./main.out(_start+0x29) [0x400939]./main.out(print_trace+0x2d) [0x400a3d]./main.out(main+0xe) [0x4008fe]/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f35a5aad830]./main.out(_start+0x29) [0x400939]

因此我们立即看到内联优化发生了,并且一些函数从跟踪中丢失了。

如果我们试图获取地址:addr2line -e main.out 0x4008f9 0x4008fe

我们获得:/home/ciro/main.c:21/home/ciro/main.c:36

完全没了。

如果我们-O0改为相同,则./main.out给出正确的完整跟踪:./main.out(print_trace+0x2e) [0x4009a4]./main.out(my_func_3+0x9) [0x400a50]./main.out(my_func_1+0x9) [0x400a68]./main.out(main+0x9) [0x400a74]/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f4711677830]./main.out(_start+0x29) [0x4008a9]./main.out(print_trace+0x2e) [0x4009a4]./main.out(my_func_3+0x9) [0x400a50]./main.out(my_func_2+0x9) [0x400a5c]./main.out(main+0xe) [0x400a79]/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f4711677830]./main.out(_start+0x29) [0x4008a9]

然后:addr2line -e main.out 0x400a74 0x400a79

得到:/home/cirsan01/test/main.c:34/home/cirsan01/test/main.c:35

所以线条只有一个,TODO为什么?但这可能仍然有用。

结论:回溯只能完美地表现出来-O0。通过优化,原始回溯在编译代码中得到根本修改。

在Ubuntu 16.04,GCC 6.4.0,libc 2.23上测试。

glibc的 backtrace_symbols_fd

这个帮助器比它更方便backtrace_symbols,并产生基本相同的输出:/* Paste this on the file you want to debug. */#include #include #include void print_trace(void) {

size_t i, size;

enum Constexpr { MAX_SIZE = 1024 };

void *array[MAX_SIZE];

size = backtrace(array, MAX_SIZE);

backtrace_symbols_fd(array, size, STDOUT_FILENO);

puts("");}

在Ubuntu 16.04,GCC 6.4.0,libc 2.23上测试。

libunwind

TODO这比glibc回溯有什么优势吗?非常相似的输出,也需要修改构建命令,但不太广泛可用。

main.c中/* This must be on top. */#define _XOPEN_SOURCE 700#include #include /* Paste this on the file you want to debug. */#define UNW_LOCAL_ONLY#include #include void print_trace() {

char sym[256];

unw_context_t context;

unw_cursor_t cursor;

unw_getcontext(&context);

unw_init_local(&cursor, &context);

while (unw_step(&cursor) > 0) {

unw_word_t offset, pc;

unw_get_reg(&cursor, UNW_REG_IP, &pc);

if (pc == 0) {

break;

}

printf("0x%lx:", pc);

if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) {

printf(" (%s+0x%lx)\n", sym, offset);

} else {

printf(" -- error: unable to obtain symbol name for this frame\n");

}

}

puts("");}void my_func_3(void) {

print_trace();}void my_func_2(void) {

my_func_3();}void my_func_1(void) {

my_func_3();}int main(void) {

my_func_1(); /* line 46 */

my_func_2(); /* line 47 */

return 0;}

编译并运行:sudo apt-get install libunwind-dev

gcc -fno-pie -ggdb3 -O3 -no-pie -o main.out -std=c99 \  -Wall -Wextra -pedantic-errors main.c -lunwind

要么#define _XOPEN_SOURCE 700必须在顶部,要么必须使用-std=gnu99:

跑:./main.out

输出:0x4007db: (main+0xb)0x7f4ff50aa830: (__libc_start_main+0xf0)0x400819: (_start+0x29)0x4007e2: (main+0x12)0x7f4ff50aa830: (__libc_start_main+0xf0)0x400819: (_start+0x29)

和:addr2line -e main.out 0x4007db 0x4007e2

得到:/home/ciro/main.c:34/home/ciro/main.c:49

用-O0:0x4009cf: (my_func_3+0xe)0x4009e7: (my_func_1+0x9)0x4009f3: (main+0x9)0x7f7b84ad7830: (__libc_start_main+0xf0)0x4007d9: (_start+0x29)0x4009cf: (my_func_3+0xe)0x4009db: (my_func_2+0x9)0x4009f8: (main+0xe)0x7f7b84ad7830: (__libc_start_main+0xf0)0x4007d9: (_start+0x29)

和:addr2line -e main.out 0x4009f3 0x4009f8

得到:/home/ciro/main.c:47/home/ciro/main.c:48

在Ubuntu 16.04,GCC 6.4.0,libunwind 1.1上测试。

C ++ demangling

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值