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

说实话,我刚开始做嵌入式开发那会儿,调试代码全靠printf。那时候觉得,打印大法好,哪里不对打哪里。直到有一次,一个内存越界的问题让我整整排查了三天...后来同事看不下去了,丢给我一句:「你试试GDB吧。」

嗯,从那以后,我再也没离开过GDB。

GDB是什么?

GDB,全称GNU Debugger。说白了,就是一个让你能「看」到程序内部运行状态的工具。

你想想看,平时我们写代码,编译完直接跑。程序崩了,就给你一个Segmentation fault,然后呢?没了。你根本不知道它崩在哪一行,变量值是什么,调用栈长什么样。

GDB就是来解决这个问题的。它让你可以:

  • 让程序在任意一行停下来
  • 查看那一刻所有变量的值
  • 一步步跟踪代码执行
  • 查看函数调用链
  • 甚至修改内存里的数据

我个人习惯把GDB比作「程序的CT机」。它能给运行中的程序做全身扫描,哪里有问题,一目了然。

GDB能做什么?

我整理了一下,GDB的核心能力其实就这几大类:

功能类别 具体能力 实际场景
断点调试 在指定行、函数、条件处暂停 定位崩溃位置、检查逻辑错误
变量查看 查看/修改变量值、内存内容 确认变量是否如预期变化
调用栈分析 查看函数调用链、回溯崩溃路径 排查段错误、空指针
单步执行 逐行、逐函数执行代码 理解复杂逻辑、追踪数据流
多线程/多进程 切换线程、查看线程状态 调试并发问题、死锁

我在项目中遇到过最典型的一个场景:一个服务跑着跑着就挂了,没有任何日志。用GDB挂上去,等它再崩溃时,backtrace一看,发现是一个野指针在作祟。这种问题,printf根本查不出来。

GDB的安装与启动

安装GDB其实很简单。不同系统下命令略有不同:

# Ubuntu/Debian
sudo apt-get install gdb

# CentOS/RHEL
sudo yum install gdb

# macOS (通过Homebrew)
brew install gdb

# 验证安装
gdb --version

嗯,这里要注意:macOS上安装GDB后,还需要做代码签名,否则没法调试。我第一次在Mac上折腾这个,花了大半天...后来发现直接装lldb反而省事。不过咱们课程还是以GDB为主,毕竟Linux下它是标配。

基本调试流程

GDB的调试流程,说白了就四步。我带你走一遍:

第一步:编译带-g选项

这是最关键的一步。没有-g,GDB就不知道源代码长什么样,你看到的全是汇编指令。

// 示例代码:bug.c
#include <stdio.h>

void crash_me() {
    int *p = NULL;
    *p = 42;  // 这里会崩溃
}

int main() {
    printf("Hello, GDB!\n");
    crash_me();
    printf("This won't print\n");
    return 0;
}
# 编译时一定要加-g
gcc -g -o bug bug.c
注意: -g选项会往可执行文件里插入调试信息。生产环境建议用-g编译但strip掉符号表,或者单独保存调试信息文件。我曾经在线上环境忘了strip,结果别人用GDB直接看到了源码...嗯,教训深刻。

第二步:启动GDB

启动方式有两种:

# 方式一:直接调试可执行文件
gdb ./bug

# 方式二:先启动,再加载文件
gdb
(gdb) file ./bug

我个人习惯用第一种,一步到位。

第三步:运行程序

进入GDB后,用run命令启动程序:

(gdb) run
Starting program: /home/user/bug 
Hello, GDB!

Program received signal SIGSEGV, Segmentation fault.
0x0000555555555149 in crash_me () at bug.c:5
5           *p = 42;  // 这里会崩溃

看到了吗?GDB直接告诉你在哪一行崩了。这就是-g的威力。

第四步:查看崩溃信息

崩溃后,我们最关心的就是三件事:

  1. 在哪里崩的 — 用backtrace(简写bt)
  2. 变量值是什么 — 用print(简写p)
  3. 调用链是怎样的 — 用frame(简写f)
(gdb) bt
#0  0x0000555555555149 in crash_me () at bug.c:5
#1  0x0000555555555172 in main () at bug.c:10

(gdb) info locals
p = 0x0

(gdb) p *p
Cannot access memory at address 0x0

信息很明确:crash_me函数里,p是空指针,然后试图往空地址写数据,崩了。

小技巧: 如果你只想看当前帧的源码上下文,用list命令。它会显示当前行附近10行代码,方便你确认逻辑。

知识体系总览

下面这张图,是我梳理的GDB入门知识结构。你可以把它当作学习地图:

GDB入门知识体系 GDB调试器 GDB是什么 GDB能做什么 安装与启动 基本调试流程 GNU调试器 源码级调试 断点/单步 变量/内存 调用栈分析 apt/yum安装 gdb命令启动 编译带-g run运行 bt/p/info 核心:编译带-g → GDB启动 → 运行 → 分析

核心要点总结:

  • GDB是源码级调试器,不是反汇编工具
  • -g选项是GDB工作的前提,忘了它GDB就废了一半
  • 调试流程就四步:编译带-g → 启动GDB → run运行 → bt/p分析
  • 崩溃信息看三个地方:bt看调用链,info locals看变量,list看源码

好了,第一章的内容就到这里。GDB入门其实不难,难的是养成用GDB的习惯。我见过太多人,明明装了GDB,遇到问题还是下意识地加printf。嗯,慢慢来,后面几章我会带你深入每个功能,让你真正爱上这个工具。

可可精品资料库