2. GDB基础回顾:启动退出、断点管理、单步调试

调试嵌入式程序,说白了就是跟代码的「灵魂」对话。而GDB,就是那根电话线。

我刚开始做NuttX开发那会儿,最头疼的就是程序跑飞了不知道在哪挂的。后来被老同事按着头学GDB,才发现——原来调试可以这么爽。今天咱们就把GDB最核心的三个基本功捋一遍:怎么启动、怎么设断点、怎么一步步看代码跑。

核心要点:GDB调试三板斧——启动(让程序停下来)、断点(告诉程序在哪停)、单步(控制程序怎么走)。这三招练熟了,90%的bug都能手到擒来。

GDB调试三板斧 启动与退出 gdb → file → run / quit 断点管理 break / info / delete / enable 单步调试 step / next / finish / continue gdb 可执行文件 gdb → file 命令 行号断点 函数断点 条件断点 step(进入函数) next(跳过函数) 💡 核心心法:先停住 → 再观察 → 最后分析

2.1 启动与退出:让调试器先跑起来

启动GDB其实就两种主流方式。我个人习惯用第一种,因为省事。

方式一:直接加载可执行文件

# 最常用的启动方式
$ gdb nuttx

# 然后你会看到类似这样的输出
GNU gdb (GDB) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
...
(gdb) _

方式二:先启动GDB,再加载文件

$ gdb
(gdb) file nuttx
Reading symbols from nuttx...
(gdb) _

小技巧:如果你用的是NuttX,编译时记得加 -g 选项。不然你看到的全是汇编,那画面太美我不敢看。我在项目中就吃过这个亏——调试了半天发现符号表没加载,白忙活两小时。

退出GDB

(gdb) quit
# 或者简写
(gdb) q

注意:如果程序还在运行,GDB会问你是否要终止。输入 y 确认就行。别慌,这是正常流程。

2.2 断点管理:让程序在关键位置「刹车」

断点就是调试的「刹车踏板」。你想想看,没有刹车你怎么控制车速?

设置断点

# 在行号设断点
(gdb) break 123
Breakpoint 1 at 0x1234: file main.c, line 123.

# 在函数入口设断点
(gdb) break os_start
Breakpoint 2 at 0x5678: file sched/init.c, line 45.

# 条件断点(我最爱的功能)
(gdb) break 123 if count > 5
Breakpoint 3 at 0x1234: file main.c, line 123.

查看和管理断点

# 列出所有断点
(gdb) info breakpoints
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x00001234 in main at main.c:123
2       breakpoint     keep y   0x00005678 in os_start at sched/init.c:45
3       breakpoint     keep y   0x00001234 in main at main.c:123
        stop only if count > 5

# 删除断点
(gdb) delete 1
(gdb) delete    # 删除所有断点(GDB会确认)

# 启用/禁用断点
(gdb) disable 2
(gdb) enable 2

避坑指南:我曾经在调试NuttX的任务调度时,设了断点在 sched_yield 函数里。结果程序一跑就停,根本进不了正常流程。后来才发现——这个函数被调用的频率太高了!条件断点这时候就派上用场了,加上 if pid == 3 瞬间清爽。

2.3 单步调试:一步一步看代码怎么跑

单步调试就像电影的「逐帧播放」。bug往往藏在细节里,你得一帧一帧地看。

核心命令对比

命令 简写 作用 我什么时候用
step s 进入函数内部 怀疑函数实现有bug时
next n 跳过函数,执行下一行 确认函数没问题时
finish fin 执行到当前函数返回 不小心step进了不想看的函数
continue c 继续运行到下一个断点 想快速跳过一段代码

实战演示

# 假设我们在 main.c 第100行停住了
(gdb) list
95    int main(void)
96    {
97        int ret;
98        ret = init_driver();    // 当前停在这里
99        if (ret < 0) {
100           printf("init failed\n");
101           return -1;
102       }
103       start_application();
104       return 0;
105   }

# 用 next 跳过 init_driver
(gdb) next
99        if (ret < 0) {

# 用 step 进入 start_application
(gdb) step
start_application () at app.c:23
23        int fd = open("/dev/ttyS0", O_RDWR);

我的习惯:调试NuttX驱动时,我一般先用 next 快速过一遍主流程。等发现某个函数返回值不对劲,再用 step 钻进去看。说白了就是「先宏观,再微观」——别一上来就step进每个函数,那会累死你。

2.4 调试中的实用小技巧

嗯,这里再分享几个我常用的「骚操作」:

  • 查看变量print var_name 或简写 p var_name。想看结构体?p *p_struct
  • 查看寄存器info registers。调试NuttX任务切换时,这个命令救过我很多次
  • 查看调用栈backtracebt。程序崩溃时第一件事就是打这个
  • 查看当前代码listl。默认显示当前行附近10行

重要提醒:在嵌入式调试中,print 大结构体可能会影响实时性。我遇到过在中断处理函数里 print 一个超大数组,结果导致看门狗超时复位。所以——调试中断服务程序时,尽量用条件断点 + 小变量打印。

好了,GDB的基础操作就这些。说白了就是三板斧:启动、断点、单步。别觉得简单,我见过太多人连 stepnext 的区别都搞不清就开始调bug,结果越调越乱。

下一节咱们会深入NuttX特有的调试场景——比如怎么调试任务切换、怎么分析中断上下文。但前提是,你得先把今天这三板斧练熟。


公众号:蓝海资料掘金营,微信deep3321