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

提升堆栈跟踪

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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在不使用execinfo库的情况下,你可以按照以下步骤编写一个简单的嵌入式Linux堆栈打印代码: 1. 导入头文件"signal.h"和"ucontext.h"。 2. 使用sigaction()函数来注册一个信号处理程序。我们可以使用SIGSEGV信号来捕获堆栈溢出错误。 3. 在信号处理程序中,使用ucontext_t结构体来获取当前堆栈的上下文信息。ucontext_t结构体包含了一个指向当前上下文中寄存器值的指针。 4. 使用该指针获取堆栈指针,并遍历堆栈来获取函数和地址。你可以使用反汇编器来获取这些信息。 下面是一个简单的示例代码,可以让你更好地理解如何实现堆栈打印: ```c #include <signal.h> #include <ucontext.h> #include <stdio.h> void signal_handler(int signal, siginfo_t* info, void* context) { ucontext_t* ucontext = (ucontext_t*) context; void* stack_pointer = (void*) ucontext->uc_mcontext.gregs[REG_SP]; void* instruction_pointer = (void*) ucontext->uc_mcontext.gregs[REG_IP]; printf("Stack trace:\n"); // Iterate through stack frames for (int i = 0; i < 10; i++) { // Get the function address and name from the instruction pointer Dl_info dl_info; dladdr(instruction_pointer, &dl_info); void* function_address = dl_info.dli_saddr; const char* function_name = dl_info.dli_sname; printf("#%d %p in %s\n", i, function_address, function_name); // Move to the next frame stack_pointer = (void*) ((long*) stack_pointer + 1); instruction_pointer = ((void**) stack_pointer)[0]; } exit(1); } int main() { struct sigaction sa; sa.sa_flags = SA_SIGINFO; sa.sa_sigaction = signal_handler; sigemptyset(&sa.sa_mask); sigaction(SIGSEGV, &sa, NULL); // Generate a segfault for testing purposes int* ptr = NULL; *ptr = 0; return 0; } ``` 这个代码使用SIGSEGV信号处理程序来捕获堆栈溢出错误,并使用ucontext_t结构体获取当前上下文中的寄存器值。然后,它遍历堆栈打印函数地址和称。请注意,此示例仅用于演示目的,不应在生产环境中使用。 ### 回答2: 在嵌入式Linux系统中,如果没有使用execinfo库,我们仍然可以通过其他方法编写堆栈打印代码。 一种方法是使用内核提供的sysrq功能。内核可以提供一个特殊的命令,通过该命令可以打印当前任务的堆栈信息。我们可以在代码中使用sysrq命令,触发内核将当前任务的堆栈信息输出到控制台。代码示例如下: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/syscall.h> int main() { pid_t pid = getpid(); syscall(SYS_sysrq, 't', pid); return 0; } ``` 代码中,首先获取当前任务的进程ID(pid),然后使用syscall函数调用sysrq命令,并传递参数't'和pid。在代码中执行该命令后,堆栈信息将被输出到控制台。 另一种方法是使用Linux中的信号处理机制。我们可以为程序注册一个信号处理函数,在函数调用backtrace函数来获取堆栈信息,并将其输出到文件或其他目标。代码示例如下: ```c #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <execinfo.h> void signal_handler(int signum) { void* buffer[10]; size_t size; char** strings; size = backtrace(buffer, 10); strings = backtrace_symbols(buffer, size); if (strings != NULL) { for (size_t i = 0; i < size; i++) { printf("%s\n", strings[i]); } free(strings); } exit(signum); } int main() { signal(SIGSEGV, signal_handler); // 其他代码... return 0; } ``` 代码中,首先定义了一个信号处理函数signal_handler。当代码运行中发生段错误(SIGSEGV)时,操作系统会发送该信号,并调用函数函数内部使用backtrace函数获取当前任务的堆栈信息,然后通过printf函数打印到控制台。最后通过exit函数退出程序。 以上是两种不使用execinfo库的方法来编写嵌入式Linux堆栈打印代码。根据你的需求和系统环境,你可以选择适合的方法进行使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值