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

大家好,我是你们的老朋友。做了十几年嵌入式开发,调试过无数个让人头疼的bug。今天咱们聊聊GDB——这个我几乎每天都在用的工具。

说实话,刚入行那会儿,我调试代码全靠printf大法。代码里到处是printf,编译、下载、看串口输出,改完再重复一遍。直到有一次,一个偶发性的指针越界问题,我打了整整两天的printf都没定位到。后来一位老同事走过来,敲了几行GDB命令,十分钟就找到了问题根源。从那天起,我就再也没离开过GDB。

一句话总结:GDB是GNU项目下的源码级调试器,是C/C++程序员最锋利的调试工具。

1.1 GDB是什么?

GDB,全称GNU Debugger。说白了,就是一个让你能“看穿”程序运行过程的工具。

你想想看,平时我们写代码,编译完直接运行。程序跑飞了、崩溃了、结果不对了,你只能靠猜。GDB能做什么?它能让你:

  • 让程序在任意一行停下来(断点)
  • 停下来后,查看变量的值、内存的内容
  • 单步执行,一行一行看代码怎么跑的
  • 程序崩溃时,告诉你崩溃在哪一行、调用栈是什么
  • 甚至能修改运行中的变量值,临时“修复”问题

我个人习惯把GDB比作“程序的CT机”。printf就像X光片,能看到大概;GDB就像CT扫描,每一层、每一个细节都清清楚楚。

1.2 GDB能做什么?

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

功能类别 具体能力 我常用的场景
断点调试 设置行断点、函数断点、条件断点 怀疑某行代码有问题,停在那看变量
单步执行 step into、step over、continue 跟踪函数调用,看参数传递是否正确
变量查看 print、display、watch 监控某个变量何时被修改
内存分析 x命令查看内存、查看寄存器 排查指针越界、内存泄漏
崩溃分析 backtrace、frame、core dump分析 程序段错误时,快速定位崩溃点
多线程/多进程 切换线程、查看线程栈 调试并发问题、死锁问题

我的小建议:刚开始学GDB,别贪多。先把断点、单步、变量查看这三个基本功练熟,能解决80%的日常调试问题。

1.3 GDB的安装与启动

安装GDB其实很简单。不同平台稍微有点区别:

Linux系统:

# Ubuntu/Debian
sudo apt-get install gdb

# CentOS/RHEL
sudo yum install gdb

# 验证安装
gdb --version

macOS系统:

# 使用Homebrew
brew install gdb

# 注意:macOS上需要额外签名,否则可能无法调试

Windows系统:

我建议用MinGW或者WSL。Windows原生GDB体验不太好,我个人更推荐在WSL的Linux环境下调试。

注意:编译程序时,一定要加-g选项!不加-g,GDB就看不到源码和变量名,只能看到汇编代码。我曾经帮一个同事排查问题,他编译时忘了加-g,结果GDB里全是地址和汇编,调试难度直接翻倍。

启动GDB有两种方式:

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

# 方式二:启动时直接加载程序(我更喜欢这种方式)
gdb ./myprogram

1.4 第一个GDB调试会话

光说不练假把式。咱们写个简单的程序,走一遍完整的调试流程。

先看代码:

// demo.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;
}

编译并启动GDB:

gcc -g -o demo demo.c
gdb ./demo

进入GDB后,你会看到类似这样的提示符:

(gdb) 

好,现在开始我们的第一个调试会话:

# 1. 在main函数入口设置断点
(gdb) break main
Breakpoint 1 at 0x4004f6: file demo.c, line 9.

# 2. 运行程序
(gdb) run
Starting program: /home/user/demo

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

# 3. 单步执行到下一行
(gdb) next
10          int y = 20;

# 4. 查看变量x的值
(gdb) print x
$1 = 10

# 5. 继续单步
(gdb) next
11          int sum = add(x, y);

# 6. 查看x和y的值
(gdb) print x
$2 = 10
(gdb) print y
$3 = 20

# 7. 单步进入add函数
(gdb) step
add (a=10, b=20) at demo.c:3
3           int result = a + b;

# 8. 查看函数参数
(gdb) print a
$4 = 10
(gdb) print b
$5 = 20

# 9. 单步执行完add函数
(gdb) next
4           return result;
(gdb) print result
$6 = 30

# 10. 继续执行到程序结束
(gdb) continue
Continuing.
sum = 30
[Inferior 1 (process 12345) exited normally]

看到了吗?整个流程非常清晰。每一步你都知道程序在干什么,变量是什么值。这就是GDB的魅力。

核心要点:

  • break:设置断点,让程序停在你关心的位置
  • run:启动程序,遇到断点会停下
  • next:单步执行,不进入函数内部
  • step:单步执行,会进入函数内部
  • print:查看变量或表达式的值
  • continue:继续执行,直到下一个断点或程序结束

避坑指南:我曾经犯过一个低级错误——在调试时忘了加-g编译。结果GDB里全是0x7fffffff这样的地址,根本看不到变量名。后来我养成了习惯,写Makefile时默认加上-g,调试完再换成-O2优化。

嗯,到这里,GDB的入门内容就差不多了。你可能会问:就这几个命令,能解决复杂问题吗?别急,这只是开胃菜。后面的章节,我会带你一步步深入,从断点的高级用法,到多线程调试,再到core dump分析。每一个技巧,都是我在实际项目中踩过坑、流过汗换来的经验。

记住一句话:调试不是浪费时间,而是节省时间。用GDB调试,一次定位问题,比打一百次printf都管用。

GDB入门知识体系 GDB调试器 GDB是什么 GDB能做什么 安装与启动 第一个调试会话 源码级调试器 GNU项目工具 断点/单步/变量查看 内存分析/崩溃定位 Linux/macOS/Windows gcc -g 编译 break/run/next/step print/continue

可可精品资料库