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 让程序继续跑。
什么时候用 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 文件在哪?
默认在当前工作目录,文件名就是 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 知识体系总览
下面这张图把三种启动方式的关系和适用场景串起来了:
说白了,这三种启动方式覆盖了调试的三大场景:开发阶段、运行阶段、崩溃之后。你只要掌握它们,遇到任何问题都能找到合适的切入点。