3. 启动与退出GDB:gdb命令的多种启动方式、core文件调试、attach到运行进程、quit与exit
说实话,很多初学者觉得GDB启动就是敲个gdb ./a.out完事。但我在实际项目中踩过不少坑,发现启动方式选对了,调试效率能翻倍。今天咱们就把GDB的启动和退出聊透。
3.1 最基础的启动方式
先看最常用的几种启动命令:
# 方式1:直接调试可执行文件
gdb ./my_program
# 方式2:调试时带参数
gdb --args ./my_program arg1 arg2
# 方式3:先启动GDB,再加载文件
gdb
(gdb) file ./my_program
(gdb) run arg1 arg2
我个人习惯用方式2,因为参数写在命令行里一目了然。但要注意,--args后面的所有内容都会被当作参数传给程序,包括路径中的空格——我曾经因为这个特性,在调试一个带空格路径的程序时折腾了半小时。
gdb -x script.gdb的方式批量执行GDB命令。
3.2 Core文件调试——救命稻草
程序崩溃了,但现场没了?这时候core文件就是你的救命稻草。说白了,core文件就是程序挂掉那一刻的内存快照。
先确保系统允许生成core文件:
# 查看当前限制
ulimit -c
# 设置为无限制(临时生效)
ulimit -c unlimited
# 永久生效(写入bashrc)
echo "ulimit -c unlimited" >> ~/.bashrc
然后让程序崩溃一次,生成core文件:
# 运行程序,让它崩溃
./my_program
Segmentation fault (core dumped)
# 用GDB加载core文件
gdb ./my_program core
我曾经在一个线上服务崩溃后,靠core文件定位到了一个指针越界问题。当时程序跑了三天才挂,如果没有core文件,根本复现不了。用bt命令看调用栈,发现是某个函数里对数组的访问越界了——索引值比数组长度大了1。
3.3 Attach到运行中的进程
有时候程序已经跑起来了,你不能停掉它(比如线上服务)。这时候就需要attach方式:
# 先找到进程PID
ps aux | grep my_program
# 假设PID是12345
gdb -p 12345
# 或者先启动GDB再attach
gdb
(gdb) attach 12345
attach上去后,程序会暂停。你可以设置断点、查看变量、修改内存。完事后用detach让程序继续跑:
(gdb) detach
(gdb) quit
这里有个坑:如果你attach到一个多线程程序,GDB默认只暂停当前线程。其他线程还在跑!我曾经调试一个死锁问题,attach上去后发现所有线程都在等锁,但GDB只显示了当前线程的栈——差点误判。后来用info threads才看清全貌。
set follow-fork-mode child或set detach-on-fork off来控制子进程的行为。特别是调试fork出来的子进程时,这个设置能救命。
3.4 退出GDB的正确姿势
退出GDB很简单,但不同场景有不同讲究:
# 正常退出
(gdb) quit
# 或
(gdb) exit
# 强制退出(不询问)
(gdb) quit -y
# 如果调试的进程还在运行,GDB会问你是否要杀掉它
(gdb) quit
The program is running. Exit anyway? (y or n) y
我个人建议:如果只是临时退出,用quit就好。但如果调试的是线上服务,记得先detach再退出,否则GDB会帮你把进程杀了——别问我怎么知道的。
3.5 知识体系总览
下面这张图总结了GDB启动与退出的核心逻辑:
3.6 实战避坑指南
最后分享几个我踩过的坑:
- core文件太大?用
ulimit -c 102400限制大小,单位是KB。我一般设100MB,够用又不占磁盘。 - attach后程序卡死?检查是不是有SIGSTOP信号。用
signal SIGCONT让程序继续跑。 - 退出时GDB问要不要杀进程?如果你只是调试,选n。如果调试完要重启,选y。我习惯先
detach再quit,这样永远不会误杀。 - 多进程调试?用
set follow-fork-mode child让GDB跟踪子进程。或者用set detach-on-fork off同时调试父子进程。
gdb -p PID attach上去,然后set scheduler-locking on锁定线程调度,再慢慢看问题。完事后detach,服务继续跑,用户无感知。
嗯,启动和退出这部分就这些。记住一点:启动方式选对了,调试就成功了一半。Core文件调试和Attach调试是线上问题排查的两大利器,建议多练练。
可可精品资料库