第一章:GDB入门——从零开始认识调试利器

说实话,我刚开始做嵌入式开发那会儿,调试代码全靠printf大法。代码里到处塞打印语句,编译下载看串口输出,改完再重复一遍……直到有一次,一个野指针问题折腾了我整整三天。后来一位老同事走过来,敲了几个命令,十分钟就定位到了问题。他用的就是GDB。从那天起,我就再也没离开过这个工具。

1.1 GDB是什么

GDB,全称GNU Debugger,是GNU项目下的一个调试器。说白了,它就是一个能让你「看穿」程序运行过程的工具。

你想想看,平时我们写代码,编译完直接运行。程序跑飞了、崩溃了、结果不对了,你只能看到最终现象。但GDB不一样,它能让程序在任意位置停下来,让你查看变量、寄存器、内存,甚至修改运行状态。

核心理解:GDB就像程序的「CT扫描仪」。普通调试是看表面症状,GDB能让你看到程序内部的每一个细节。

1.2 GDB能做什么

我在项目中用过GDB解决过太多问题了。简单列一下它的核心能力:

  • 设置断点——让程序在指定行停下来。我习惯在可疑函数入口先设个断点,看看参数对不对。
  • 单步执行——一行一行地跑代码。遇到函数调用,可以跟进去,也可以跳过。
  • 查看变量——随时查看任意变量的当前值。还能监控变量什么时候被修改。
  • 查看堆栈——程序崩溃时,看一眼调用堆栈,就知道是从哪条路走到这里的。
  • 修改内存——嗯,这个我常用。临时改个变量值,不用重新编译就能测试边界情况。
  • 反汇编——看C代码对应的汇编指令。调试启动代码或者优化问题时特别有用。

我的经验:GDB最强大的地方在于调试「偶发bug」。那种跑十次才出现一次的问题,用printf基本没戏。但用GDB的条件断点,可以精确捕获触发条件。

1.3 GDB的安装与启动

安装GDB其实很简单。不同平台略有差异:

平台 安装命令
Ubuntu/Debian sudo apt-get install gdb
CentOS/RHEL sudo yum install gdb
macOS brew install gdb(需要额外签名步骤)
Windows 安装MinGW或Cygwin,自带GDB

安装完后,验证一下:

$ gdb --version
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
...

启动GDB有两种方式。我一般这么用:

// 方式一:直接启动,再加载程序
$ gdb
(gdb) file ./my_program

// 方式二:启动时直接加载(我更喜欢这种方式)
$ gdb ./my_program

注意:编译程序时一定要加 -g 选项!不加的话,GDB能看到的东西非常有限。我曾经帮同事调试,折腾半天发现他编译没加-g……嗯,从那以后我每次写Makefile都会检查这个选项。

1.4 GDB的基本工作流程

GDB的工作流程,说白了就是「停-看-改-跑」四个字。我画了一张图,帮你理解这个过程:

GDB基本工作流程 1. 启动GDB 2. 设置断点 3. 运行程序 4. 查看/修改变量 继续运行 gdb ./program break main run print var

具体来说,一个典型的调试会话是这样的:

  1. 启动GDB:加载你的程序。我习惯用 gdb ./program 一步到位。
  2. 设置断点:告诉GDB在哪里停下来。比如 break main 在main函数入口停,break 42 在第42行停。
  3. 运行程序:输入 run 开始执行。程序会在第一个断点处停下。
  4. 查看状态:用 print 看变量,用 backtrace 看堆栈,用 info locals 看所有局部变量。
  5. 继续或单步:next 执行下一行(不进入函数),step 进入函数内部,continue 继续跑到下一个断点。
  6. 修改变量:用 set var x=5 临时改值,测试不同情况。

来看一个完整的例子。假设我们有这么一段代码:

// test.c
#include <stdio.h>

int add(int a, int b) {
    int result = a + b;
    return result;
}

int main() {
    int x = 10;
    int y = 20;
    int sum = add(x, y);
    printf("sum = %d\n", sum);
    return 0;
}

编译并调试:

$ gcc -g -o test test.c
$ gdb ./test

(gdb) break main
Breakpoint 1 at 0x4004f6: file test.c, line 9.

(gdb) run
Starting program: /home/user/test
Breakpoint 1, main () at test.c:9
9           int x = 10;

(gdb) next
10          int y = 20;

(gdb) print x
$1 = 10

(gdb) next
11          int sum = add(x, y);

(gdb) step
add (a=10, b=20) at test.c:4
4           int result = a + b;

(gdb) print a
$2 = 10

(gdb) print b
$3 = 20

(gdb) next
5           return result;

(gdb) print result
$4 = 30

(gdb) continue
Continuing.
sum = 30
[Inferior 1 (process 12345) exited normally]

(gdb) quit

我的习惯:调试时我一般先 break main,然后 run。这样程序从入口就开始受控。遇到函数调用不确定时,先用 step 跟进去看看,确认没问题了再用 next 跳过。

避坑指南:我曾经在调试一个多线程程序时,发现断点总是触发不到。查了半天,原来是断点设在了会被编译器优化掉的代码行上。记住,加了 -O2 或更高优化等级时,GDB的行号可能不准。调试阶段建议用 -O0 -g。

嗯,到这里GDB的基本概念和流程就讲完了。你可能会觉得,这些命令看起来也不复杂嘛。没错,GDB的入门门槛其实很低。但真正用好它,需要在实际项目中不断积累经验。后面几章,我会带你深入每个功能的具体用法。

可可精品资料库