大家好,你们可以叫我凌,是个16岁的网络安全学习者。
今天,我们来讲讲汇编当中经常会用到的东西------GDB调试器。它将会是以后内容当中的常客,特别地重要。那么废话不多说,我们直接开始吧!
调试器 GDB 的作用
我们知道汇编语言直接操作寄存器和内存,没有变量名、没有函数调用栈的自动记录,一旦出错,往往只能看到一个莫名其妙的段错误。GDB 就像一台"显微镜",可以实现:
单步执行每一条汇编指令
实时查看所有寄存器的值变化
观察内存中数据的原始字节
没有 GDB,我们只能靠猜测和 " echo $? " 验证退出码。有了它,CPU 的每一步工作都变得透明,调试效率提升数倍。对于汇编学习者来说,GDB 是必不可少的工具。
准备工作
在开始使用 GDB 调试汇编程序之前,需要确保环境就绪,并准备好一个最简单的示例程序。
安装 GDB
如果你的系统中还没有 GDB,在 Ubuntu/Debian 下执行:
sudo apt update
sudo apt install gdb -y
安装后可以用 gdb --version 验证。
gdb --version
确保像这样子显示版本号就可以了

准备汇编程序
使用vim或其他同等工具编写以下内容,以便于后续观察寄存器的变化。
cpp
; hello.asm
section .data
msg db "Hello", 10 ; 10 是换行符,共6字节
section .text
global _start
_start:
mov rax, 1 ; sys_write
mov rdi, 1 ; stdout
mov rsi, msg ; 字符串地址
mov rdx, 6 ; 长度
syscall
mov rax, 60 ; sys_exit
mov rdi, 0
syscall

编译并生成可执行文件
nasm -f elf64 exit.asm -o exit.o
ld exit.o -o exit
注意:NASM 默认不生成额外的调试信息,但 GDB 仍然可以对符号 "_start" 设置断点并单步执行。如果想获得更详细的调试信息,可以在 nasm 命令中加入 -g 选项(但不强制)。
最后,就是这个样子:


启动 GDB 并加载程序
gdb ./exit
如果看到类似 (gdb) 的提示符,说明已经成功进入调试环境。接下来就可以设置断点、运行和单步执行了。

提示:在 GDB 中,可以随时输入 "quit" 退出。
GDB 基本命令速查表
在开始调试之前,我们先熟悉最常用的几个命令。
|----------------|----------|----------------------------|
| 命令 | 简写 | 作用 |
| gdb ./程序 | 无 | 启动 GDB 并加载指定程序 |
| break _start | b _start | 在 _start 处设置断点 |
| run | r | 运行程序,直到遇到断点或结束 |
| stepi | si | 单步执行一条汇编指令 |
| info registers | i r | 查看所有寄存器的当前值 |
| x/s 地址 | 无 | 以字符串形式查看内存(如 x/s &msg) |
| x/6xb 地址 | 无 | 以十六进制字节查看内存(如 x/6xb &msg) |
| quit | q | 退出 GDB |
在 GDB 中,大部分命令可以缩写(如 b 代替 break)。直接按回车会重复上一条命令,非常适合连续执行 stepi。
调试示例:hello.asm 的执行过程
用准备好的 hello.asm 程序,完整走一遍 GDB 调试流程。可以边看边在虚拟机上操作。
启动 GDB 并设置断点
gdb ./hello

在 (gdb) 提示符后输入:
break _start
输出类似:
Breakpoint 1 at 0x4000b0
这表示已经在 _start 的地址处设置了断点。

运行程序
run
程序开始运行,但会停在断点处,显示:
Starting program: /home/yourname/hello
Breakpoint 1, 0x00000000004000b0 in _start ()

单步执行并观察寄存器
输入 stepi(或 si)执行第一条指令 mov rax, 1,然后用 info registers(或 i r)查看寄存器:
stepi
info registers
可以看到 rax 变成了 0x1,其他寄存器不变。

继续 stepi,执行 mov rdi, 1,再 info registers,此时 rdi 变为 0x1。

重复上述过程,直到执行完 mov rsi, msg 和 mov rdx, 6。注意观察 rsi 和 rdx 的变化。
查看内存中的字符串
在执行 syscall 之前,我们想确认 msg 地址处存放的是不是 "Hello\n"。
首先查看 msg 的地址:
info address msg
输出类似 "Symbol "msg" is at 0x402000 in a file compiled without debugging."

然后以字符串形式查看该地址的内容:
x/s 0x402000

或者直接用标签名(GDB 支持):
x/s &msg
输出应为:"Hello\n"。

如果想看原始字节:
x/6xb &msg
输出类似:
0x402000: 0x48 0x65 0x6c 0x6c 0x6f 0x0a
其中 0x48 是 'H',0x65 是 'e',...... 0x0a 是换行符 \n。

执行 syscall 并观察输出
继续 stepi 执行 syscall。此时屏幕上会打印出 Hello 并换行。然后执行退出部分。
最后两条指令 mov rax, 60 和 mov rdi, 0 以及 syscall 可以用 stepi 逐一执行,观察 rax 和 rdi 的变化。
当执行到最后的 syscall 后,程序正常退出,GDB 会显示:
Inferior 1 (process 1234) exited normally

