汇编语言 - 寄存器
寄存器(Register)是 CPU 内部的高速存储单元,是汇编编程中最频繁操作的对象。理解寄存器是学好汇编语言的关键第一步。
什么是寄存器
寄存器是 CPU 芯片内部集成的 超高速小型存储器,用于暂存指令、数据和地址。
与内存不同,寄存器就嵌在 CPU 内部,CPU 访问寄存器几乎零延迟,而访问内存则需要几十到几百个时钟周期。
在汇编语言中,绝大多数运算都是围绕寄存器展开的——数据从内存加载到寄存器,在寄存器中完成运算,再将结果存回内存。
可以把寄存器理解为 CPU 的"工作台"。工作台上的工具随时可用,而内存则像是仓库,需要走过去取放。
x86 32 位寄存器分类
x86 32 位架构提供了多种类型的寄存器,各有不同的用途:
通用寄存器
通用寄存器(General Purpose Registers) 是最常用的寄存器,用于存放运算数据和临时结果。
x86 提供了 8 个 32 位通用寄存器:
| 32位 | 16位 | 低8位 | 高8位(低16位) | 主要用途 |
|---|---|---|---|---|
| EAX | AX | AL | AH | 累加器,存放函数返回值、算术运算结果 |
| EBX | BX | BL | BH | 基址寄存器,常用于存放内存基地址 |
| ECX | CX | CL | CH | 计数器,常用于循环计数和移位 |
| EDX | DX | DL | DH | 数据寄存器,存放乘除法的高位结果 |
| ESI | SI | SIL | - | 源变址寄存器,字符串操作的源地址 |
| EDI | DI | DIL | - | 目的变址寄存器,字符串操作的目标地址 |
| EBP | BP | BPL | - | 基址指针,指向当前栈帧的底部 |
| ESP | SP | SPL | - | 栈指针,始终指向栈顶 |
名称规律:E 前缀表示 Extended(扩展到 32 位),X 后缀表示可拆分为高低字节。
实例
; 演示寄存器的各部分访问
section .text
global _start
_start:
mov eax, 0x12345678 ; 完整的 32 位寄存器
; 此时:EAX = 0x12345678
; AX = 0x5678 (低 16 位)
; AH = 0x56 (高 8 位,指 AX 的高 8 位)
; AL = 0x78 (低 8 位)
mov ax, 0xAABB ; 修改 AX(低 16 位)
; 此时:EAX = 0x1234AABB (高 16 位保持不变!)
; AX = 0xAABB
; AL = 0xBB
mov al, 0xCC ; 修改 AL(最低 8 位)
; 此时:EAX = 0x1234AACC (只有低 8 位变了)
; AX = 0xAACC
; AL = 0xCC
mov eax, 1
mov ebx, 0
int 0x80
修改 32 位寄存器的低 16 位(如 AX)时,高 16 位保持不变。但将 32 位寄存器作为目标操作数时,会覆盖整个 32 位。这是初学者容易出错的地方。
段寄存器
段寄存器用于指定当前使用的内存段:
| 寄存器 | 名称 | 用途 |
|---|---|---|
| CS | 代码段寄存器 | 指向当前指令所在的段 |
| DS | 数据段寄存器 | 指向数据所在的段 |
| SS | 栈段寄存器 | 指向栈所在的段 |
| ES | 附加段寄存器 | 额外的数据段 |
| FS | 附加段寄存器 | 通用,常用于线程局部存储 |
| GS | 附加段寄存器 | 通用,常用于线程局部存储 |
在 32 位保护模式下编程时,操作系统已经设置好了段寄存器,你通常不需要手动修改它们。
指针和变址寄存器
这些寄存器主要用于访问内存,存放内存地址:
| 寄存器 | 全称 | 用途 |
|---|---|---|
| EIP | 指令指针(Instruction Pointer) | 指向 CPU 下一条要执行的指令地址(不可直接访问) |
| ESP | 栈指针(Stack Pointer) | 指向栈顶,PUSH/POP 指令自动调整它 |
| EBP | 基址指针(Base Pointer) | 指向当前函数栈帧的底部,用于访问函数参数和局部变量 |
| ESI | 源变址(Source Index) | 字符串/内存操作的源地址 |
| EDI | 目的变址(Destination Index) | 字符串/内存操作的目标地址 |
ESP 和 EBP 不能当作普通通用寄存器随意使用。ESP 指向栈顶,push/pop/call/ret 都会改变它;EBP 是访问函数参数的关键。随意修改它们会导致程序崩溃。
标志寄存器(EFLAGS)
EFLAGS 是一个 32 位寄存器,每个比特位代表一个状态标志(Flag)。
你不能直接读写整个 EFLAGS,但 CPU 会根据运算结果自动更新这些标志位,条件跳转指令则根据标志位决定是否跳转。
| 标志位 | 名称 | 含义 |
|---|---|---|
| CF | 进位标志(Carry Flag) | 无符号运算产生进位/借位时置 1 |
| PF | 奇偶标志(Parity Flag) | 结果低 8 位中 1 的个数为偶数时置 1 |
| AF | 辅助进位标志 | 低 4 位向高 4 位进位/借位时置 1 |
| ZF | 零标志(Zero Flag) | 运算结果为 0 时置 1 |
| SF | 符号标志(Sign Flag) | 运算结果为负数时置 1(等于结果的最高位) |
| OF | 溢出标志(Overflow Flag) | 有符号运算溢出时置 1 |
实例
; 演示运算对标志位的影响
section .text
global _start
_start:
mov eax, 10
sub eax, 10 ; 10 - 10 = 0
; ZF = 1(结果为零)
; SF = 0(结果非负)
; CF = 0(无借位)
mov eax, 0xFFFFFFFF
add eax, 1 ; 0xFFFFFFFF + 1 = 0x100000000(超出了 32 位)
; ZF = 1(32位结果为0)
; CF = 1(产生进位)
; OF = 0(有符号角度看无溢出)
mov eax, 1
mov ebx, 0
int 0x80
寄存器使用约定
在实际编程中,一些寄存器有约定俗成的用法——称为 调用约定(Calling Convention):
| 寄存器 | 调用约定中的用途 |
|---|---|
| EAX | 存放函数返回值 |
| ECX | 计数器(循环计数) |
| EDX | 存放除法的高位结果、扩展 EAX |
| EBX、ESI、EDI、EBP | 被调用函数必须保存和恢复(callee-saved) |
| EAX、ECX、EDX | 调用者负责保存(caller-saved) |
在编写自己的汇编程序时,不一定严格遵循调用约定。但如果你要和 C 语言混合编程,就必须遵守 cdecl 等调用约定。
