ⓘ(待修订版)
本文部分段落借助 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 经常和 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 — 源索引 / 目的索引



这两个前面讲过了,简单回顾一下:

BTW:在 Linux x64 下,RSI 是函数调用的第 2 个参数(第 1 个是 RDI);但在 Windows x64 下,第 5 个及以后的参数走栈传递,不走寄存器。


R8 ~ R15 — 扩展寄存器



R8 到 R15 是 x64 新增的寄存器,x86 时代没有。它们没有历史包袱,用途比较灵活:

名字也很直白,就是编号递增,没什么花里胡哨的历史含义。


易失寄存器 vs 非易失寄存器



讲完了每个寄存器 individually 的用处,现在来一个"大局观"的总结。

在 x64 的函数调用约定里,寄存器被分成两类:

具体哪些属于哪类呢?

简单记法: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 的"基础设施"寄存器。


结语



如果你看到这里,第一章"寄存器"也就差不多结束了。

回顾一下:

不用急着全背下来,真正常用的就那么几个,写多了自然就熟了。汇编这东西跟学语言一样,看得多写得多了,手感自然就来了。


下一章我们会讲栈(Stack)和MOV 指令,到时候会结合具体的代码示例来演示这些寄存器是怎么配合工作的。栈这个东西理解了对调试和逆向特别有帮助,敬请期待!



参考资料



  1. 知乎 | Windows x64寄存器介绍 作者: 知乎@十月份

目录