GDB 进阶调试(Advanced GDB)

基础命令、寄存器/内存查看见模块十四。本模块收录:核心转储、反向调试、硬件观察点、断点自动化、信号控制、attach、多线程、栈帧深入与脚本化。全部命令在 GDB 15 上实测。

核心转储(Core Dump)

程序崩溃时把内存现场写入 core 文件,事后用 GDB 离线分析:

ulimit -c unlimited          # 允许生成 core 文件
./crash                      # 崩溃后目录下出现 core 文件
gdb ./crash core             # 离线调试:崩溃瞬间的现场
(gdb) bt                     # 崩溃时的调用栈
(gdb) x/i $pc                # 崩溃指令
(gdb) info registers         # 崩溃时的寄存器

注意:现代 Ubuntu 的 /proc/sys/kernel/core_pattern 指向 apport 管道(|/usr/share/apport/apport ...),core 文件不会出现在当前目录(转存于 /var/crash,可用 apport-unpack 解出)。若需要传统 core 文件,执行 sysctl -w kernel.core_pattern=core(需 root)后再崩溃一次。没有 core 文件时,直接 gdb ./crash + run 现场复现同样有效。

反向调试(Reverse Debugging)

GDB 可记录执行历史并倒着走——对"寄存器什么时候被改坏"这类问题极其有效:

(gdb) break _start
(gdb) run
(gdb) record full            # 开始记录执行轨迹
(gdb) continue               # 跑到出错点
(gdb) reverse-stepi          # 倒着单步(rsi)
(gdb) reverse-continue       # 反向继续,直到断点/记录起点
(gdb) p $rcx                 # 回退后查看历史值
(gdb) record stop            # 停止记录

实测示例(前进 4 步 %rcx=1reverse-stepi 4%rcx=0,寄存器值随指令回滚)。限制:记录有性能与内存开销;不能跨系统调用反向(syscall 会停止记录,报 Process record: inferior program stopped)。

硬件观察点(Watchpoints)

命令 触发条件
watch expr 表达式被时(寄存器、内存均可,如 watch $rcx
rwatch expr 表达式被时(仅内存地址)
awatch expr 读或写都触发(仅内存地址)
(gdb) watch $rcx                    # rcx 每次变化都停
(gdb) rwatch *(long*)&counter       # 谁读了 counter
(gdb) awatch *(long*)&counter       # 谁碰了 counter
(gdb) info watchpoints

实测注意:rwatch/awatch 不能用于寄存器表达式(报 Expression cannot be implemented with read/access watchpoint),要用内存地址;硬件观察点数量有限(x86 通常 4 个),超出自动降级为慢速软件单步。

断点自动化

命中断点后自动执行一组命令(自动打印、继续等):

(gdb) break *0x401011
(gdb) commands 1              # 给 1 号断点挂命令
> silent                     # 命中时不打印提示
> printf "rcx = %ld\n", $rcx
> continue
> end
(gdb) run                    # 之后每次命中自动打印并继续

一次性断点与忽略计数:

(gdb) tbreak *0x40100e       # 命中一次后自动删除
(gdb) break *0x40100e
(gdb) ignore 1 2             # 1 号断点前 2 次命中忽略(第 3 次才停)

信号控制

默认崩溃信号(SIGSEGV 等)会让 GDB 停在现场:

(gdb) handle SIGSEGV stop print pass   # 停住 + 打印 + 继续传给程序(默认)
(gdb) handle SIGSEGV nopass            # 吞掉信号不让程序接收
(gdb) handle SIGSEGV nostop            # 不停,仅记录
(gdb) info signals SIGSEGV             # 查看当前设置

汇编调试常用姿势:什么都不设,直接 run 等崩溃停在现场,然后 x/i $pc 看是哪个指令炸的。

Attach 到运行中的进程

gdb -p PID                  # 附加到正在运行的进程(或 gdb ./prog PID)

调试无法交互启动的守护进程、挂死的程序时有用。注意 Linux 的 yama 限制(同用户附加也可能被拒):

Could not attach to process. ... check the setting of
/proc/sys/kernel/yama/ptrace_scope ...

被拒时:root 附加,或 sysctl -w kernel.yama.ptrace_scope=0,或用 gdb ./prog + run 方式调试(子进程场景可用 set follow-fork-mode child)。

多线程调试

(gdb) info threads                    # 列出所有线程,* 为当前线程
(gdb) thread 2                        # 切换到 2 号线程
(gdb) set scheduler-locking on        # 单步时只跑当前线程,其他线程冻结
(gdb) thread apply all bt             # 打印所有线程的调用栈

调试自旋锁/无锁结构(模块十三)时,scheduler-locking on 是必备——否则 stepi 之间其他线程会"偷跑"。

栈帧深入

(gdb) bt                             # 完整调用栈
#0  0x000000000040101c in worker ()
#1  0x000000000040100c in _start ()
(gdb) info frame                     # 当前帧详情(rip/rbp/rsp、saved rip 位置)
(gdb) frame 1                        # 切到 1 号帧(调用者)
(gdb) up / down                      # 相邻帧切换
(gdb) x/gx $rbp+8                    # 看本帧保存的返回地址(帧指针布局见模块六)
(gdb) x/16gx $rsp                    # 直接看原始栈内容

脚本与自动化

命令文件(-x):

printf 'break _start\nrun\nsi 3\np $rax\nquit\n' > auto.gdb
gdb -batch -x auto.gdb ./prog

自定义命令:

(gdb) define showpc
> x/i $pc
> p/x $pc
> end
(gdb) showpc
=> 0x401000 <_start>:  mov    $0x0,%rcx
$1 = 0x401000

~/.gdbinit 常用配置(自动加载):

set disassembly-flavor att          # 保持 AT&T 语法
set disassemble-next-line on        # 每步显示下一条指令
set history save on

便利变量与条件循环(批量脚本):

set $i = 0
while $i < 8
  x/1i $pc
  si
  set $i = $i + 1
end

内存转储与恢复

(gdb) dump memory /tmp/mem.bin $rsp $rsp+16    # 导出 16 字节内存
(gdb) restore /tmp/mem.bin binary $rsp         # 恢复

远程调试(gdbserver)

跨机器/嵌入式场景,目标机运行 gdbserver :1234 ./prog,本机 target remote 主机:1234。需先安装 gdbserver 包;本机调本机一般用不着。

汇编调试工作流清单

  1. 能复现 → gdb ./progrun 直接到崩溃现场。
  2. 崩溃指令不明 → x/i $pc + info registers,对照模块十七检查操作数。
  3. 寄存器值被莫名修改 → watch $rcx 或反向调试 reverse-stepi
  4. 内存被踩 → awatch *(long*)&var
  5. 循环逻辑错 → 条件断点 break *addr if $rcx == N
  6. 无头环境 → core dump 或 gdb -batch -x script.gdb
  7. 常见报错对照模块十八排除汇编/链接问题。