家好,你们可以叫我凌,是个16岁的网络安全学习者。
经过前面几篇"杂七杂八"的内容,我们是时候也应该开始正式写写代码了!
不过,前面"八宝粥"式的内容我相信肯定有许多人满头雾水(比如为什么 rax=1 是写,rax=60 是退出)。所以,我将再写两篇"复习篇",帮助各位将前面的知识点彻底梳理起来,为后续动手实战铺平道路。
本篇为上篇,重点复习内存、寄存器、系统调用和GDB调试速查。读完你会掌握一个汇编程序的最小骨架,并能用调试器看清它的每一步。
内存与数据表示(极简回顾 + 技巧)
原文链接1:
x64汇编之内存工作的基本原理
https://blog.csdn.net/2401_88251163/article/details/159761650
原文链接2:
x64汇编之进制讲解和寄存器
https://blog.csdn.net/2401_88251163/article/details/160631471
计算机内部只存储 0 和 1,但我们需要用更友好的方式表示它们。
二进制 ↔ 十六进制 ↔ 十进制
|------|----|------|------------|
| 进制 | 基数 | 示例 | 用途 |
| 二进制 | 2 | 1011 | 计算机内部存储 |
| 十六进制 | 16 | 0xB | 缩短二进制,便于阅读 |
| 十进制 | 10 | 11 | 人类习惯 |
快速手动转换技巧:
- 二进制 → 十六进制:从右向左每4位一组,每组转成一位十六进制。
例如 11010110 → 1101 0110 → D6。
- 十六进制 → 二进制:每一位十六进制写成4位二进制,去掉前导零。
例如 0xA3 → 1010 0011。
- 十六进制 ↔ 十进制:记住 0xA=10、0xF=15,按权展开即可。
常用值:0xFF = 255,0x100 = 256。
小技巧:一个字节(8位)的最大值 0xFF 是255,在调试内存时看到 0x7F = 127,0x80 = 128(也是 -128 补码)。
有符号整数:补码
计算机无法存储"负号",负数通过补码表示。
核心规律(8 位示例):
-
11111111 = -1
-
10000000 = -128
-
01111111 = 127(最大正数)
速算技巧:对于负数补码,直接看十六进制:0xFF = -1,0xFE = -2,0x80 = -128。即 0x100 - 该数绝对值 就是补码。
浮点数(仅提及)
浮点数用于小数或极大数,采用 IEEE 754 标准,分为符号、指数、尾数三部分。例如单精度 32 位:1 位符号 + 8 位指数 + 23 位尾数。
> 现在只需知道浮点数的存在,暂不涉及具体转换。
x64 寄存器速查表
原文链接:
x64汇编之进制讲解和寄存器
https://blog.csdn.net/2401_88251163/article/details/160631471
寄存器是 CPU 内部的"高速便签纸"。以下是最常用的几个:
|--------|-----|-----|-------|------------------|
| 64位 | 32位 | 16位 | 8位(低) | 常见用途 |
| rax | eax | ax | al | 系统调用号 / 返回值 |
| rdi | edi | di | dil | 系统调用第1参数 / 目的指针 |
| rsi | esi | si | sil | 系统调用第2参数 / 源指针 |
| rdx | edx | dx | dl | 系统调用第3参数 / 乘除法辅助 |
| rsp | esp | sp | spl | 栈顶指针(仅64位常用) |
| rbp | ebp | bp | bpl | 栈帧基址 |
| rip | (无) ||| 指令指针 |
| eflags | (无) ||| 标志寄存器 |
子寄存器关系:修改 al 会影响 ax 和 eax 和 rax 的低8位;修改 eax 会清零 rax 的高32位。
小技巧:调试时,用 info registers 看到 rax 很大,可能实际只需要它的低32位,可以单独看 eax。例如 info registers eax。
记忆技巧:大多数寄存器都是由16位寄存器发展过来了,只需要记住16位寄存器其他大部分寄存器就可以快速记忆
汇编程序的最小骨架
原文链接:
x64汇编之从程序编辑到系统调用
https://blog.csdn.net/2401_88251163/article/details/160027656
一个最简单的汇编程序只需要 .text 段、_start 标签和退出系统调用。
程序结构
cpp
section .text ; 代码段
global _start ; 告诉链接器程序的入口
_start: ; 入口标签
; 这里写指令
最简退出程序(完整可运行)
cpp
; exit.asm
section .text
global _start
_start:
mov rax, 60 ; 系统调用号 60 = exit
mov rdi, 0 ; 退出码 0 = 成功
syscall
编译链接运行:
nasm -f elf64 exit.asm -o exit.o
ld exit.o -o exit
./exit
echo ? ; 输出 0 小技巧:如果想返回非0值,将 mov rdi, 0 改为 mov rdi, 42,然后 echo ? 会显示 42。
系统调用核心(write&exit)
原文链接:
x64汇编之从程序编辑到系统调用
https://blog.csdn.net/2401_88251163/article/details/160027656系统调用是用户程序请求内核服务的接口。在 x64 Linux 中,通过 syscall 指令触发,参数通过特定寄存器传递。
系统调用号
|-----------|--------|------------|
| 系统调用 | 号(rax) | 功能 |
| sys_write | 1 | 向文件描述符写入数据 |
| sys_exit | 60 | 退出进程 |
参数传递
sys_write:
rdi:文件描述符(1 = 标准输出,即屏幕)
rsi:要写入的数据地址
rdx:数据长度(字节数)
sys_exit:
- rdi:退出码(0 = 成功,非 0 = 错误)
完整输出示例(Hello 加换行)
cpp
; hello.asm
section .data
msg db "Hello", 10 ; 10 是换行符 ASCII
len equ $ - msg ; 自动计算长度(6)
section .text
global _start
_start:
mov rax, 1 ; sys_write
mov rdi, 1 ; stdout
mov rsi, msg ; 字符串地址
mov rdx, len ; 长度
syscall
mov rax, 60 ; sys_exit
mov rdi, 0
syscall
编译链接运行:
nasm -f elf64 hello.asm -o hello.o
ld hello.o -o hello
./hello
输出:
Hello
小技巧:如果想要不换行,去掉 10 并将长度改为 5。
常见误区澄清
|----------------|----------------------------------------------------|
| 错误理解 | 正确理解 |
| rax=1 表示成功 | rax=1 是系统调用号,代表"写入";成功退出是 rax=60 + rdi=0 |
| rdi=1 永远表示标准输出 | 仅在 sys_write 中;在 sys_exit 中 rdi=1 表示退出码 1(错误) |
| 字符串长度不包括换行符 | 如果希望输出换行,必须在字符串中包含 10,长度也要算上它 |
| 中括号 \[\] 可以省略 | mov rsi, msg 得到地址;mov al, msg 得到该地址的第一个字节。切勿混淆 |
GDB 调试速查
原文链接1:
x64汇编之用调试器进行程序分析:GDB
https://blog.csdn.net/2401_88251163/article/details/161540677原文链接2:
x64汇编之GDB进阶与printf
https://blog.csdn.net/2401_88251163/article/details/161548434调试是理解汇编执行过程的最佳方式。以下命令足以应付初期调试。
启动与断点
gdb ./程序 # 启动 GDB 并加载程序
(gdb) break _start # 在 _start 处设置断点(可简写 b _start)
(gdb) run # 运行程序,停在断点(可简写 r)
单步执行
(gdb) stepi # 执行一条指令(可简写 si)
按回车键会重复上条命令,方便连续单步。
小技巧:设置 display/i rip 可以每次单步后自动显示下一条指令,避免反复输入 x/i rip。
查看信息
寄存器:info registers(简写 i r)
- 只查看特定寄存器:i r rax rdi
内存:
-
字符串:x/s &msg
-
十六进制字节:x/6xb &msg(b 表示字节,6 表示数量)
-
十进制字节:x/6db &msg
-
指令:x/i $rip
小技巧:x 命令格式为 x/重复次数格式单位 地址。常用格式:s(字符串)、x(十六进制)、d(十进制)、i(指令)。常用单位:b(字节)、h(半字)、w(字)、g(巨字)。
自动显示(display)
(gdb) display/i $rip # 每次停下自动显示下一条指令
(gdb) display/x $rax # 自动显示 rax 的十六进制值
设置后,每次 stepi 或 run 遇到断点都会自动打印这些信息。用 info display 查看列表,undisplay 编号 取消。
函数调用栈(了解)
-
backtrace(简写 bt)显示调用栈
-
frame 1(简写 f 1)切换到上层栈帧
-
disas 反汇编当前函数
监视变量变化
汇编变量没有类型信息,需要强制转换:
watch (unsigned long)count
watch *(unsigned long *)&count
小技巧:如果变量是 db(1字节),用 (unsigned char);dw 用 (unsigned short);dd 用 (unsigned int);dq 用 (unsigned long)。
内容小结
本篇复习了:
-
内存中数据的二进制/十六进制表示、补码和手动转换技巧
-
x64 常用寄存器及其子寄存器
-
一个汇编程序的最小骨架(_start + sys_exit)
-
系统调用 sys_write 和 sys_exit 的用法与常见误区(含中括号作用)
-
GDB 调试的核心命令、display 自动显示、x 命令格式、info 用法