x64汇编之用调试器进行程序分析:GDB

大家好,你们可以叫我凌,是个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 中,直接按回车键会重复执行上一条命令。这对连续单步非常有用:

  1. 输入一次 stepi

  2. 然后反复按回车,就会一直单步执行,每步都会触发 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)?

单步执行并观察变化

  1. 接上面练习,连续执行 stepi 7 次(执行完 mov rdx, 6)。

  2. 每执行一次 stepi 后,查看 rax、rdi、rsi、rdx 的值,记录它们何时发生变化。

  3. 验证是否与代码中的赋值一致。

查看内存内容

  1. 在第4条指令 mov rsi, msg 执行后,执行 x/s &msg,查看输出是否为 "Hello\n"。

  2. 执行 x/6xb &msg,记录每个字节的十六进制值,对照 ASCII 表验证。

  3. 尝试用 x/6db &msg 查看十进制格式。

修改寄存器的值

  1. 在执行 mov rdx, 6 之后,手动修改 rdx 的值:set $rdx = 3。

  2. 继续执行到 syscall,观察屏幕上输出什么(应该是 "Hel" 而不是 "Hello")。

  3. 恢复原值或重新运行。

相关推荐
是星辰吖~4 小时前
X86反汇编_深度学习:从 C 指针到汇编逻辑
汇编
iCxhust20 小时前
c#多串口重量采集上位机程序
开发语言·汇编·c#·微机原理·8088单板机
AI科技星1 天前
万有引力G与真空介电常数ε0全维度完整关系式汇编(基于v=c螺旋时空理论)
c语言·开发语言·前端·javascript·网络·汇编·electron
技术不好的崎鸣同学1 天前
x64汇编之GDB进阶与printf
汇编
是星辰吖~2 天前
X86反汇编:深度学习阶段_2
汇编
程序喵大人2 天前
从内存/汇编角度看C与C++:指针、引用、对象的底层差异
c语言·汇编·c++·指针·引用·对象
是星辰吖~2 天前
X86反汇编_深度学习阶段_1
汇编
say_fall2 天前
输入输出技术_接口到中断完全指南
汇编·微机原理·8086
Dovis(誓平步青云)2 天前
《QT学习第四篇:常见事件与UDP、TCP、文件系统、(锁、信号量、条件变量》
c语言·开发语言·汇编·qt