4. 栈回溯与函数调用分析
任务目标
本案例通过一个包含三层函数调用的示例程序,演示程序崩溃时如何使用GDB的backtrace命令(简称bt)查看函数调用栈,定位崩溃发生的函数及完整调用路径。
示例程序
创建文件crash.c,内容如下。程序在level3()中通过空指针写入触发段错误:
#include <stdio.h>
void level3(int *p) {
*p = 42; // 空指针解引用,触发崩溃
}
void level2(int *p) {
level3(p);
}
void level1(int *p) {
level2(p);
}
int main() {
int *ptr = NULL;
level1(ptr);
return 0;
}
编译与运行
使用-g选项保留调试符号,然后启动GDB:
gcc -g -o crash crash.c
gdb ./crash
在GDB中直接运行程序,使其崩溃:
(gdb) run
程序崩溃后,GDB会暂停并提示收到SIGSEGV信号。此时执行:
(gdb) backtrace
也可使用简写bt。预期输出如下(地址可能因环境而异):
#0 level3 (p=0x0) at crash.c:4
#1 0x0000000000401157 in level2 (p=0x0) at crash.c:8
#2 0x0000000000401163 in level1 (p=0x0) at crash.c:12
#3 0x0000000000401173 in main () at crash.c:16
输出解读
- #0:当前崩溃帧,即
level3函数第4行,此处发生空指针写入。 - #1~#3:从内到外依次为调用者
level2、level1、main,每帧显示函数名、调用参数值及对应源码行号。 - 帧编号越小越靠近崩溃点,编号越大越靠近程序入口。
常用辅助命令
frame N(或f N):切换到第N帧,查看该函数局部变量。info locals:查看当前帧的局部变量值。list:查看当前帧对应的源码行。
验证方法
执行bt后,若输出包含level3、level2、level1、main四个帧,且#0帧指向crash.c第4行,则说明栈回溯成功,调用路径与源码一致。可进一步执行frame 3切换到main帧,用info locals确认ptr值为0x0,验证空指针来源。
常见问题
- 看不到函数名:编译时未加
-g,或程序被strip过。重新用-g编译即可。 - 输出只有
??:通常因优化选项(如-O2)导致帧信息丢失,调试时建议使用-O0。