退出 GDB
quit

常用 GDB 技巧
在实际调试中,有些小技巧能大大提高效率,避免重复输入命令。
自动显示下条指令
每次手动输入 "x/i $rip" 或 "info registers" 确实麻烦。GDB 提供了 "display" 命令,可以在每次停顿时自动显示你关心的信息。
display/i $rip ; 每次都显示下一条要执行的指令
display/x $rax ; 自动显示 rax 的十六进制值
display/x $rdi ; 自动显示 rdi 的十六进制值
设置好之后,每当你执行 stepi 或 run 遇到断点时,GDB 会自动打印这些信息。你可以根据需要添加更多寄存器。
如果想查看已设置的 display 列表,用 info display。删除某个可以用 "undisplay 编号"。
重复执行上一条命令
在 GDB 中,直接按回车键会重复执行上一条命令。这对连续单步非常有用:
-
输入一次 stepi
-
然后反复按回车,就会一直单步执行,每步都会触发 display 显示信息
这样你只需专注观察输出,不必反复输入 si。
查看内存的多种格式
除了字符串和十六进制字节,GDB 还支持多种格式:
|-----|---------------|
| 格式 | 说明 |
| x/s | 字符串(以 \0 结尾) |
| x/x | 十六进制 |
| x/d | 十进制 |
| x/t | 二进制 |
| x/i | 反汇编指令 |
可以指定显示单位:b(字节)、h(半字,2字节)、w(字,4字节)、g(巨字,8字节)。
例如,以十进制显示 msg 的前 6 个字节:
x/6db &msg
查看断点信息
使用 info breakpoints(或 i b)可以列出所有已设置的断点。删除断点用 delete 断点编号。
退出 GDB
quit
或者按 Ctrl+D。
你的观察非常到位!`info` 就是"查看"的意思,类似的规律还有很多。我们先把"动手练习"放一放,先整理一个**GDB 指令速记与理解**的小节,让你能快速记住常用命令。
GDB 指令速记与理解
GDB 的命令大多采用英文单词或缩写,理解其含义后就不需要死记硬背。
核心命令速记表
|-------|------------------|----------|
| 命令 | 英文全称 | 含义 |
| break | break | 设置断点 |
| run | run | 运行程序 |
| stepi | step instruction | 单步执行一条指令 |
| info | information | 查看信息 |
| x | examine | 查看内存 |
| quit | quit | 退出 |
info 的常用组合
既然已经知道 info 是"查看",那么后面跟不同对象就能查看不同内容:
|------------------|--------------|
| 命令 | 查看内容 |
| info registers | 所有寄存器的值 |
| info breakpoints | 所有断点 |
| info address 标签 | 某个标签(如 g)的地址 |
| info display | 当前自动显示列表 |
规律:info + 名词 = 查看该名词的信息。
x 命令的格式
x 是 examine 的缩写,格式为 x/数量格式 地址。其中以下为各个参数讲解:
- 数量: 显示几个单位(默认1)
- 格式:
s 字符串
x 十六进制
d 十进制
t 二进制
i 指令(反汇编)
- 单位(可选):
b 字节
h 半字(2字节)
w 字(4字节)
g 巨字(8字节)
示例:
x/s &msg :看字符串
x/6xb &msg :看6个十六进制字节
x/4dw &msg :看4个十进制双字
x 就像 "X光机" ,能照出内存里的内容。
其他常用缩写
GDB 允许使用明确的首字母缩写,非常方便:
|------------------|-----|----------|
| 全称 | 缩写 | 示例 |
| break | b | b _start |
| run | r | r |
| stepi | si | si |
| info registers | i r | i r |
| info breakpoints | i b | i b |
| quit | q | q |
规律:多数命令取第一个单词的首字母,info 简写为 i,后面跟对象首字母(如 r 代表 registers)。
练习巩固
观察寄存器的初始值
启动 GDB 加载 hello 程序:
gdb ./hello

在 _start 设置断点并运行:
break _start
run
执行 info registers,记录此时各寄存器的值(尤其是 rax、rdi、rsi、rdx 和 rip)。

思考:为什么这些寄存器一开始都是 0(除了 rip 和 rsp)?
单步执行并观察变化
-
接上面练习,连续执行 stepi 7 次(执行完 mov rdx, 6)。
-
每执行一次 stepi 后,查看 rax、rdi、rsi、rdx 的值,记录它们何时发生变化。
-
验证是否与代码中的赋值一致。

查看内存内容
-
在第4条指令 mov rsi, msg 执行后,执行 x/s &msg,查看输出是否为 "Hello\n"。
-
执行 x/6xb &msg,记录每个字节的十六进制值,对照 ASCII 表验证。
-
尝试用 x/6db &msg 查看十进制格式。

修改寄存器的值
-
在执行 mov rdx, 6 之后,手动修改 rdx 的值:set $rdx = 3。
-
继续执行到 syscall,观察屏幕上输出什么(应该是 "Hel" 而不是 "Hello")。
-
恢复原值或重新运行。
