Appearance
gdb —— GNU 调试器(崩溃还原 / 死锁定位 / 逻辑错误的瑞士军刀)
更新时间:2026-08-13
这个工具是做什么的
perf 告诉你哪个函数烧 CPU,strace 告诉你狂调哪些 syscall——但程序停不下来、停错地方、数据不对时,需要让程序在指定位置暂停并检视内部状态,这就是 gdb(GNU Debugger)的领地。它是 Linux 上最通用的调试器:可调试 C/C++/汇编/Rust/Go(需插件),支持断点、单步、内存/寄存器检视、多线程、core dump 离线分析。
可以回答什么问题
| 问题 | 怎么用 |
|---|---|
| 程序段错误/abort 崩了,死在哪一行? | gdb ./prog core → bt 看崩溃栈 |
| 程序卡死了/死锁了,各个线程在等什么? | gdb -p <PID> → thread apply all bt,看谁持锁等谁 |
| 变量值怎么变成了 0?哪个时刻变的? | break file:line + watch x,定位最后写入者 |
| 某个函数参数/返回值是什么? | break <func> → print args / finish 后看返回寄存器 |
| 数组越界/内存被踩,谁干的? | watch buf[0] 或 x 观察内存,看写入方 |
| 线上进程卡住不动,现场是什么? | gdb -p <PID> → thread apply all bt(无 GDB 权限时退路见末尾) |
| C++ 异常在哪里抛出、为何没被捕获? | catch throw / catch catch |
| 只看汇编时程序怎么走的? | disassemble /m <func> + si/ni 汇编单步 |
数据来源与原理(简版)
- 来源接口:
ptrace(2)系统调用。gdb 是 tracer,目标进程是 tracee;内核在两者之间转发"停/放行/读写"三类指令。ptrace 完整模型(tracer/tracee、ptrace-stop、四大类操作)见 ptrace.md。 - 断点三兄弟:
- 软件断点:gdb 把目标指令第一个字节改写为
0xCC(int3),命中后内核发SIGTRAP停住目标,gdb 再恢复原字节并把 RIP 回退到断点处。可随便下、数量多,但改的是代码段(-O0下没问题,写保护页除外)。 - 硬件断点:用 CPU 调试寄存器
DR0-DR3+DR7,不改内存,适合只读区域/内核热路径;数量有限(通常 4 个)。 - 观测点(watchpoint):监视数据地址的读写,本质是硬件断点(或退化为逐指令模拟)。最适合抓"谁改了这个变量"。
- 软件断点:gdb 把目标指令第一个字节改写为
- 单步:
PTRACE_SINGLESTEP置EFLAGS.TF,执行一条指令后触发#DB异常再停下。 - 符号解析:优先
.symtab/.debug_info(编译时-g);没有就按 build-id 查~/.debug/.build-id/缓存,或走 debuginfod 远程拉取。符号分离/归档的完整工作流见 symbol-separation.md;bt全是??的排查见 core-dump.md §5.1。 - 开销特性:与 strace 类似,走 ptrace 会让目标进程显著变慢(尤其单步),适合开发和测试阶段,生产环境尽量用 core/
gcore离线分析。
bash
yum install gdb / apt install gdb⚠️ 内核安全策略
ptrace_scope(/proc/sys/kernel/yama/ptrace_scope)默认只允许父进程附加子进程,附加别人的进程通常要sudo;容器里要加SYS_PTRACEcapability。详见 ptrace.md 的 ptrace_scope 一节。
一、启动三式
bash
gdb ./prog # ① 启动并加载可执行文件
gdb ./prog core.12345 # ② 离线分析 core(崩溃现场还原,见 core-dump.md)
gdb -p <PID> # ③ 附加到运行中的进程(Ctrl-C 停下目标,q 退出时可选 detach)常用参数:
| 参数 | 作用 |
|---|---|
-q | 安静模式,不打印版本版权信息 |
-tui | 进入文本界面(上半源码/汇编,下半命令行) |
-batch | 批处理模式:执行完 -ex 命令就退出,不交互——脚本/自动化首选 |
-ex "命令" | 启动后先执行一条 gdb 命令,可重复多次 |
-x script.gdb | 执行脚本文件(等价于交互里 source) |
--args ./prog a b | 把后面的参数当程序参数传入(省去 set args) |
启动文件:gdb 会先读
~/.gdbinit(用户级)和./.gdbinit(目录级,需信任配置)。批量脚本、pretty-printer、自定义命令都可以放这里。
二、基本调试循环(最核心的五连)
gdb
(gdb) break main # ① 设断点:函数 / 文件:行 / 文件:函数 / *地址
(gdb) run # ② 启动(或 restart);运行到断点/信号/异常停下
(gdb) bt # ③ 看调用栈,判断"停在哪条路径上"
(gdb) print x # ④ 查变量值(格式:/x 十六进制 /s 字符串 /f 浮点 /t 二进制)
(gdb) continue # ⑤ 继续跑,等下一个断点;next/step 则单步推进
三、断点体系
3.1 断点类型
gdb
break worker_loop # 按函数名
break main.cpp:100 # 按 文件:行号
break main.cpp:worker_loop # 按 文件:函数(同名函数消歧义)
break *0x40123a # 按地址(配 disassemble 用)
break main.cpp:100 if i == 10 # 条件断点:只在条件成立时停(比手动 continue 高效)
tbreak main.cpp:100 # 临时断点:命中一次自动删除
rbreak Worker:: # 正则断点:给所有匹配符号设断点
hbreak main.cpp:100 # 硬件断点:用调试寄存器,不改内存gdb
info breakpoints # 列出所有断点(含编号/状态/命中次数)
delete 2 # 删 2 号断点;delete 删全部
disable 2 / enable 2 # 禁用/启用,不删
clear main.cpp:100 # 按位置删断点
ignore 2 100 # 跳过 2 号断点前 100 次命中
commands 2 # 给 2 号断点挂命令列表(命中后自动执行)
> print x
> continue # 不加 continue 会停在断点
> end
save breakpoints bp.txt # 保存断点 → 换环境后 source bp.txt 恢复断点生效时机:
run之后才真正写入。改完代码要重新编译再run——gdb 不感知源码变化,行号/符号错位是常见坑。
3.2 观测点(watchpoint)——抓"谁改了它"
gdb
watch x # 数据 x 被写就停
rwatch x # 数据 x 被读就停
awatch x # 读写都停
watch *(int*)0x7fff... # 监视裸地址适合定位内存被踩/变量莫名变值:设好观测点后
continue,gdb 会精确停在被写的那条指令上。局部变量在离开作用域时观测点自动失效。
3.3 catch —— 抓信号与 C++ 异常
gdb
catch throw # C++ 异常抛出时停(定位"异常从哪抛的")
catch catch # 异常被捕获时停
catch syscall write # 目标进程调 write 时停
catch signal SIGSEGV # 收到 SIGSEGV 时停
info catchpoints四、执行控制
| 命令 | 含义 | 典型用途 |
|---|---|---|
continue (c) | 继续运行 | 到下一个断点 |
next (n) | 单步,不进入函数 | 跳过函数调用 |
step (s) | 单步,进入函数 | 钻进被调函数内部 |
finish (fin) | 跑完当前函数并返回 | 看函数返回值 |
until (u) | 跑到当前行/指定位置 | 快速跳出循环 |
nexti (ni) | 单步一条汇编指令(不进入 call) | 汇编级调试 |
stepi (si) | 单步一条汇编指令(进入 call) | 同上 |
jump *0x4012ab | 跳到指定地址继续执行 | 跳过已知坏代码段 |
return | 立即从当前函数返回 | 伪造函数结果继续跑 |
signal SIGUSR1 | 给程序发信号 | 测试信号处理 |
gdb
set print pretty on # 结构体/容器美化打印
set pagination off # 关闭分页(批处理/脚本必设,否则卡住)
set disassembly-flavor intel # 反汇编用 Intel 语法(更常见)五、查看数据
5.1 变量与表达式
gdb
print x # 简写 p;print i+1 可求表达式
print/x p # 十六进制(/x /d /u /o /t /c /f /s)
print *p # 解指针
ptype node # 打印类型定义
info locals # 当前帧所有局部变量
info args # 当前帧所有参数
set var x = 42 # 运行期改值(临时绕过 bug 继续调试)
call func(1, 2) # 调用目标进程里的函数(危险但有用)
display x # 每次停下自动打印 x;undisplay N 取消5.2 内存检视 x(重点,格式固定为 x/<数量><单位><格式>)
gdb
x/10xw &buf # 10 个 4 字节十六进制字
x/20i main # 反汇编 20 条指令
x/8gb buf # 8 个 8 字节
x/32bx &arr # 32 个单字节十六进制(字节级看内存布局)
x/s str # 字符串
x/f &d # 浮点| 单位 | 含义 | 格式 | 含义 |
|---|---|---|---|
b | 1 字节 | x | 十六进制 |
h | 2 字节 | d | 十进制 |
w | 4 字节 | u | 无符号十进制 |
g | 8 字节 | o | 八进制 |
i | 指令 | t | 二进制 |
s | 字符串 | c | 字符 |
f | 浮点 | a | 地址 |
5.3 寄存器
gdb
info registers # 全部寄存器(含标志位)
info registers rip rsp # 只看关键的
print $rax # x86-64 函数返回值通常在这x86-64 寄存器语义速查(参数/返回值/调用约定)见 x86-64-registers.md。
六、栈操作(bt 全解析)
gdb
bt # 调用栈,最底下是 main,最上面是当前函数
bt full # 每帧带局部变量+参数(最常用!崩溃分析首选)
bt 5 / bt -5 # 只看前/后 5 帧
frame 2 # 切到 2 号帧(#0 是当前帧)
up / down # 往调用者/被调者方向切一帧
info frame # 当前帧详情(栈基址、PC、函数参数)空指针崩溃现场长这样,
bt第一帧直接给答案:gdb(gdb) bt #0 Worker::print (this=0x0) at worker.cpp:18 ← this=0x0,空指针实锤 #1 0x00000000004011d2 in main () at main.cpp:42
七、多线程 / 多进程调试
gdb
info threads # 列出所有线程(gdb 自己编的号 ←→ LWP)
thread 3 # 切到 3 号线程
thread apply all bt # ★ 所有线程各自打栈——死锁/卡死第一招
thread apply 1 3 bt # 只打 1、3 号线程
set scheduler-locking on # 单步时只放行当前线程(默认其他线程会继续跑)
set print thread-events on # 显示线程创建/退出gdb
set follow-fork-mode child # fork 后跟子进程(默认 parent)
set detach-on-fork on|off # on: 另一个进程脱离调试;off: 两个都管
info inferiors # 多进程调试时的进程列表死锁定位完整实战(
thread find LWP、从 mutex 地址反查持锁者、信号 handler 里的死锁闭环)见 gdb-deadlock-locate.md——本文 §7 是它的前置基础。
八、源码与汇编级调试
gdb
list / list main.cpp:100 # 看源码(带行号),默认 10 行
set listsize 30 # 调整 list 行数
disassemble main # 反汇编函数
disassemble /m main # 源码 + 汇编对照(查编译产物/优化)
layout asm / layout src # TUI 分屏:上半汇编/源码,下半命令
layout split # 源码+汇编+命令三分屏汇编级调试(
si逐指令、看$rip/寄存器、配disassemble)适合:定位未定义行为、验证优化器产物、读库代码无源码时。-O2下源码与指令对不上是正常的(见 compile-link-load.md 的优化一节)。
九、批处理与自动化(生产排查利器)
bash
# 离线分析 core:一条命令打印全部线程栈(配合 systemd 定时抓取脚本)
gdb -batch -ex "set pagination off" -ex "bt full" \
-ex "thread apply all bt" ./prog core.12345
# 附加线上进程,抓线程栈后立即退出
sudo gdb -batch -ex "thread apply all bt" -p <PID>
# 在线抓 core(进程活着,先凝固现场再慢慢看)
gcore <PID> # 生成 <PID>.core,可离线 gdb 分析常用组合:-batch(非交互退出)+ -ex(多条命令)+ set pagination off(防分页卡死)+ set confirm off(防确认提示)。thread apply all bt 三件套是任何 crash/卡死现场的第一动作。
十、与 core dump / 符号的配合
- 崩溃还原全流程:
gdb ./prog core→bt full→frame N→info locals→x看关键内存。core 文件结构(PT_NOTE寄存器快照 /PT_LOAD内存镜像)与生成配置见 core-dump.md。 - 符号对不上:
bt全是??→ 编译没-g、二进制被strip、或 build-id 缓存缺失。解决路径见 symbol-separation.md:-g编译、保留/usr/lib/debug分离调试信息、debuginfod 远程拉符号。 coredumpctl(systemd):coredumpctl debug一条命令自动找到二进制+符号进 gdb,比手拼路径省事。
十一、常见场景速查
| 场景 | 命令组合 |
|---|---|
| 段错误还原现场 | gdb prog core → bt → frame 0 → info locals → x 看可疑内存 |
| 程序卡死/死锁 | gdb -p <PID> → thread apply all bt → 找"谁等谁"(深入见 gdb-deadlock-locate.md) |
| 变量值错 | break <行> → print x → watch x → continue 抓写入者 |
| 数组越界踩内存 | watch buf[0] / x/32bx &buf 观察 → 定位越界写入 |
| 函数结果不对 | break <func> → finish → print $rax(x86-64) |
| C++ 异常吞了 | catch throw → run → 停在抛出点看构造参数 |
| 线上进程挂起 | sudo gdb -batch -ex "thread apply all bt" -p <PID>(无 ptrace 权限时:gcore 或等 core) |
| 疑是优化器 bug | disassemble /m <func> + si 核对产物;-O0 重编对比 |
无 GDB 可用的退路:
strace -p <PID>看卡在哪个 syscall、/proc/<PID>/stack看内核栈、perf sched看调度等待——但用户态栈只有 gdb/core 拿得到。
十二、命令速查总表
| 类 | 命令 | 作用 |
|---|---|---|
| 启动 | gdb ./prog / gdb -p PID / gdb prog core | 三种入口 |
-batch -ex ... / -x script | 批处理 / 脚本 | |
| 断点 | break tbreak rbreak hbreak | 四类断点 |
watch rwatch awatch | 数据观测 | |
catch throw catch syscall catch signal | 事件捕获 | |
info breakpoints delete disable ignore commands save/source | 断点管理 | |
| 运行 | run continue next step finish until | 执行控制 |
nexti stepi jump return signal | 汇编/跳转 | |
| 数据 | print ptype x display info locals/args set var call | 查看/修改 |
| 栈 | bt bt full frame up down info frame | 调用栈 |
| 线程/进程 | info threads thread thread apply all bt scheduler-locking | 并发调试 |
follow-fork-mode detach-on-fork info inferiors | 多进程 | |
| 源码/汇编 | list disassemble layout set disassembly-flavor | 代码视窗 |
| 信息 | help apropos <词> info <主题> show/set | 自助查询 |
help <命令>是 gdb 自带的离线手册;apropos watch按关键字搜命令——记不全时靠这两条。
与同仓库文档的关系
| 文档 | 与本文关系 |
|---|---|
| cpp-debug.md | 本文是系统教程(从启动到批处理全覆盖),该文是高级技巧合集(汇编级/内存碎片量化等进阶),互不重复、互相衔接 |
| gdb-deadlock-locate.md | 本文 §7 打底,该文是死锁定位实战延伸 |
| core-dump.md | 本文 §10 打底,该文讲 core 生成配置与符号前置 |
| ptrace.md | gdb 底层的内核机制(tracer/tracee、ptrace-stop、ptrace_scope) |
| symbol-separation.md | 符号分离/归档/build-id cache,bt 全是 ?? 的根治 |
| strace.md | 无 GDB 时看 syscall 层面的退路 |
一句话总结:
perf找"哪里烧 CPU",strace找"疯狂调什么 syscall",gdb 找"程序停在哪、数据为何错"——三者回答的是三个不同的问题,崩溃现场还原永远从gdb <程序> <core>+bt full开始。