1. GDB入门

GDB到底是什么?

GDB,全称GNU Debugger。说白了,就是一个能让你「钻进程序内部」看个究竟的工具。

我刚开始做嵌入式开发那会儿,调试基本靠printf。遇到bug就加打印,编译,运行,看输出,再改代码……循环往复。效率低不说,有时候加个打印反而把问题掩盖了。后来一位老同事跟我说:「你试试GDB吧,别再用printf硬扛了。」

嗯,这一试就回不去了。

GDB能让你在程序运行时暂停、查看变量、修改内存、单步执行、查看堆栈……说白了,它就像给程序装了个「显微镜」。你想想看,如果代码是一栋大楼,printf就像站在楼外喊话,而GDB能让你走进每个房间翻箱倒柜。

GDB能做什么?

我总结了一下,GDB的核心能力其实就这几条:

  • 启动程序:可以指定参数、环境变量来启动你的程序
  • 设置断点:让程序在指定行、指定函数、甚至指定条件时停下来
  • 查看变量:停下来之后,你可以看变量的值、内存的内容
  • 修改状态:甚至可以临时修改变量的值,看看程序会怎么走
  • 单步执行:一行一行地走,看程序到底是怎么跑的
  • 查看堆栈:程序崩溃了?看看是谁调了谁,一路回溯上去
  • 调试多线程/多进程:哪个线程卡住了?哪个进程挂了?GDB都能管

一句话总结:GDB就是程序员的「后悔药」和「透视镜」。

GDB的安装与版本检查

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

Ubuntu/Debian 系

sudo apt-get update
sudo apt-get install gdb

CentOS/RHEL/Fedora 系

sudo yum install gdb
# 或者
sudo dnf install gdb

macOS

brew install gdb

装完之后,检查一下版本:

gdb --version

你会看到类似这样的输出:

GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
...

我个人习惯:装完GDB后第一件事就是跑一下 gdb --version。确认版本号,心里踏实。我曾经遇到过系统自带的GDB版本太老,不支持某些新特性,折腾了半天才发现是版本问题。

第一个GDB调试会话

光说不练假把式。我们来写一个简单的C程序,然后用GDB调试它。

准备一个示例程序

创建一个文件 hello.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;
}

编译时一定要加 -g 选项,这样才能保留调试信息:

gcc -g -o hello hello.c

注意:不加 -g 编译出来的程序,GDB也能调试,但你看不到源码、变量名这些信息。说白了就是「盲调」,非常痛苦。我曾经有一次忘了加 -g,对着汇编代码看了半天……嗯,那感觉就像在黑暗里找开关。

启动GDB

gdb ./hello

你会看到GDB的提示符 (gdb)。这就说明你已经进入GDB的世界了。

设置断点

我们在 main 函数入口处设一个断点:

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

也可以在某一行设断点:

(gdb) break hello.c:12
Breakpoint 2 at 0x11c0: file hello.c, line 12.

运行程序

(gdb) run
Starting program: /home/user/hello 

Breakpoint 1, main () at hello.c:9
9	    int x = 10;

程序停在了第9行。注意看,GDB告诉你:停在了 main 函数,文件 hello.c 的第9行。

单步执行

用 next 命令(简写 n)可以执行下一行:

(gdb) next
10	    int y = 20;

再执行一次:

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

查看变量

用 print 命令(简写 p)查看变量:

(gdb) print x
$1 = 10
(gdb) print y
$2 = 20

看到没?x 是10,y 是20,跟代码里写的一样。

进入函数内部

用 step 命令(简写 s)可以进入函数内部:

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

你看,GDB带着我们跳进了 add 函数。参数 a=10、b=20 都显示出来了。

继续执行到结束

用 continue 命令(简写 c)让程序继续跑完:

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

程序正常退出,打印了 sum = 30。

退出GDB

(gdb) quit

避坑指南:我曾经在调试一个复杂的嵌入式程序时,忘记设断点就直接 run,结果程序跑完了,什么都没抓到。后来我养成了一个习惯:先设好断点,再 run。如果程序已经跑起来了,可以用 Ctrl+C 中断它,再设断点。

本章知识体系

下面这张图帮你理清GDB入门的核心脉络:

GDB入门 GDB是什么 GDB能做什么 安装与版本检查 GNU调试器 程序显微镜 设置断点 查看变量 单步执行 查看堆栈 apt/yum/brew install gdb --version 检查 第一个GDB调试会话

这张图把GDB入门分成了三大块:它是什么、它能做什么、怎么安装检查。最下面就是咱们刚刚亲手操作过的「第一个调试会话」。你看,其实没那么复杂,对吧?

核心要点回顾:

  • GDB是GNU调试器,让你能「钻进程序内部」
  • 编译时加 -g 选项才能保留调试信息
  • 常用命令:break(断点)、run(运行)、next(下一步)、step(进入函数)、print(查看变量)、continue(继续)、quit(退出)

好了,第一章就到这儿。你已经在GDB的世界里迈出了第一步。后面我们会一步步深入,从单步调试到多线程、从core dump分析到Makefile集成……嗯,路还长,但每一步都值得。