一、简介:为什么必须掌握 gdb?
C 语言贴近硬件,指针越界、段错误、内存泄漏悄无声息,编译期无法发现。
printf 打桩效率低、破坏线程时序、难以观察复杂结构体。
gdb(GNU Debugger)是 Linux 下事实标准调试器,支持:
断点/单步/回退执行
查看变量、内存、寄存器
多线程、Core 文件、远程调试
应用场景
嵌入式开发(ARM/Linux)
高性能服务器(Nginx、Redis)
算法竞赛现场(ACM/OI 允许携带 gdb 脚本)
学会 gdb = 给 C 程序装“外科手术灯”,定位问题从“小时”级缩短到“分钟”级。
二、核心概念:5 个单词先记牢
| 名词 | 一句话说明 | 本文出现形式 |
|---|---|---|
| 断点(breakpoint) | 让程序暂停的地址/行号 | b main |
| 单步(step/next) | 逐行执行,step 进入函数,next 不进入 | s/n |
| 回溯(backtrace) | 查看调用栈,秒级定位段错误 | bt |
| Core 文件 | 程序崩溃时的内存快照,可事后调试 | gdb a.out core |
| TUI | gdb 的文本图形界面,代码窗口+命令窗口 | ctrl+x a |
三、环境准备:3 行命令搞定
操作系统
Ubuntu 20.04+ / CentOS 8+ / WSL2 均可(内核无要求)。安装 gdb
sudo apt update && sudo apt install -y gdb gcc # Debian/Ubuntu sudo dnf install -y gdb gcc # CentOS/RHEL确认版本
gdb --version | head -1 # ≥ 8.0 支持语法高亮实验目录
mkdir -p ~/gdb-lab && cd ~/gdb-lab
四、实际案例与步骤:从 10 行小程序到 Core dump
每个示例均可直接复制,保存后
chmod +x run.sh && ./run.sh一键跑通。
4.1 编译就要带调试信息:-g -O0
# file: hello.c #include <stdio.h> int main(){ int a = 1; printf("a=%d\n", a); return 0; } # 编译命令(记住模板) gcc -g -O0 hello.c -o hello要点:
-g生成调试符号;-O0关闭优化,防止变量被优化消失。
4.2 快速体验:启动→断点→打印→继续
gdb hello -ex "b main" -ex "r" -ex "p a" -ex "c" -ex "q"一行命令拆解
| 片段 | 作用 |
|---|---|
gdb hello | 加载可执行文件 |
-ex "b main" | 设置断点 |
-ex "r" | run |
-ex "p a" | print 变量 a |
-ex "c" | continue |
-ex "q" | 退出 |
输出:
Breakpoint 1, main () at hello.c:4 4 int a = 1; (gdb) p a $1 = 1恭喜,你已完成人生第一次 gdb 调试!
4.3 交互式调试:单步 + 查看源码
gdb hello (gdb) b main (gdb) r (gdb) n # next,不进入 printf (gdb) l # list,显示源码 (gdb) p a (gdb) s # step,会进入 printf 库函数(若想看可进入) (gdb) q快捷键:n/s/c/q养成肌肉记忆。
4.4 段错误经典案例:空指针解引用
// file: crash.c #include <stdio.h> int main(){ int *p = NULL; *p = 42; // 段错误 return 0; }编译 & 运行:
gcc -g -O0 crash.c -o crash ./crash # 屏幕输出:Segmentation fault (core dumped)调试步骤:
# 1. 直接 gdb 跑 gdb crash -ex r # 2. 自动停在 SIGSEGV (gdb) bt # 输出 #0 main () at crash.c:5 # 3. 看变量 (gdb) p p $1 = (int *) 0x0结论:p是空指针,*p = 42导致段错误,3 秒定位。
4.5 Core 文件事后调试:生产环境必用
打开 core 开关
ulimit -c unlimited echo "core.%e.%p" | sudo tee /proc/sys/kernel/core_pattern重新运行 crash
目录下生成core.crash.12345事后调试
gdb crash core.crash.12345 -ex bt -ex q无需重新运行程序,现场保留。
4.6 多线程调试:生产者-消费者示例
// file: pc.c(片段,完整代码见文末 GitHub) pthread_mutex_t mtx; pthread_cond_t cv; ... void* producer(void* arg){ pthread_mutex_lock(&mtx); while (count == MAX) pthread_cond_wait(&cv, &mtx); buffer[in] = rand(); in = (in+1)%MAX; count++; pthread_mutex_unlock(&mtx); }编译:
gcc -g -O0 pc.c -o pc -pthread调试:
gdb pc (gdb) b producer (gdb) r (gdb) info threads # 查看线程 (gdb) thread 2 # 切换线程 (gdb) bt技巧:set print thread-events off减少线程切换提示噪音。
4.7 TUI 文本界面:告别黑白屏
gdb pc -tui # 或内部:ctrl+x a界面拆分:
上半部:源码窗口
下半部:gdb 命令窗口
快捷键:ctrl+x 2打开汇编/寄存器窗口,炫酷又实用。
4.8 VS Code 图形调试(bonus)
安装插件C/C++(ms-vscode.cpptools)
按
F5→ 选择gdb→ 自动生成.vscode/launch.json关键片段(可复制)
{ "name": "gdb launch", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/crash", "miDebuggerPath": "/usr/bin/gdb", "stopAtEntry": false, "cwd": "${workspaceFolder}", "externalConsole": false }打断点、监视变量、调用栈图形化,新手友好。
五、常见问题与解答(FAQ)
| 问题 | 现象 | 解决 |
|---|---|---|
No symbol table is loaded. | 忘了-g | 重新gcc -g -O0编译 |
ptrace: Operation not permitted. | 容器/Seccomp 限制 | 加--cap-add=SYS_PTRACE |
| 打印 STL 容器乱码 | std::vector显示 `{...}`` | 安装gdb-pretty-printer或升级 gdb≥9 |
| 单步进入汇编 | s后看到__libc_start_main汇编 | 用n直到源码行,或set step-mode off |
| Core 文件太大 | 磁盘爆满 | 限制大小ulimit -c 102400或使用systemd-coredump |
六、实践建议与最佳实践
编译脚本模板(保存为
build.sh)#!/bin/bash set -e gcc -g -O0 -Wall -Wextra "$1.c" -o "$1" "${@:2}"用法:
./build.sh crash -pthread.gdbinit 个人配置
set print pretty on set print array on set confirm off set history save on自动化小脚本:一键 backtrace
# usage: gdb-bt <prog> <core> gdb -batch -ex bt "$1" "$2"远程调试(嵌入式)
目标板:gdbserver :1234 ./app
本地:gdb app -ex "target remote 192.168.1.100:1234"生产环境
永远保留未裁剪二进制(
-g)到调试仓库。使用
strip --only-keep-debug app app.debug分离符号,减小发布体积。
七、总结:一张脑图带走全部要点
gdb 调试路线图 ├─ 编译:gcc -g -O0 ├─ 启动:gdb hello → b main → r ├─ 单步:n / s / finish ├─ 查看:p var / bt / info locals ├─ 高级:core / TUI / VS Code / gdbserver └─ 习惯:.gdbinit + 自动化脚本掌握 gdb,你就拥有了:
段错误3 分钟定位能力
多线程死锁可视化调用栈
Core dump事后复盘,无需现场重新运行
远程/图形/自动化三套环境无缝切换
立刻打开终端,复制本文命令跑一遍,把build.sh、.gdbinit加入你的 Git 仓库——从此告别 printf 打桩,调试效率翻倍!祝你玩的开心,代码无 bug。