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=1,reverse-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 包;本机调本机一般用不着。