在c语言中输出调用栈

#include <execinfo.h>

void my_backtrace()

{

    void *buffer[100] = {NULL};

    char **trace = NULL;

    int i = 0;

    int size = 0;

 

    size = backtrace(buffer, 100);

    trace = backtrace_symbols(buffer, size);

    if (NULL == trace)

    {

        return;

    }

 

    for (i = 0; i < size; ++i)

    {

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

    }

 

    free(trace);

    printf("----------done----------\n");

}

 

转载于:https://www.cnblogs.com/duanqibo/p/11140613.html

<think>在C语言中,打印调用栈(也称为堆栈跟踪)通常用于调试程序,尤其是在程序崩溃或出现异常时。不同的操作系统和平台提供了不同的方法来实现这一功能。以下是在几种常见环境下的实现方法: ### 1. Linux/Unix 系统 (使用 glibc 的 backtrace 函数) 在Linux系统上,可以使用glibc提供的`backtrace`和`backtrace_symbols`函数来获取当前的调用栈。 ```c #include <execinfo.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> void print_stack_trace() { void *array[10]; size_t size; char **strings; size_t i; size = backtrace(array, 10); strings = backtrace_symbols(array, size); printf("Obtained %zd stack frames.\n", size); for (i = 0; i < size; i++) { printf("%s\n", strings[i]); } free(strings); } void func_c() { print_stack_trace(); } void func_b() { func_c(); } void func_a() { func_b(); } int main() { func_a(); return 0; } ``` **编译时需要加上`-rdynamic`选项**,以便在输出中看到函数名: ```bash gcc -rdynamic program.c -o program ``` **输出示例**: ``` Obtained 5 stack frames. ./program(print_stack_trace+0x33) [0x400a0d] ./program(func_c+0x9) [0x400b0d] ./program(func_b+0x9) [0x400b19] ./program(func_a+0x9) [0x400b25] ./program(main+0x9) [0x400b31] ``` **解释**: - `backtrace`函数获取当前调用栈的地址,并存储在`array`数组中。 - `backtrace_symbols`将地址转换为可读的字符串。 - 注意:`backtrace_symbols`返回的字符串数组需要手动释放。 ### 2. Windows 系统 (使用 Win32 API) 在Windows系统上,可以使用`CaptureStackBackTrace`函数来获取调用栈。 ```c #include <windows.h> #include <dbghelp.h> #include <stdio.h> #pragma comment(lib, "dbghelp.lib") void print_stack_trace() { void *stack[100]; unsigned short frames; SYMBOL_INFO *symbol; HANDLE process; process = GetCurrentProcess(); SymInitialize(process, NULL, TRUE); frames = CaptureStackBackTrace(0, 100, stack, NULL); symbol = (SYMBOL_INFO *)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1); symbol->MaxNameLen = 255; symbol->SizeOfStruct = sizeof(SYMBOL_INFO); printf("Obtained %d stack frames.\n", frames); for (unsigned short i = 0; i < frames; i++) { SymFromAddr(process, (DWORD64)(stack[i]), 0, symbol); printf("%d: %s - 0x%0llX\n", frames - i - 1, symbol->Name, symbol->Address); } free(symbol); } void func_c() { print_stack_trace(); } void func_b() { func_c(); } void func_a() { func_b(); } int main() { func_a(); return 0; } ``` **解释**: - `CaptureStackBackTrace`函数用于捕获调用栈。 - `SymInitialize`初始化符号处理器。 - `SymFromAddr`通过地址获取符号信息。 - 需要链接`dbghelp.lib`库。 ### 3. 跨平台方法 (使用 libunwind) libunwind 是一个跨平台的库,提供了获取调用栈的接口。 ```c #include <libunwind.h> #include <stdio.h> void print_stack_trace() { unw_cursor_t cursor; unw_context_t context; unw_getcontext(&context); unw_init_local(&cursor, &context); printf("Backtrace:\n"); while (unw_step(&cursor) > 0) { unw_word_t offset, pc; char sym[256]; unw_get_reg(&cursor, UNW_REG_IP, &pc); if (pc == 0) { break; } 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"); } } } void func_c() { print_stack_trace(); } void func_b() { func_c(); } void func_a() { func_b(); } int main() { func_a(); return 0; } ``` **编译**(需要安装libunwind): ```bash gcc program.c -lunwind -o program ``` ### 4. 信号处理中打印调用栈 在程序崩溃时(如遇到段错误),可以通过信号处理函数打印调用栈。 ```c #include <execinfo.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> void handler(int sig) { void *array[10]; size_t size; // 获取调用栈 size = backtrace(array, 10); // 打印调用栈到标准错误 fprintf(stderr, "Error: signal %d:\n", sig); backtrace_symbols_fd(array, size, STDERR_FILENO); exit(1); } void func_c() { // 触发段错误 int *p = NULL; *p = 0; } void func_b() { func_c(); } void func_a() { func_b(); } int main() { signal(SIGSEGV, handler); func_a(); return 0; } ``` **解释**: - 当程序收到`SIGSEGV`信号(段错误)时,会调用`handler`函数。 - `handler`函数中使用`backtrace_symbols_fd`将调用栈直接打印到标准错误。 ### 注意事项 1. **可移植性**:不同平台需要使用不同的方法。 2. **性能**:获取调用栈的操作可能比较耗时,不适合在频繁调用的函数中使用。 3. **调试信息**:为了获得有意义的函数名,编译时需要包含调试信息(如GCC的`-g`选项)以及`-rdynamic`选项(Linux下)。 4. **动态链接**:动态链接库中的函数可能无法正确显示函数名,需要确保符号表正确加载。 ### 总结 以上展示了在C语言中打印调用栈的几种方法,包括Linux、Windows以及跨平台的实现。在开发中,我们可以根据目标平台选择合适的方案。在调试程序时,打印调用栈是非常有用的手段,可以快速定位问题发生的位置。 §§相关问题§§: 1. 在C++中如何打印调用栈? 2. 如何将调用栈信息输出到日志文件? 3. 在嵌入式系统中如何实现调用栈跟踪? 4. 如何解析调用栈中的地址为函数名和行号?</think># C语言打印调用栈的详细实现 在C语言中打印调用栈(堆栈跟踪)是调试和错误处理的重要技术。不同平台提供了不同的方法来实现这一功能,下面我将详细介绍几种主要实现方式。 ## 1. Linux/Unix系统使用`backtrace`函数族 这是最常用的跨平台方法,适用于Linux和类Unix系统: ```c #include <execinfo.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define MAX_STACK_FRAMES 128 void print_stack_trace() { void *stack_traces[MAX_STACK_FRAMES]; int num_frames = backtrace(stack_traces, MAX_STACK_FRAMES); char **stack_symbols = backtrace_symbols(stack_traces, num_frames); if (stack_symbols == NULL) { perror("backtrace_symbols"); return; } // 打印调用栈 printf("Call stack (most recent call first):\n"); for (int i = 0; i < num_frames; i++) { printf("#%d %s\n", i, stack_symbols[i]); } free(stack_symbols); } // 示例函数调用 void function_c() { print_stack_trace(); } void function_b() { function_c(); } void function_a() { function_b(); } int main() { function_a(); return 0; } ``` **编译和运行**: ```bash gcc -rdynamic -o stack_trace stack_trace.c ./stack_trace ``` **代码解释**: 1. `backtrace()` 函数获取当前调用栈的地址列表 2. `backtrace_symbols()` 将地址转换为可读的字符串 3. `-rdynamic` 编译选项确保输出包含函数名 4. 输出格式:`#<帧号> <二进制文件名>(<函数名>+<偏移量>) [<地址>]` **输出示例**: ``` Call stack (most recent call first): #0 ./stack_trace(print_stack_trace+0x20) [0x55a5a3b152a0] #1 ./stack_trace(function_c+0x9) [0x55a5a3b15329] #2 ./stack_trace(function_b+0x9) [0x55a5a3b15339] #3 ./stack_trace(function_a+0x9) [0x55a5a3b15349] #4 ./stack_trace(main+0x15) [0x55a5a3b1536f] #5 /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0x7f8a1d4e00b3] #6 ./stack_trace(_start+0x2e) [0x55a5a3b151ee] ``` ## 2. Windows系统使用`CaptureStackBackTrace` 在Windows平台,可以使用DbgHelp库实现: ```c #include <windows.h> #include <dbghelp.h> #include <stdio.h> #pragma comment(lib, "dbghelp.lib") #define MAX_STACK_FRAMES 62 void print_stack_trace() { void *stack_traces[MAX_STACK_FRAMES]; USHORT num_frames = CaptureStackBackTrace(0, MAX_STACK_FRAMES, stack_traces, NULL); HANDLE process = GetCurrentProcess(); SymInitialize(process, NULL, TRUE); SYMBOL_INFO *symbol = (SYMBOL_INFO *)malloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char)); symbol->MaxNameLen = 255; symbol->SizeOfStruct = sizeof(SYMBOL_INFO); printf("Call stack (most recent call first):\n"); for (USHORT i = 0; i < num_frames; i++) { DWORD64 address = (DWORD64)(stack_traces[i]); SymFromAddr(process, address, 0, symbol); printf("#%d 0x%0llX: %s\n", i, address, symbol->Name); } free(symbol); SymCleanup(process); } // 示例函数调用(同Linux版本) ``` ## 3. 使用libunwind库(跨平台) 对于需要跨平台支持的场景,可以使用libunwind库: ```c #include <libunwind.h> #include <stdio.h> void print_stack_trace() { unw_cursor_t cursor; unw_context_t context; unw_getcontext(&context); unw_init_local(&cursor, &context); printf("Call stack (most recent call first):\n"); int frame_num = 0; while (unw_step(&cursor) > 0) { unw_word_t offset, pc; char sym[256]; unw_get_reg(&cursor, UNW_REG_IP, &pc); if (pc == 0) break; printf("#%d: 0x%lx", frame_num++, (long)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\n"); } } } // 示例函数调用(同Linux版本) ``` ## 4. 在信号处理函数中使用堆栈跟踪 当程序崩溃时自动打印调用栈: ```c #include <execinfo.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> void signal_handler(int sig) { void *stack_traces[128]; int num_frames = backtrace(stack_traces, 128); // 打印到标准错误 fprintf(stderr, "Error: signal %d\n", sig); backtrace_symbols_fd(stack_traces, num_frames, STDERR_FILENO); // 退出程序 exit(1); } int main() { // 注册信号处理函数 signal(SIGSEGV, signal_handler); // 段错误 signal(SIGABRT, signal_handler); // 异常中止 // 示例:人为制造崩溃 int *ptr = NULL; *ptr = 42; // 这将触发SIGSEGV return 0; } ``` ## 5. 高级技巧:获取行号信息 使用`addr2line`工具解析地址到源代码位置: ```c #include <execinfo.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> void print_stack_trace_with_lines() { void *stack_traces[128]; int num_frames = backtrace(stack_traces, 128); printf("Call stack with source locations:\n"); for (int i = 0; i < num_frames; i++) { char cmd[256]; // 使用addr2line解析地址 snprintf(cmd, sizeof(cmd), "addr2line -e /proc/%d/exe -f -C -i -p %p", getpid(), stack_traces[i]); printf("#%d: ", i); fflush(stdout); // 确保输出顺序正确 system(cmd); } } // 示例函数调用(同Linux版本) ``` **编译要求**: ```bash gcc -g -rdynamic -o stack_trace stack_trace.c ``` ## 注意事项 1. **调试符号**:编译时需要`-g`选项保留调试信息 2. **动态链接**:使用`-rdynamic`确保输出函数名 3. **安全性**:生产环境应限制堆栈深度 4. **性能**:堆栈跟踪操作较慢,避免高频调用 5. **可移植性**:不同平台实现差异较大
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值