inux kernel 里面打印堆栈的函数

本文介绍了Linux内核中用于打印当前CPU堆栈调用函数的方法。主要通过dump_stack()函数实现,该函数会调用show_trace显示堆栈信息。此外还介绍了如何使用print_symbol打印函数调用过程。

inux kernel 里面打印堆栈的函数


调用dump_stack()就会打印当前cpu的堆栈的调用函数了。参考内核源代码
arch/x86_64/kernel/traps.c


/*
* The architecture-independent dump_stack generator
*/
void dump_stack(void)
{
        unsigned long dummy;
        show_trace(&dummy);
}



        /*
         * When in-kernel, we also print out the stack and code at the
         * time of the fault..
         */
        if (in_kernel) {

                printk("Stack: ");
                show_stack(NULL, (unsigned long*)rsp);
还有一种是可以打出函数调用的过程:

print_symbol("caller is %s/n", (long)__builtin_return_address(0));
<think>我们需要回答的问题是:在Linux/Unix系统使用backtrace函数族是否需要修改编译选项? 回答:是的,通常需要特定的编译选项来确保函数名等信息能够正确显示。 详细解释: 在Linux/Unix系统中,使用backtrace函数族(backtrace()和backtrace_symbols())可以获取调用栈信息,但是默认情况下,编译后的程序可能不会包含足够的符号信息,导致输出的调用栈只有地址而没有函数名。为了正确显示函数名,我们需要在编译时添加一些选项。 主要需要两个编译选项: 1. `-rdynamic`(或`-Wl,-export-dynamic`):这个选项用于将所有的符号(而不仅仅是已使用的符号)添加到动态符号表中,这样backtrace_symbols才能解析出函数名。 2. `-g`:这个选项用于生成调试信息,虽然不是必须的,但是如果有调试信息,我们可以使用addr2line等工具将地址转换为源代码位置(文件名和行号)。 另外,注意backtrace函数族位于`libexecinfo`库中(在FreeBSD等系统上),但在GNU/Linux上,这个函数是glibc的一部分,所以通常不需要额外链接库。但在链接时,可能需要加上`-lexecinfo`(在非glibc系统上)。 示例代码和编译命令如下: 示例代码(stack_trace.c): ```c #include <execinfo.h> #include <stdio.h> #include <stdlib.h> void print_trace(void) { 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 dummy_function(void) { print_trace(); } int main(void) { dummy_function(); return 0; } ``` 编译命令(在Linux上): ```bash gcc -rdynamic -o stack_trace stack_trace.c ``` 如果不加`-rdynamic`,则输出的调用栈可能只有函数地址而没有函数名,或者函数名被省略。 例如,不加`-rdynamic`时,输出可能如下: ``` Obtained 5 stack frames. ./stack_trace(print_trace+0x1f) [0x4007c1] ./stack_trace(dummy_function+0x9) [0x400864] ./stack_trace(main+0x9) [0x400870] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f9a0a3e2830] ./stack_trace(_start+0x29) [0x4006a9] ``` 加上`-rdynamic`后,输出可能如下(可以看到函数名更清晰): ``` Obtained 5 stack frames. ./stack_trace(print_trace+0x1f) [0x4007c1] ./stack_trace(dummy_function+0x9) [0x400864] ./stack_trace(main+0x9) [0x400870] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f9a0a3e2830] ./stack_trace(_start+0x29) [0x4006a9] ``` 注意:即使加上`-rdynamic`,我们可能还是只能看到函数名,而看不到具体的行号。为了看到行号,我们可以使用`addr2line`工具,或者使用`backtrace_symbols_fd`配合`addr2line`,但这就需要程序在编译时加上`-g`选项。 例如,重新编译: ```bash gcc -rdynamic -g -o stack_trace stack_trace.c ``` 然后我们可以使用addr2line工具来将地址转换为文件名和行号,例如: ```bash addr2line -e stack_trace 0x4007c1 ``` 但是,在程序中直接打印出行号需要更复杂的处理(如解析backtrace_symbols的字符串,然后对每个地址调用外部工具或使用libdw等库)。 总结:使用backtrace函数族时,为了正确显示函数名,编译时需要加上`-rdynamic`选项;如果需要行号信息,还需要加上`-g`选项,并且可能需要额外的代码来解析地址。 §§相关问题§§: 1. 如何在C程序中获取调用栈并解析出文件名和行号? 2. 在C++中打印调用栈时,如何显示经过名称修饰(mangled)的函数名? 3. 在Linux中,除了backtrace函数族,还有哪些方法可以获取调用栈? 4. 如何在程序崩溃时自动打印调用栈?</think># Linux/Unix系统使用backtrace函数族的编译要求 是的,在Linux/Unix系统中使用`backtrace`函数族需要特定的编译选项才能获得完整的函数名信息。以下是详细解释和解决方案: ## 必需的编译选项 ### 1. `-rdynamic` 或 `-Wl,--export-dynamic` 选项 **作用**:将符号添加到动态符号表,使`backtrace_symbols()`能解析函数名 **重要性**:没有此选项时,只能看到地址偏移量,无法看到实际的函数名 ```bash # 编译命令 gcc -rdynamic -o my_program my_program.c ``` ### 2. `-g` 调试选项(推荐) **作用**:添加调试信息,支持获取行号信息 **重要性**:虽然不是严格必需,但能显著增强调试能力 ```bash # 完整编译命令(推荐) gcc -rdynamic -g -o my_program my_program.c ``` ## 编译选项对比测试 ### 测试代码 ```c #include <execinfo.h> #include <stdio.h> #include <stdlib.h> void func_c() { void *buffer[10]; int size = backtrace(buffer, 10); char **symbols = backtrace_symbols(buffer, size); printf("Call stack:\n"); for (int i = 0; i < size; i++) { printf("%s\n", symbols[i]); } free(symbols); } void func_b() { func_c(); } void func_a() { func_b(); } int main() { func_a(); return 0; } ``` ### 不同编译选项的结果对比 | 编译选项 | 输出示例 | 问题 | |----------|----------|------| | **无特殊选项**<br>`gcc -o test test.c` | `./test(func_c+0x1a) [0x55f5b5e5a1da]`<br>`./test(main+0x1e) [0x55f5b5e5a2de]` | 只有地址偏移,无函数名 | | **仅`-g`**<br>`gcc -g -o test test.c` | `./test(func_c+0x1a) [0x55f5b5e5a1da]`<br>`./test(main+0x1e) [0x55f5b5e5a2de]` | 有调试信息但无函数名 | | **仅`-rdynamic`**<br>`gcc -rdynamic -o test test.c` | `./test(func_c+0x1a) [0x55f5b5e5a1da]`<br>`./test(func_b+0x9) [0x55f5b5e5a259]`<br>`./test(func_a+0x9) [0x55f5b5e5a269]` | 有完整函数名 | | **完整选项**<br>`gcc -rdynamic -g -o test test.c` | `./test(func_c+0x1a) [0x55f5b5e5a1da]`<br>`./test(func_b+0x9) [0x55f5b5e5a259]`<br>`./test(func_a+0x9) [0x55f5b5e5a269]` | 有函数名和调试信息 | ## 高级配置:获取行号信息 要获取源代码文件名和行号,需要额外处理: ```c #include <execinfo.h> #include <stdio.h> #include <stdlib.h> #include <libgen.h> #include <string.h> #include <unistd.h> void print_stack_trace() { void *buffer[20]; int size = backtrace(buffer, 20); char **symbols = backtrace_symbols(buffer, size); printf("Call stack with source locations:\n"); char cmd[256]; char path[256]; // 获取当前程序路径 ssize_t len = readlink("/proc/self/exe", path, sizeof(path)-1); if (len != -1) { path[len] = '\0'; char *dir = dirname(path); for (int i = 0; i < size; i++) { // 提取地址部分 char *addr_start = strchr(symbols[i], '['); char *addr_end = strchr(symbols[i], ']'); if (!addr_start || !addr_end) continue; // 构造addr2line命令 *addr_end = '\0'; snprintf(cmd, sizeof(cmd), "addr2line -e %s -f -C -s -p %.*s", path, (int)(addr_end - addr_start - 1), addr_start + 1); printf("#%d: ", i); fflush(stdout); system(cmd); } } free(symbols); } ``` **使用要求**: 1. 必须使用`-g`选项编译 2. 系统需要安装`addr2line`工具 3. 程序路径不能包含特殊字符 ## Makefile 示例 ```makefile CC = gcc CFLAGS = -Wall -Wextra -rdynamic -g LDFLAGS = TARGET = stack_trace SRCS = stack_trace.c OBJS = $(SRCS:.c=.o) all: $(TARGET) $(TARGET): $(OBJS) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ clean: rm -f $(OBJS) $(TARGET) .PHONY: all clean ``` ## 常见问题解决 1. **函数名显示为`??`或`0x...`**: - 确保使用了`-rdynamic`编译选项 - 检查函数是否被优化掉(避免使用`-O2`或更高优化级别) - 静态函数不会出现在符号表中 2. **行号信息不正确**: - 确保使用`-g`编译 - 检查`addr2line`是否在PATH中 - 确认程序路径正确(`/proc/self/exe`符号链接) 3. **性能问题**: - 限制堆栈深度(不要获取过多帧) - 生产环境中考虑缓存符号信息 - 避免在性能关键路径中使用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值