3、启动调试:gdb命令启动、附加到进程、core dump调试

调试器启动的方式,说白了就三种:直接跑程序、挂到正在跑的进程上、或者事后分析崩溃现场。这三种场景我几乎每天都在切换,今天就把它们掰开揉碎了讲清楚。

3.1 直接启动:gdb ./program

这是最基础的方式。你编译好一个程序,想看看它内部怎么跑的,直接敲:

gdb ./my_program

然后你就进了 GDB 的交互界面。这时候程序还没跑,你可以先下断点,再输入 run 让它跑起来。

带参数启动怎么办?

很多程序需要命令行参数。比如你的程序要读一个配置文件:

gdb --args ./my_program -c config.ini -v

我个人习惯用 --args,因为它把后面的所有东西都当作参数传给程序,省得我手动去设 set args。当然你也可以进 GDB 后敲:

(gdb) set args -c config.ini -v
(gdb) run

效果一样,看个人喜好。

小技巧: 如果你程序需要标准输入,比如从键盘读数据,直接 run < input.txt 就能重定向输入。我在调试一个网络协议解析器时经常这么干,把抓好的报文喂给程序。

3.2 附加到进程:gdb attach

有时候程序已经跑起来了,突然卡住或者表现异常。这时候你不能重启它——生产环境哪能随便重启?那就用 attach 方式。

先找到进程 ID:

ps aux | grep my_program
# 假设 PID 是 12345

然后:

gdb attach 12345

GDB 会暂停这个进程,你就可以下断点、查看变量、修改变量了。完事之后用 detach 让程序继续跑。

注意: 附加到进程需要你有对应权限。一般要求你是进程的 owner 或者 root。我曾经在调试一个系统守护进程时忘了加 sudo,结果 GDB 报 "Operation not permitted",折腾了半天才发现是权限问题。

什么时候用 attach?

  • 程序已经跑起来,出现了偶发性的死锁或卡顿
  • 你想在不中断服务的情况下查看内部状态
  • 程序启动过程太复杂,不想从头跑一遍

嗯,这里要注意:attach 之后程序是暂停的。如果你 attach 的是一个对外提供服务的进程,暂停期间客户端会超时。所以动作要快,查完赶紧 detach。

3.3 Core Dump 调试:事后诸葛亮

程序崩溃了,现场没了,怎么办?Core dump 就是你的救命稻草。

Core dump 是程序崩溃时操作系统把内存映像保存下来的文件。你可以用 GDB 加载它,像调试活进程一样查看崩溃时的变量、堆栈、寄存器。

先确保能生成 core 文件:

ulimit -c unlimited

这行命令设置 core 文件大小不受限制。默认很多系统是 0,也就是不生成 core 文件。我建议你把这个加到 .bashrc 里,省得每次都要手动设。

调试 core dump:

gdb ./my_program core

GDB 会加载程序符号表和 core 文件,然后停在崩溃的位置。你直接敲 bt 看堆栈:

(gdb) bt
#0  0x00007f... in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1  0x00007f... in __GI_abort () at abort.c:79
#2  0x00007f... in __assert_fail_base ...
#3  0x000055... in main () at test.c:10

看到 #3 了吗?那就是你代码里崩溃的地方。用 frame 3 切过去,然后 print variable_name 看变量值。

核心思路: Core dump 调试的关键是「还原现场」。你不需要重现 bug,只需要分析 crash 那一瞬间的内存快照。我在嵌入式项目中遇到过一种情况:程序跑几个小时才崩一次,用 core dump 分析,发现是一个全局数组越界写,把函数返回地址覆盖了。这种问题靠打日志根本查不出来。

core 文件在哪?

默认在当前工作目录,文件名就是 core 或者 core.PID。你可以通过 /proc/sys/kernel/core_pattern 查看系统配置。有些系统会把 core 文件丢到 /var/crash 或者 /var/lib/systemd/coredump 里。

启动方式 适用场景 优点 缺点
gdb ./program 从头调试,可控性强 可以设置断点、单步执行 需要重启程序
gdb attach PID 调试正在运行的进程 不中断服务(短暂暂停) 需要权限,暂停期间服务不可用
gdb ./prog core 分析崩溃现场 不需要重现 bug,直接分析 只能看,不能单步执行

3.4 三种方式的切换与组合

你想想看,这三种方式其实可以灵活组合。比如:

  • 先用 core dump 分析崩溃位置,找到可疑变量
  • 然后用 gdb ./program 重新跑,在可疑位置下断点
  • 如果程序已经跑起来了,就用 attach 方式挂上去

我在调试一个多线程服务器时就这么干过。程序跑着跑着就 segfault,但 core dump 里堆栈被破坏了(因为栈被踩了)。我只好用 attach 方式挂到进程上,在关键函数入口下断点,然后一步步跟踪变量变化,最后发现是一个线程在释放内存后还在使用指针。

我的习惯: 开发阶段多用 gdb ./program,配合断点和单步。测试阶段遇到崩溃,第一时间看 core dump。线上问题用 attach,但动作要快,查完就 detach。

3.5 知识体系总览

下面这张图把三种启动方式的关系和适用场景串起来了:

GDB 启动调试三种方式 GDB 启动调试 gdb ./program 从头调试,可控性强 gdb attach PID 调试运行中的进程 gdb ./prog core 事后分析崩溃现场 设置断点、单步执行 短暂暂停,查完 detach 查看堆栈、变量、寄存器 开发用直接启动,测试看 core dump,线上用 attach 三种方式灵活组合,覆盖所有调试场景

说白了,这三种启动方式覆盖了调试的三大场景:开发阶段、运行阶段、崩溃之后。你只要掌握它们,遇到任何问题都能找到合适的切入点。

可可精品资料库