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。