内存寻址模式
内存寻址模式是指令访问内存数据的方式。x86 架构提供了丰富灵活的寻址模式,这是 CISC 架构的重要特点之一。
寻址模式概述
x86 支持以下主要寻址模式:
- 立即寻址:操作数是立即数
- 寄存器寻址:操作数在寄存器中
- 直接寻址:直接指定内存地址
- 寄存器间接寻址:通过寄存器中的地址访问内存
- 寄存器相对寻址:寄存器地址加偏移量
- 变址寻址:基址寄存器加变址寄存器
- 比例变址寻址:支持比例因子
立即寻址
立即寻址的操作数直接包含在指令中:
mov eax, 100 ; 将立即数 100 加载到 EAX
mov rax, 0x12345678 ; 将十六进制数加载到 RAX
mov al, 'A' ; 将字符 'A' 的 ASCII 码加载到 AL
立即数的大小限制:
; 8 位立即数
mov al, 0xFF ; 正确
; 16 位立即数
mov ax, 0xFFFF ; 正确
; 32 位立即数
mov eax, 0xFFFFFFFF ; 正确
; 64 位立即数(需要特殊处理)
mov rax, 0x123456789ABCDEF0 ; 正确,但会被编码为多条指令
mov rax, qword [address] ; 从内存加载 64 位值
寄存器寻址
操作数存储在寄存器中:
mov eax, ebx ; 将 EBX 的值复制到 EAX
add rax, rbx ; RAX = RAX + RBX
xor ecx, ecx ; 将 ECX 清零
寄存器大小必须匹配:
mov al, bl ; 8 位 -> 8 位,正确
mov ax, bx ; 16 位 -> 16 位,正确
mov eax, ebx ; 32 位 -> 32 位,正确
mov rax, rbx ; 64 位 -> 64 位,正确
; mov al, bx ; 错误:大小不匹配
; mov eax, bl ; 错误:大小不匹配
直接寻址
直接在指令中指定内存地址:
section .data
value dd 12345
section .text
mov eax, [value] ; 将 value 地址处的值加载到 EAX
mov eax, [0x1000] ; 将地址 0x1000 处的值加载到 EAX(需要绝对地址)
在 64 位模式下,直接寻址通常需要 RIP 相对寻址:
; 64 位模式下的推荐写法
mov eax, [rel value] ; RIP 相对寻址
lea rax, [rel value] ; 加载 value 的地址
寄存器间接寻址
通过寄存器中的地址访问内存:
; 基本形式
mov eax, [ebx] ; 将 EBX 指向的内存值加载到 EAX
mov [ebx], eax ; 将 EAX 存储到 EBX 指向的内存
; 使用示例
mov rsi, buffer ; RSI 指向缓冲区
mov eax, [rsi] ; 读取第一个双字
mov eax, [rsi + 4] ; 读取第二个双字
可用的基址寄存器:
; 32 位模式:可以使用任何通用寄存器
mov eax, [ebx]
mov eax, [ecx]
mov eax, [edx]
mov eax, [esi]
mov eax, [edi]
mov eax, [ebp]
mov eax, [esp]
; 64 位模式:可以使用任何 64 位通用寄存器
mov rax, [rbx]
mov rax, [r8] ; 包括 R8-R15
寄存器相对寻址
基址寄存器加上偏移量:
; 格式:[base + displacement]
mov eax, [ebx + 4] ; EBX + 4 处的值
mov eax, [ebx + 0x100] ; EBX + 0x100 处的值
; 访问数组元素
mov rsi, array
mov eax, [rsi + 0] ; 第 0 个元素
mov eax, [rsi + 4] ; 第 1 个元素(32 位元素)
mov eax, [rsi + 8] ; 第 2 个元素
; 访问结构体成员
mov rsi, person
mov eax, [rsi + 0] ; 第一个成员
mov eax, [rsi + 32] ; 偏移 32 处的成员
偏移量可以是负数:
mov eax, [rbx - 4] ; EBX - 4 处的值
mov eax, [rbp - 8] ; 栈帧中的局部变量
变址寻址
基址寄存器加变址寄存器:
; 格式:[base + index]
mov eax, [ebx + ecx] ; EBX + ECX 处的值
; 数组访问
mov rsi, array ; 数组基址
mov rcx, 5 ; 索引
mov eax, [rsi + rcx*4] ; 第 5 个元素(比例因子 4)
比例变址寻址
完整的寻址格式:[base + index * scale + displacement]
- base:基址寄存器
- index:变址寄存器
- scale:比例因子(1、2、4、8)
- displacement:偏移量
; 完整格式
mov eax, [ebx + ecx*4 + 0x100]
; 各部分可以省略
mov eax, [ebx] ; 仅基址
mov eax, [ebx + 0x100] ; 基址 + 偏移
mov eax, [ebx + ecx] ; 基址 + 变址
mov eax, [ebx + ecx*4] ; 基址 + 比例变址
mov eax, [ecx*4 + 0x100] ; 比例变址 + 偏移
mov eax, [0x100] ; 仅偏移(直接寻址)
比例因子
比例因子用于数组访问,根据元素大小选择:
| 元素大小 | 比例因子 | 示例 |
|---|---|---|
| 字节 | 1 | mov al, [rsi + rcx*1] |
| 字 | 2 | mov ax, [rsi + rcx*2] |
| 双字 | 4 | mov eax, [rsi + rcx*4] |
| 四字 | 8 | mov rax, [rsi + rcx*8] |
数组访问示例
section .data
int_array dd 10, 20, 30, 40, 50
array_len equ 5
section .text
; 访问第 i 个元素
mov rsi, int_array
mov rcx, 2 ; 索引 i = 2
mov eax, [rsi + rcx*4] ; EAX = 30
; 遍历数组求和
mov rsi, int_array
mov rcx, array_len
xor eax, eax
sum_loop:
add eax, [rsi]
add rsi, 4 ; 指向下一个元素
loop sum_loop
; EAX = 150
RIP 相对寻址
x86-64 引入了 RIP 相对寻址,用于位置无关代码:
; RIP 相对寻址
mov eax, [rip + offset] ; 相对于当前 RIP 的地址
; 访问全局变量
section .data
global_var dd 100
section .text
mov eax, [rel global_var] ; NASM 语法
lea rax, [global_var] ; 加载地址(NASM 自动使用 RIP 相对)
RIP 相对寻址的优势:
- 支持位置无关代码
- 代码可以加载到任意地址执行
- 现代操作系统推荐的方式
段寻址
在 16 位和 32 位保护模式下,可以指定段寄存器:
; 指定段寄存器
mov eax, [es:ebx] ; 使用 ES 段
mov eax, [fs:0] ; 访问 FS 段偏移 0 处
; 访问线程本地存储(Linux)
mov eax, [fs:0] ; TLS 基址
; 访问线程本地存储(Windows)
mov eax, [gs:0x60] ; PEB 地址
64 位模式下,大多数段寄存器被忽略,但 FS 和 GS 仍可用于特殊用途。
寻址模式总结
完整格式
[段: 基址 + 变址 * 比例 + 偏移]
示例汇总
; 立即寻址
mov eax, 100
; 寄存器寻址
mov eax, ebx
; 直接寻址
mov eax, [0x1000]
; 寄存器间接寻址
mov eax, [ebx]
; 寄存器相对寻址
mov eax, [ebx + 0x10]
; 变址寻址
mov eax, [ebx + ecx]
; 比例变址寻址
mov eax, [ebx + ecx*4 + 0x100]
; RIP 相对寻址
mov eax, [rel global_var]
; 段寻址
mov eax, [fs:0]
实际应用示例
数组遍历
; 计算数组元素之和
; 输入:RSI = 数组地址,RCX = 元素个数
; 输出:RAX = 总和
array_sum:
xor eax, eax
xor rdx, rdx
.loop:
add eax, [rsi + rdx*4]
inc rdx
cmp rdx, rcx
jb .loop
ret
结构体访问
struc Point
.x resd 1
.y resd 1
.z resd 1
endstruc
; 计算点数组中所有 x 坐标之和
; 输入:RSI = 点数组,RCX = 点数
; 输出:EAX = x 坐标之和
sum_x_coords:
xor eax, eax
.loop:
add eax, [rsi + Point.x]
add rsi, Point_size ; 移动到下一个点
loop .loop
ret
字符串处理
; 查找字符在字符串中的位置
; 输入:RSI = 字符串地址,AL = 要查找的字符
; 输出:RCX = 位置(-1 表示未找到)
find_char:
xor ecx, ecx
.loop:
cmp byte [rsi + rcx], 0
je .not_found
cmp byte [rsi + rcx], al
je .found
inc ecx
jmp .loop
.found:
ret
.not_found:
mov ecx, -1
ret
二维数组访问
; 访问二维数组元素
; 数组:int matrix[rows][cols]
; 访问:matrix[row][col]
; 地址:matrix + (row * cols + col) * 4
section .data
matrix dd 1, 2, 3, 4
dd 5, 6, 7, 8
dd 9, 10, 11, 12
rows equ 3
cols equ 4
section .text
; 访问 matrix[1][2]
mov rax, 1 ; row = 1
mov rbx, 2 ; col = 2
mov rcx, cols
; 计算偏移:(row * cols + col) * 4
imul rax, rcx ; row * cols
add rax, rbx ; + col
mov eax, [matrix + rax*4] ; EAX = 7
小结
本章介绍了 x86 的内存寻址模式:
- 立即寻址:操作数在指令中
- 寄存器寻址:操作数在寄存器中
- 直接寻址:直接指定内存地址
- 寄存器间接寻址:通过寄存器访问内存
- 寄存器相对寻址:寄存器加偏移
- 变址寻址:基址加变址
- 比例变址寻址:支持比例因子
- RIP 相对寻址:位置无关代码
x86 丰富的寻址模式使得内存访问非常灵活,能够高效地处理数组、结构体等复杂数据结构。下一章将学习基础指令。