调试技巧:GDB基础命令、断点与观察点、core dump分析、AddressSanitizer

调试这事儿,说白了就是跟程序「对质」。你写代码时觉得逻辑天衣无缝,跑起来却翻车——这种事我见得太多了。刚入行那会儿,我习惯到处加 printf 打日志,后来发现这招在复杂场景下根本不够用。真正靠谱的,还是得靠专业工具。

今天咱们聊四个硬核调试手段:GDB 基础操作、断点与观察点、core dump 分析,还有 AddressSanitizer。每个都是我实际项目中反复用过的,希望能帮你少走弯路。

GDB 基础命令:跟程序面对面

GDB 是 Linux 下最经典的调试器。你想想看,程序跑着跑着崩了,你总得知道它死在哪一行、变量值是什么吧?GDB 就是干这个的。

先说说怎么启动。编译时记得加 -g 选项,保留调试符号:

gcc -g -o myapp myapp.c
gdb ./myapp

进去之后,最常用的几个命令我列一下:

命令作用我常用的场景
run / r启动程序刚设好断点,跑一把看看
break / b设置断点怀疑某行有问题,停住检查
next / n单步执行(不进入函数)一步步走,看流程对不对
step / s单步执行(进入函数)想看看函数内部干了什么
print / p打印变量值检查变量是不是预期值
backtrace / bt查看调用栈程序崩了,看谁调了谁
continue / c继续执行当前断点看完了,放行
quit / q退出 GDB收工

我个人习惯,一进 GDB 先设几个关键断点,然后 r 跑起来。如果程序崩了,第一时间打 bt 看调用栈——这能帮你快速定位到崩溃现场。

小技巧:list 命令可以查看当前行附近的源码。配合 break 行号 设断点,效率很高。

断点与观察点:精准卡位

断点不光是停在某一行。你还可以设条件断点,比如:

(gdb) break 42 if i > 100

这样只有当 i 大于 100 时才会停下来。我在项目中遇到过循环跑几万次的情况,不加条件根本没法调试。

观察点(watchpoint)就更厉害了。它能监控某个变量,一旦值发生变化就停下来:

(gdb) watch my_var

嗯,这里要注意:观察点对性能有影响,别在超大循环里乱用。我曾经在一个嵌入式项目里用 watch 监控全局变量,结果程序跑得比蜗牛还慢——后来改成条件断点才搞定。

还有硬件断点和软件断点的区别。简单说,硬件断点数量有限(一般 4 个),但不会影响代码执行速度。软件断点无限,但会修改指令。日常调试用软件断点就够了。

Core Dump 分析:事后诸葛亮也得当

程序崩了没来得及调试?别急,core dump 就是你的「事后现场照片」。它记录了程序崩溃那一刻的内存快照。

首先确保系统允许生成 core 文件:

ulimit -c unlimited

然后运行程序,崩溃后就会生成 core 文件。用 GDB 加载它:

gdb ./myapp core

进去之后,直接打 bt 看调用栈。我一般会接着打 frame N 切换到具体栈帧,然后用 p 打印局部变量。这样基本能还原出事发经过。

注意: core 文件可能很大,生产环境记得限制大小。另外,编译时一定要加 -g,否则 core 文件里只有地址没有符号信息,分析起来很痛苦。

我曾经接手过一个线上崩溃的 bug,用户反馈说「偶尔崩一次」。本地死活复现不了,最后靠 core dump 分析才发现是某个指针在特定条件下没初始化。嗯,从那以后我写代码都会刻意检查指针初始化。

AddressSanitizer:内存错误的照妖镜

说到内存错误——越界、野指针、释放后使用——这些是 C 程序员的噩梦。AddressSanitizer(简称 ASan)就是专门抓这些问题的。

用法很简单,编译时加两个选项:

gcc -fsanitize=address -g -o myapp myapp.c

然后正常跑程序。如果触发了内存错误,ASan 会直接打印出详细的错误信息,包括:

  • 错误类型(越界、释放后使用等)
  • 发生位置(文件名、行号)
  • 分配和释放的调用栈

举个例子,你写了个越界:

int arr[10];
arr[10] = 42;  // 越界!

ASan 跑起来会输出类似这样的信息:

==12345==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffc...
WRITE of size 4 at 0x7ffc... thread T0
    #0 0x... in main /home/user/myapp.c:5
    #1 0x... in __libc_start_main ...
    #2 0x... in _start ...

你看,它直接告诉你第 5 行有问题。省去了你一行行排查的时间。

建议: 开发阶段尽量开启 ASan。虽然会让程序变慢(大约 2 倍),但能提前发现大量内存问题。我团队现在 CI 流程里就强制跑 ASan,上线后内存相关的 bug 几乎绝迹了。

知识体系总览

下面这张图帮你理清这四种调试手段的关系:

C 语言调试工具箱 GDB 基础命令 • run / break / next / step • print / backtrace / continue • 编译加 -g,启动 gdb ./app 断点与观察点 • 条件断点:break 42 if i>100 • 观察点:watch my_var • 硬件断点有限,软件断点无限 Core Dump 分析 • ulimit -c unlimited 开启 • gdb ./app core 加载 • bt 看调用栈,frame 切栈帧 AddressSanitizer • 编译加 -fsanitize=address • 自动检测越界/释放后使用 • 开发阶段强烈建议开启 四种手段互补:GDB 日常调试,断点精准卡位,core 事后分析,ASan 预防内存问题

说白了,这四种手段各有侧重。GDB 是日常主力,断点和观察点是精准武器,core dump 是事后补救,ASan 是预防性检查。我个人的工作流是:开发时开 ASan,遇到问题用 GDB 加断点,线上崩溃靠 core dump 复盘。这套组合拳打下来,绝大多数 bug 都无所遁形。

调试不是丢人的事。恰恰相反,能把调试工具用得溜,说明你对程序的掌控力强。别怕花时间学这些工具——它们会在你最需要的时候拉你一把。