2、GDB基本命令:run/start、continue、next、step、finish、until、list、print、whatis、ptype
调试器这东西,说白了就是给程序装个「慢放键」。GDB 作为 Linux 下最经典的调试工具,它的基本命令就像你的手和脚——用熟了,根本不用过脑子。今天我就带你把这些命令捋一遍,都是我这些年踩坑踩出来的经验。
核心思想:调试不是瞎跑,而是有节奏地控制程序执行,并在关键点观察状态。
2.1 启动与运行:run vs start
先说说怎么让程序跑起来。你编译好一个带 -g 选项的可执行文件,然后 gdb ./a.out 进去,这时候程序还没动呢。
run 就是直接开跑。不带断点的话,程序一口气执行完。我个人习惯是先用 break main 设个断点,再用 run,这样能停在入口处慢慢看。
start 呢?它相当于 break main 加 run 的合体。你输入 start,GDB 会自动在 main 函数入口停住。嗯,这个命令我特别喜欢在快速验证时用,省得敲两行。
(gdb) start
Temporary breakpoint 1 at 0x4004d6: file test.c, line 5.
Starting program: /home/user/a.out
Temporary breakpoint 1, main () at test.c:5
5 int a = 10;
小技巧:如果程序需要命令行参数,用 run arg1 arg2 或者 set args arg1 arg2 提前设好。start 也支持传参,比如 start arg1。
2.2 继续执行:continue
程序停在断点上了,你想让它继续跑,直到下一个断点或结束。这时候用 continue(简写 c)。
我曾经在调试一个网络服务时,断点设在数据接收函数里。每次 continue 后,程序处理完一个包又停回来。配合 print 看变量变化,特别直观。
(gdb) c
Continuing.
Breakpoint 1, handle_packet (pkt=0x7fffffffdb10) at server.c:42
42 process(pkt);
2.3 单步执行:next vs step
这两个命令是调试的「显微镜」。区别就一句话:next 跳过函数,step 钻进函数。
- next (n):执行当前行,如果遇到函数调用,把它当成一个整体执行完,不进去。
- step (s):执行当前行,如果遇到函数调用,就跳进函数内部。
举个例子,假设当前停在 result = calc(a, b); 这一行:
(gdb) n // 直接执行完 calc,停在下一行
(gdb) s // 跳进 calc 函数内部
我个人习惯是:不确定函数有没有 bug 时用 step,确定没问题就用 next 跳过。你想想看,每次 step 进标准库函数里,那代码量够你看半天的。
注意:step 遇到没有调试信息的函数(比如系统库),会直接执行完。这时候可以用 stepi 按指令级别单步,但那是另一层话题了。
2.4 跳出与跑到指定位置:finish 和 until
finish 用来跳出当前函数。你 step 进一个函数,看了一半发现没问题,想直接回到调用处?finish 搞定。它会执行完当前函数,然后停在返回后的那一行。
(gdb) finish
Run till exit from #0 calc (a=10, b=20) at test.c:12
0x00000000004004f6 in main () at test.c:8
8 result = calc(a, b);
until 呢?它用来跑到指定行号。比如你在一个循环里,想直接跑到第 20 行:
(gdb) until 20
我记得有一次调试一个死循环,用 until 配合行号,快速跳过了前面几百次迭代,直接定位到出问题的那个分支。嗯,这命令在循环调试里特别好用。
2.5 查看源码:list
list(简写 l)用来显示源码。默认显示当前执行位置附近的 10 行。你可以指定行号、函数名,甚至文件加行号。
(gdb) list
5 int a = 10;
6 int b = 20;
7 int result;
8 result = calc(a, b);
9 return 0;
10 }
(gdb) list calc // 显示 calc 函数的源码
(gdb) list test.c:1 // 显示 test.c 第 1 行开始
我个人习惯是:调试时保持源码窗口打开。如果你用 TUI 模式(gdb -tui),list 命令基本用不上,因为源码一直显示着。但纯命令行下,list 就是你的眼睛。
2.6 查看数据:print、whatis、ptype
调试的核心就是看数据。这三个命令帮你从不同角度观察变量。
print (p):打印变量的值。支持表达式,比如 p a+b、p &a、p *ptr。
(gdb) p a
$1 = 10
(gdb) p &a
$2 = (int *) 0x7fffffffdabc
(gdb) p a + b
$3 = 30
whatis:告诉你变量的类型。简单直接,适合快速确认。
(gdb) whatis a
type = int
(gdb) whatis ptr
type = int *
ptype:比 whatis 更详细。对于结构体、类等复杂类型,它会展开所有成员。
(gdb) ptype student
type = struct student {
char name[32];
int age;
float score;
}
我曾经在调试一个内核模块时,遇到一个嵌套了 5 层的结构体。用 ptype 一层层看,比翻头文件快多了。说白了,ptype 就是你的「类型浏览器」。
2.7 知识体系总览
下面这张图帮你理清这些命令的关系:
2.8 实战组合拳
光记命令没用,得会用。我给你一个典型的调试流程:
gdb ./program进入调试break main在 main 设断点run启动,停在 mainlist看看附近代码next一步步走,遇到可疑函数用step进去- 在函数里用
print看变量,ptype看结构 - 确认没问题用
finish跳出 - 想跳过循环用
until 行号 - 继续用
continue跑到下一个断点
我的习惯:调试时右手放在小键盘区,左手放在方向键附近。next、step、continue 这些命令我都用简写:n、s、c。敲得快,思路不容易断。
这些命令看着简单,但组合起来威力很大。你想想看,一个程序从上亿条指令中跑出 bug,你能用这些命令精确控制每一步,这就是调试的乐趣所在。
嗯,今天就先聊到这儿。这些命令你先练熟,后面章节我们会用它们解决更复杂的问题。