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 # 跑到当前循环/当前函数内的某行之后
  • 指令级(pwn 最常用)
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 的格式:

1
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 到正在跑的进程

1
2
attach <pid>
detach

线程

1
2
info threads
thread 2

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. 看当前下一条指令与栈顶:
1
2
3
x/6i  $rip
x/16gx $rsp # x64;x86 用 x/32wx $esp
info reg rip rsp rbp
  1. si 进入 call(会跳进 foo),观察:
1
2
3
si
info reg rip rsp rbp
x/16gx $rsp
  1. 看到 push rbp; mov rbp,rsp; sub rsp,xx 后,再看:
  • rbp 会变成“新栈帧基址”
  • rsp 会向低地址移动(分配局部变量空间)
  • 栈上通常能看到“返回地址”“保存的 rbp”