跳到主要内容

内存寻址模式

内存寻址模式是指令访问内存数据的方式。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] ; 仅偏移(直接寻址)

比例因子

比例因子用于数组访问,根据元素大小选择:

元素大小比例因子示例
字节1mov al, [rsi + rcx*1]
2mov ax, [rsi + rcx*2]
双字4mov eax, [rsi + rcx*4]
四字8mov 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 丰富的寻址模式使得内存访问非常灵活,能够高效地处理数组、结构体等复杂数据结构。下一章将学习基础指令。