内核中dump_stack()的实现,并在用户态模拟dump_stack()

内核中的dump_stack()

获得内核中当前进程的栈回溯信息需要用到的最重要的三个内容就是:

栈指针:sp寄存器,用来跟踪程序执行过程。

返回地址:ra寄存器,用来获取函数的返回地址。

程序计数器:epc,用于定位当前指令的位置。

本文的内容都是基于mips体系架构的,如果你不搞mips,就只看个大致流程就可以了,不然可能会被某些内容误导。在ARM中,这三个寄存器分别为SP、LR和PC寄存器。

dump_stack()用于回溯函数调用关系,他需要做的工作很简单:

1.   从进程栈中找到当前函数(callee)的返回地址。

2.   根据函数返回地址,从代码段中定位该地址位于哪个函数中,找到的函数即为caller函数。

3.   打印caller函数的函数名。

4.   重复前3个步骤。直到返回值为0或不在内核记录的符号表范围内。

在编译程序的时候,所有函数所需要的栈空间的大小都已经计算出来,如果函数需要保存返回地址,返回地址在该函数的栈空间中保存的位置也都计算出来了。所以,我们想得到返回地址,只需得到每个函数栈即可,而所有函数栈都放在进程的栈中,栈顶为sp。

返回地址是caller函数中将要执行的指令,是指向代码段的,这个更容易得到,因为代码段在编译时就确定了。

当前函数的位置通过pc的值可以得到。

例如,现在有func0调用func1,func1又调用func2,在func2执行过程中,进程栈空间大致如下:

在 **MIPS 架构** 的用户态(应用程序)中,没有直接等同于内核 `dump_stack()` 的函数,但可以通过 **glibc 的 backtrace 功能** 或 **信号处理** 实现类似效果。以下是几种方法: --- ### **1. 使用 `backtrace()`(glibc 函数)** **原理**: 通过 `execinfo.h` 提供的 `backtrace()` 和 `backtrace_symbols()` 获取当前调用。 **示例代码**: ```c #include <execinfo.h> #include <stdio.h> #include <stdlib.h> void dump_stack() { void *buffer[50]; // 存储帧地址 int frames = backtrace(buffer, 50); // 获取调用 char **symbols = backtrace_symbols(buffer, frames); // 解析为符号 if (symbols == NULL) { perror("backtrace_symbols"); return; } for (int i = 0; i < frames; i++) { printf("%s\n", symbols[i]); // 打印调用 } free(symbols); } int main() { printf("Dumping user space stack:\n"); dump_stack(); return 0; } ``` **编译时需添加 `-rdynamic` 选项**(确保函数名可解析): ```bash gcc -rdynamic dump_stack.c -o dump_stack ``` **输出示例**: ``` ./dump_stack(dump_stack+0x1c) [0x123456] ./dump_stack(main+0x24) [0x123abc] /lib/mips-linux-gnu/libc.so.6(__libc_start_main+0xe0) [0x789def] ``` --- ### **2. 使用 `libunwind`(跨平台方案)** **优势**:支持 MIPS/ARM/x86 等多种架构,解析更精准。 **安装**: ```bash sudo apt-get install libunwind-dev # Debian/Ubuntu ``` **示例代码**: ```c #include <libunwind.h> #include <stdio.h> void dump_stack() { unw_cursor_t cursor; unw_context_t context; unw_getcontext(&context); unw_init_local(&cursor, &context); while (unw_step(&cursor) > 0) { unw_word_t offset, pc; char sym[256]; unw_get_reg(&cursor, UNW_REG_IP, &pc); if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) { printf("0x%lx: %s + 0x%lx\n", pc, sym, offset); } else { printf("0x%lx: [unknown]\n", pc); } } } ``` --- ### **3. 通过信号处理捕获调用(如 `SIGSEGV`)** **用途**:在程序崩溃时自动打印堆。 **示例**: ```c #include <signal.h> #include <execinfo.h> void sig_handler(int sig) { dump_stack(); // 调用前面的 backtrace() 实现 exit(1); } int main() { signal(SIGSEGV, sig_handler); // 注册段错误信号 // 触发崩溃测试 int *p = NULL; *p = 1; // 人为触发 SIGSEGV return 0; } ``` --- ### **4. MIPS 汇编实现(底层手动解析)** 若需极端优化,可直接通过 MIPS 寄存器(如 `$ra` 返回地址)遍历帧,但需注意: - MIPS 的延迟槽(Delay Slot)特性可能影响地址解析。 - 需处理无帧指针(`$fp`)的情况。 --- ### **注意事项** 1. **动态链接库**:某些 MIPS 环境需确保 `-lexecinfo` 或 `-lunwind` 链接。 2. **嵌入式系统**:若使用裸机环境(无 glibc),需移植简化版 backtrace。 3. **符号表**:发布时需保留调试符号(或使用 `addr2line` 工具解析地址)。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值