You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

能否让内核解析用户态崩溃栈跟踪中的地址符号?

能否让内核在SIGSEGV栈跟踪中直接显示函数名/行号?

嘿,你问的这个问题很常见——内核本身是不会帮你把用户态地址解析成函数名和行号的,原因其实很简单:内核的职责是管理系统资源,它不会主动去读取用户程序里的调试符号(比如DWARF信息、符号表),一来是安全考虑(内核没必要碰用户空间的调试数据),二来是解析符号会带来额外的性能开销,不符合内核的设计理念。

不过别担心,我们有好几种办法可以事后或者实时拿到带函数名和行号的栈跟踪,结合你的例子给你详细讲讲:

方法1:用addr2line手动解析地址

这是最直接的方式,只要你编译程序时加了-g参数保留了调试信息(你已经这么做了),addr2line就能从二进制文件里把地址转换成函数名和行号。

比如你的内核日志里给出的PC地址是0x557e8a395c,直接运行:

addr2line -e /tmp/prog 0x557e8a395c

结合你的代码,这个地址应该对应到main函数里*(char *)s = 'H';这一行——毕竟你试图修改只读的字符串字面量,这就是触发段错误的原因。

如果遇到ASLR(地址空间随机化)导致加载地址变化的情况,你可以用内核输出的内存映射信息计算偏移:

557e8a3000-557e8a4000 r-xp 00000000 00:13 267223 /tmp/prog

这里代码段的加载基址是0x557e8a3000,用实际地址减去基址得到编译时的偏移:0x557e8a395c - 0x557e8a3000 = 0x95c,再用这个偏移解析:

addr2line -e /tmp/prog 0x95c

方法2:用gdb加载core文件自动生成带符号的栈跟踪

如果你的系统开启了core dump功能(可以先运行ulimit -c unlimited开启),程序崩溃时会生成一个core文件,用gdb加载这个文件就能直接看到完整的、带函数名和行号的栈回溯:

gdb /tmp/prog core

进入gdb后输入bt命令,你会看到类似这样的输出:

#0  0x000000557e8a395c in main () at prog.c:3

直接就定位到了出错的代码行,非常直观。

方法3:让程序崩溃时自动输出带符号的栈跟踪

如果你希望程序崩溃时自己就能输出带符号的信息,不用事后工具,可以自己实现一个SIGSEGV信号处理函数,借助backtrace()backtrace_symbols()函数(编译时需要加-rdynamic参数):

修改你的代码如下:

#include <stdio.h>
#include <signal.h>
#include <execinfo.h>
#include <stdlib.h>

void sigsegv_handler(int sig) {
    void *stack_frames[20];
    int frame_count = backtrace(stack_frames, 20);
    char **symbols = backtrace_symbols(stack_frames, frame_count);
    
    printf("Segmentation fault! Stack trace:\n");
    for (int i = 0; i < frame_count; i++) {
        printf("%s\n", symbols[i]);
    }
    
    free(symbols);
    exit(1);
}

int main() {
    signal(SIGSEGV, sigsegv_handler);
    const char *s = "hello world";
    *(char *)s = 'H';
    return 0;
}

gcc -g -rdynamic prog.c -o prog编译后,程序崩溃时会输出:

Segmentation fault! Stack trace:
./prog(sigsegv_handler+0x3c) [0x557e8a39f4]
/lib/libc-2.26.so(+0x37b6c) [0x7f8ae97b6c]
./prog(main+0x2c) [0x557e8a395c]
/lib/libc-2.26.so(__libc_start_main+0xe4) [0x7f8ae7b1a90]
./prog(_start+0x24) [0x557e8a3840]

虽然这里只能显示函数名和偏移,要拿到行号还是得结合addr2line,但已经比纯地址友好太多了。

总的来说,内核输出的栈跟踪已经给了你足够的信息(地址、内存映射),只要你保留了带调试信息的二进制文件,就可以通过上面的工具轻松还原出函数名和行号。

内容的提问来源于stack exchange,提问作者yy7k

火山引擎 最新活动