本文部分段落借助 AI 辅助整理,主体思路、示例、表述为作者原创。
后续会逐步重写,统一风格,敬请期待。
前言
上一章
我们成功的跑起来了第一个汇编写的Hello World
接下来我们讲汇编最常用的东西——寄存器
寄存器为何物?
你可以把它想象成高级语言的变量
但是严格来讲,寄存器只是存储数据的地方
x64 架构一共有 16 个这样的通用寄存器,分别是:
RAX RBX RCX RDX
RSI RDI RBP RSP
R8 R9 R10 R11
R12 R13 R14 R15
这些不需要全背下来,真正高频使用的就那么几个,忘了随时可以查。
关于寄存器的前缀和后缀
接下来肯定会有人这么想:这些 RAX、RBX 的名字乱七八糟的,肯定是乱起的吧?
那可不一定,这些前缀和后缀都是有意义的。
R 开头(如 RAX、RBX)代表 64 位寄存器,可以存储 64 位数据;
E 前缀(如 EAX)代表 32 位,刚好是 64 的一半;
没有前缀的 AX 就是 16 位,也就是 64 的四分之一。
后缀 L 代表寄存器的前 8 位,后缀 H 代表寄存器的后 8 位(第 8~15 位)。
那 X 是怎么来的呢?8086 从 8 位升级到 16 位时,原来的 A/B/C/D 被拆成高 8 位(H)和低 8 位(L), 需要一个新名字表示"整体 16 位"。设计师选了 X,类似代数里用 X 表示一个未知数/整体, 没有特别深的含义,就是"需要一个字母把 H 和 L 组合起来"。
寄存器的别名
既然都讲到E代表 32位的意思了那么RAX是不是也可以写成EAX?
没错!这就是代表在RAX的前32位的意思!
那么以此类推,就有了 AX、AL、AH 这些名字。
这里肯定有人要问了:为什么 AL 前面不能加 R?AX 又是什么东西?
其实 AX 就是 RAX 的低 16 位
你看R代表64位数
而E代表32 刚好就是64/2
那么没有带R或者E的AX就是64/4啦也就是16位数
我们还可以写这样的公式
RAX 位宽 = EAX 位宽 × 2 = AX 位宽 × 4至于为什么 AL 前面不能加 R
因为 R 前缀只代表 64 位,而 AL 只有 8 位,所以不能叫 RAL。同理,AH 也只有 8 位,也不能叫 RAH。
简单来说:R 前缀 = 64 位,E 前缀 = 32 位,没有前缀 = 16 位,L/H 后缀 = 8 位。每个名字对应不同的位宽。
那么我们不难发现RAX/ EAX/ AX/ AL/ AH这些唯一没有变的是A
同样的RBX不变的是B, RCX不变的是C, R8不变的8
那么RSI不变的是S吗答案并不是,而是SI,那么为什么不是RSIX
这就要讲到x86寄存器命名的历史了
起初,1972年, Intel 8008(八位处理器)只有7个八位寄存器
A, B, C, D, E, H, L(H, L被使用存放内存地址和高位,低位字节)
后来到了1978年, Intel 推出了8086(16位处理器)
为了兼容那 7 个8位处理器,把 16 位寄存器逻辑上对半拆成两个 8 位,这也是为什么RAX可以被拆分
当时 Intel 的设计师 Steve Morse 回忆说,X 只是一个任意选的字母,用来表示"AX 是 AH 和 AL 的组合",就像代数里用 X 代表未知数一样,没多想。
而 SI、DI、BP、SP 这四个寄存器从诞生起就是完整的 16 位,没有拆分成高低 8 位的用法,所以名字里不带 X。
X 不是"寄存器"的意思,而是"这个 16 位寄存器可以拆成两个 8 位"的标记。SI 从来没拆过,所以一直是 SI,没有 SIX。
寄存器的用法
好了现在是不是对寄存器有一点初步的认识了呢?
这里再补充一点
这是RSI的S是 Source (源) 的意思, I代表索引
D是目的的意思DI就是目的索引寄存器的意思
P是指针的意思RBP和RSP都是指针寄存器,他们用来存储内存地址,而BP是基址指针(base pointer)的意思
既然RBP是存放指针的寄存器那么其他都是什么用处呢?
RAX — 累加器(Accumulator)
先说 RAX,这是你以后打交道最多的寄存器之一。它主要干两件事:
- 算术运算:加减乘除的结果默认就往 RAX 里放。
- 函数返回值:调用一个函数后,返回值就在 RAX 里等着你去取。
另外,RAX 经常和 RDX 搭档。做乘法时如果结果太大,高位会溢出到 RDX 里;做除法时 RDX 放余数。所以这俩经常成对出现。
RBX — 基址寄存器(Base Register)
RBX 比较特别,它是一个非易失寄存器。什么意思呢?就是如果你在函数里改了 RBX 的值,函数返回前必须把它恢复成原来的样子,否则调用者会炸。
所以 RBX 适合放那些"我不想被别人改掉"的数据——有点像高级语言里的常量,一旦赋值就别乱动了。
RCX — 计数器(Counter)
RCX 的名字来自 Counter(计数器)。在 x86 时代,它专门用来做循环计数,经典的 LOOP 指令每执行一次就把 RCX 减 1,减到 0 就跳出循环。
到了 x64 时代,RCX 多了一个身份:函数调用的第 1 个参数。所以现在它既可以做计数器,也可以传参数,身兼两职。
RDX — 数据寄存器(Data Register)
RDX 刚才在 RAX 那里已经露过脸了——它经常配合 RAX 处理"装不下"的数据。乘法高位、除法余数,都往 RDX 里塞。
另外在 x64 下,RDX 是函数调用的第 2 个参数。所以 RCX 和 RDX 经常一起出现,一个传第 1 个参数,一个传第 2 个。
RSI / RDI — 源索引 / 目的索引
这两个前面讲过了,简单回顾一下:
- RSI指向数据来源的地址,常用于字符串/内存拷贝操作。
- RDI指向数据写入的目标地址。
BTW:在 Linux x64 下,RSI 是函数调用的第 2 个参数(第 1 个是 RDI);但在 Windows x64 下,第 5 个及以后的参数走栈传递,不走寄存器。
R8 ~ R15 — 扩展寄存器
R8 到 R15 是 x64 新增的寄存器,x86 时代没有。它们没有历史包袱,用途比较灵活:
- R8、R9:Windows x64 下的第 3、第 4 个函数参数。
- R10、R11:临时寄存器,随便用,函数调用后值可能被覆盖。
- R12 ~ R15:非易失寄存器,和 RBX 一样,用了就得恢复原值。
名字也很直白,就是编号递增,没什么花里胡哨的历史含义。
易失寄存器 vs 非易失寄存器
讲完了每个寄存器 individually 的用处,现在来一个"大局观"的总结。
在 x64 的函数调用约定里,寄存器被分成两类:
- 易失寄存器(Volatile):函数调用后不保证值还在。也就是说,你调用了一个函数之后,这些寄存器里的值可能被覆盖掉,别指望它们还保留着。
- 非易失寄存器(Non-Volatile):函数调用后值必须保持不变。如果你在一个函数里改了这些寄存器,那函数返回之前必须把它们恢复成原来的值——就像借了别人的东西得还回去一样。
具体哪些属于哪类呢?
- 易失寄存器:RAX、RCX、RDX、R8、R9、R10、R11
- 非易失寄存器:RBX、RBP、RSI、RDI、R12、R13、R14、R15
简单记法:RAX 和 RCX、RDX 这些"干活"的寄存器是易失的,因为函数内部随便用;而 RBX、RBP 这些"存东西"的寄存器是非易失的,函数得负责保管好。
函数参数传递规则
前面讲 RCX、RDX、R8、R9 的时候,每次都提到"函数调用的第 N 个参数",这里统一说一下规则。
在 Windows x64 调用约定下,函数的前 4 个参数不是压栈传的,而是直接放寄存器里:
RCX - 第1个参数
RDX - 第2个参数
R8 - 第3个参数
R9 - 第4个参数
第 5 个及以后的参数才走栈(Stack)传递。
顺便提一嘴:Linux x64 的调用约定不太一样,前 6 个参数走寄存器(RDI、RSI、RDX、RCX、R8、R9),第 7 个才开始压栈。不过咱们先掌握 Windows 的就行,后面碰到了再补充。
RSP / RIP — 特殊寄存器
除了上面那些"普通"寄存器,还有两个比较特殊的,单独拎出来讲一下。
RSP(Stack Pointer,栈指针):永远指向栈顶。你调用函数、分配局部变量、压栈出栈,全靠它来维护栈的边界。这个寄存器你一般不需要手动去改它(除非你在写很底层的代码),但理解它的作用很重要——因为它决定了你的程序"现在用到多少内存"。
RIP(Instruction Pointer,指令指针):指向下一条要执行的指令的地址。简单说,CPU 就是靠 RIP 知道"下一步该去哪"。这个寄存器我们通常不能直接修改它(除非用 JMP、CALL 这类跳转指令),因为它是 CPU 自动维护的。
所以 RSP 管"内存栈",RIP 管"执行流程",这两个算是 CPU 的"基础设施"寄存器。
结语
如果你看到这里,第一章"寄存器"也就差不多结束了。
回顾一下:
- 寄存器是什么—— CPU 里面的"信箱",存取速度比内存快得多。
- 命名规则—— R 前缀 = 64 位,E = 32 位,没前缀 = 16 位,L/H = 8 位。
- 各寄存器的用途—— RAX 做运算和返回值,RBX 存常量,RCX 做计数器,RDX 配合 RAX,RSI/RDI 负责搬数据,R8~R15 是 x64 新加的扩展寄存器。
- 易失 vs 非易失—— 易失的(RAX、RCX、RDX、R8~R11)函数调用后值可能丢;非易失的(RBX、RBP、RSI、RDI、R12~R15)函数调用后必须保持原样。
- 函数参数传递—— Windows x64 下前 4 个参数走寄存器(RCX、RDX、R8、R9),第 5 个开始走栈。
- 特殊寄存器—— RSP 管栈顶,RIP 管下一条指令,这两个是 CPU 的"基础设施"。
不用急着全背下来,真正常用的就那么几个,写多了自然就熟了。汇编这东西跟学语言一样,看得多写得多了,手感自然就来了。
下一章我们会讲栈(Stack)和MOV 指令,到时候会结合具体的代码示例来演示这些寄存器是怎么配合工作的。栈这个东西理解了对调试和逆向特别有帮助,敬请期待!