1) 启动与运行控制
启动
1 2 3
| gdb ./a.out gdb -q ./a.out gdb --args ./a.out a b c
|
运行/重启/退出
1 2 3 4 5 6
| run (r) # 运行 start # 在 main 前停住(自动下断) starti # 从入口第一条指令开始停(更底层) kill # 杀掉被调试进程 run # 重新 run(相当于重启) quit (q) # 退出 gdb
|
继续/单步
1 2 3 4
| next (n) # 下一行(不进函数) step (s) # 进入函数 finish # 跑到当前函数返回处 until # 跑到当前循环/当前函数内的某行之后
|
1 2 3
| ni # next instruction:下一条指令(不跟进 call) si # step instruction:下一条指令(会跟进 call) continue (c) # 继续跑到下个断点
|
2) 断点:下断、查看、管理、条件断点
常见下断方式
1 2 3 4 5
| break main # 按符号名 break *0x401234 # 按地址(pwn 常用) break file.c:123 # 按文件行号 tbreak main # 临时断点(命中一次自动删) rbreak ^foo # 正则断点(符号很多时好用)
|
条件断点
1 2
| break *0x401234 if $rax==0 break vuln if $rsp < 0x7fffffffe000
|
查看/删除/启用禁用
1 2 3 4 5
| info breakpoints (i b) delete 1 # 删除编号 1 的断点 disable 1 enable 1 clear # 清掉当前行断点(源码级)
|
3) 寄存器/调用栈/当前栈帧
看寄存器(EIP/RIP、ESP/RSP、EBP/RBP 等)
1 2 3 4
| info registers info registers rip rsp rbp # x64 info registers eip esp ebp # x86 p/x $rip # 打印 rip(十六进制)
|
调用栈(谁调用了谁)
1 2 3 4 5
| bt # backtrace bt full # 带局部变量(有符号时) frame 2 # 切到第 2 帧 info frame # 当前栈帧细节(保存了什么返回地址等) up / down # 上下移动栈帧
|
4) 反汇编与“下一条指令在哪里”
反汇编
1 2 3 4
| disassemble disassemble main disassemble /m main # 混合源码/汇编(有符号时) x/10i $rip # 从 rip 开始看 10 条指令(最常用)
|
切 Intel 语法(建议)
1 2
| set disassembly-flavor intel set disassemble-next-line on
|
5) 读内存:x 命令(pwn 必会)
x 的格式:
- 格式:
x(hex) d(dec) u``t``c``s(string) i(instruction)
- 单位:
b(1) h(2) w(4) g(8)
常用例子
1 2 3 4 5 6
| x/16gx $rsp # 以 8 字节为单位看栈(x64) x/32wx $esp # 以 4 字节为单位看栈(x86)
x/10i $rip # 从当前指令开始看 10 条 x/s 0x404080 # 把某地址当 C 字符串打印 x/32bx 0x404080 # 按字节看(适合看输入/patch)
|
理解栈布局的小技巧:栈向“低地址”增长,所以如果你希望“低地址在上”,那你读栈时就把地址从小到大理解为从上到下;x/..输出通常是地址递增排列。
6) 打印与计算:p / set / printf(调试表达式)
p:打印变量/表达式
1 2 3 4
| p var p/x var p/x $rsp p (char*)0x404080
|
printf:按格式打印(调试输出很爽)
1
| printf "rsp=%p rip=%p\n", $rsp, $rip
|
set:改寄存器/改内存(动调 patch)
1 2 3 4 5 6
| set $rip = 0x401000 # 改控制流(演示用途) set $rax = 0
set {int}0x404080 = 0xdeadbeef # 往内存写 4 字节 set {long}0x404080 = 0x1122334455667788 # 写 8 字节 set *(int*)0x404080 = 123
|
7) 监视点(watchpoint):变量/内存被谁改了
当你想知道“哪个指令改了某个值”:
1 2 3 4
| watch *(int*)0x404080 # 写触发 rwatch *(int*)0x404080 # 读触发 awatch *(int*)0x404080 # 读写都触发 info watchpoints
|
8) 断点命中时自动执行命令(很实用)
例如每次停住都自动显示寄存器和栈顶:
1 2 3 4 5 6 7
| commands 1 silent printf "RIP=%p RSP=%p RBP=%p\n", $rip, $rsp, $rbp x/8i $rip x/16gx $rsp continue end
|
9) 多线程/多进程、attach、跟 fork(实战常见)
attach 到正在跑的进程
线程
fork/exec(调试子进程很常用)
1 2 3 4
| set follow-fork-mode child set detach-on-fork off catch fork catch exec
|
10) 建议的基础配置(进 gdb 先敲这几句)
1 2 3
| set pagination off set disassembly-flavor intel set print pretty on
|
11) 一个最小“函数调用过程”动调观察模板(看 esp/ebp/eip 或 rsp/rbp/rip)
假设你停在 call foo 附近:
- 看当前下一条指令与栈顶:
1 2 3
| x/6i $rip x/16gx $rsp # x64;x86 用 x/32wx $esp info reg rip rsp rbp
|
si 进入 call(会跳进 foo),观察:
1 2 3
| si info reg rip rsp rbp x/16gx $rsp
|
- 看到
push rbp; mov rbp,rsp; sub rsp,xx 后,再看:
rbp 会变成“新栈帧基址”
rsp 会向低地址移动(分配局部变量空间)
- 栈上通常能看到“返回地址”“保存的 rbp”