3、GDB启动与退出:启动GDB的多种方式、退出GDB、GDB的配置文件.gdbinit
调试器怎么启动?这问题听起来简单,但实际工作中我见过不少新手在这上面栽跟头。GDB的启动方式其实有好几种,每种都有它的适用场景。今天我就把这些年积累的经验都抖出来,咱们一起捋一捋。
3.1 启动GDB的多种方式
启动GDB,说白了就是告诉它「嘿,我要调试这个程序」。但具体怎么告诉它,有讲究。
3.1.1 直接启动:gdb 程序名
这是最常用的方式。你编译好了一个程序,比如叫 myapp,直接在终端敲:
gdb myapp
GDB就会加载这个程序,然后停在提示符等你下命令。我个人习惯用这种方式,简单直接。
-g 选项,不然调试信息不全,你看到的全是汇编代码,那滋味可不好受。
3.1.2 带参数启动:gdb --args 程序名 参数1 参数2
如果你的程序需要命令行参数,比如 ./myapp -v --output result.txt,怎么办?
你可能会想:gdb ./myapp -v --output result.txt。错!这样GDB会把 -v 当成自己的参数,程序根本拿不到。
正确做法是:
gdb --args ./myapp -v --output result.txt
或者先启动GDB,再用 set args 命令设置参数:
(gdb) set args -v --output result.txt
(gdb) run
嗯,这里要注意:--args 后面的所有内容都会被当作程序的参数,包括程序名本身。我曾经在这上面吃过亏,调试一个网络服务时传错了参数,排查了半天才发现是启动方式不对。
3.1.3 附加到正在运行的进程:gdb -p PID
这是我最常用的方式之一。程序已经在跑了,突然出问题了,你不能把它停下来重新启动吧?
这时候用 gdb -p 12345(12345是进程ID),GDB就会attach上去,你可以查看当前状态、设置断点、继续运行。
gdb -p 12345
或者先启动GDB,再attach:
(gdb) attach 12345
3.1.4 调试core dump文件:gdb 程序名 core文件
程序崩溃了,生成了一个core dump文件。这时候怎么查?
gdb ./myapp core.12345
GDB会加载core文件,直接定位到程序崩溃的位置。你可以查看当时的堆栈、变量值,就像程序暂停在那里一样。
为什么要有core文件?因为程序崩溃后,现场就没了。core文件相当于一个「快照」,把程序崩溃时的内存状态都保存了下来。我调试过的最棘手的一个bug,就是靠core文件找到的——一个野指针在特定条件下才触发,复现了三天才抓到一次core。
3.1.5 其他启动方式
| 启动方式 | 命令 | 适用场景 |
|---|---|---|
| 调试子进程 | gdb --follow-fork-mode child 程序名 |
程序fork出子进程,你想调试子进程 |
| 远程调试 | gdb 程序名 然后 target remote 192.168.1.100:1234 |
程序运行在嵌入式设备或另一台机器上 |
| 调试内核模块 | gdb vmlinux /proc/kcore |
内核调试(需要root权限) |
3.2 退出GDB
退出GDB,你可能会说:这还用教?quit 不就完了?
对,但也不全对。GDB退出时有一些细节需要注意。
3.2.1 正常退出
(gdb) quit
# 或者简写
(gdb) q
如果被调试的程序还在运行,GDB会问你是否要终止它:
(gdb) quit
The program is running. Exit anyway? (y or n) y
选 y,程序会被杀掉。选 n,GDB继续调试。
3.2.2 强制退出
有时候GDB卡住了,比如网络远程调试时连接断了,quit 没反应。这时候可以:
Ctrl + d
或者直接关掉终端窗口。不过我不建议这么做,因为可能会留下一些后台进程。
detach,让进程恢复运行。不然进程会被杀掉。
3.3 GDB的配置文件 .gdbinit
每次启动GDB都要设置一堆东西?比如设置断点、加载符号、配置显示风格?太麻烦了。
.gdbinit 就是干这个的。GDB启动时会自动读取这个文件,执行里面的命令。
3.3.1 全局 .gdbinit
放在你的home目录下:~/.gdbinit
所有GDB会话都会加载它。我一般在这里放一些通用配置:
# 设置显示风格
set print pretty on
set print elements 0
set print array-indexes on
# 设置历史记录
set history save on
set history size 1000
# 设置汇编风格为Intel
set disassembly-flavor intel
# 设置断点提示
set breakpoint pending on
3.3.2 项目级 .gdbinit
放在项目目录下:./.gdbinit
GDB默认不会加载当前目录的 .gdbinit,因为安全原因。你需要:
# 在 ~/.gdbinit 中添加
set auto-load safe-path /
或者启动时指定:
gdb -x ./.gdbinit ./myapp
项目级的 .gdbinit 适合放一些项目特定的配置:
# 设置源码路径
directory /home/user/project/src
# 设置断点
break main
break file_io.c:100
# 设置环境变量
set env LD_LIBRARY_PATH ./lib
# 运行参数
set args --config config.json
run 命令。结果每次启动GDB,程序就自动跑起来了,根本来不及设断点。所以 .gdbinit 里尽量不要放 run 或 continue 这类会改变程序执行流程的命令。
3.3.3 禁用 .gdbinit
有时候你不想加载任何配置文件,比如调试别人的项目时,怕被坑。可以用:
gdb -nx ./myapp
-nx 表示不加载任何 .gdbinit 文件。干干净净,从头开始。
3.4 知识体系图
下面这张图把GDB启动与退出的核心知识点串起来了,你可以对照着看:
这张图把GDB启动与退出的核心脉络画出来了。左边是启动方式,右边是退出方式,中间是配置文件。你想想看,掌握了这三块,GDB的「门」你就摸透了。
我个人建议,把常用的配置放到 ~/.gdbinit 里,项目特定的放到项目目录的 .gdbinit 里。这样每次启动GDB,环境就已经准备好了,省心省力。
~/.gdbinit 里加载了一个巨大的符号文件。从那以后,我养成了一个习惯:.gdbinit 里只放最必要的配置,其他的按需加载。
好了,GDB的启动与退出就聊到这儿。记住一句话:启动方式选对了,调试就成功了一半。配置文件用好了,调试效率翻倍。