Chapter III-2
CSAPP NOTE CHAP III-2
Chapter 3.2 IA-32 指令系统概述#
数据类型及其格式#
IA-32 指令系统中,操作数可以按存储方式分为:
- 立即数
- 寄存器操作数
- 存储器操作数
注:IA-32 指令系统中,操作数被看作 0/1 序列
AT&T 格式和 Intel 格式的汇编指令有不小的区别:
Intel格式 AT&T格式 MOV EAX, 0 movl $0, %eax ADD EAX, EBX addl %ebx, %eax INC ECX incl %ecx 两者目的操作数和源操作数的顺序不同!
AT&T 指令长度后缀#
| 后缀 | 操作数 | 例子 |
|---|---|---|
| b | 8位 | movb $0, %al, |
| w | 16位 | movw $0, buf |
| l | 32位 | movl $0, %eax |
若指令中有一个操作数是寄存器,则操作数长度已确定,长度后缀可省略(不推荐)
寄存器组织#

寻址方式#
- 主存储器
| 主存 | 辅存 | |
|---|---|---|
| 容量 | 小 | 大 |
| 存取速度 | 快 | 慢 |
- 基本单位是 bit
- 为了区别各个存贮单元,给每个单元编号,称为地址
- 字节单元的编号,也称为物理地址
- 低字节放地位,高字节放高位


存储器物理地址的形成#
早期的 8086 微处理:
- 20 位地址总线,寻址范围:
- 与地址相关的寄存器均为 16 位(SP, BP, SI, DI),寻址范围:
问题:如何通过 16 位寄存器访问 1 MB 的内存
解决:将 1 M 字节主存分段,每段最多 64 K 字节
-
用 CS, DS, SS, ES 保存当前可用段的段首地址
-
计算物理地址时,应将段寄存器内容左移 4 位,然后再与偏移地址相加,得到待访问单元的物理地址。

80x86 的寻址范围:
- 80x86 地址总线 32 位,寻址范围 4 G
- 80x86 通用寄存器 32 位,寻址范围也是 4 G
所以 80x86 的一个通用寄存器能够存放一个完整的 32 位地址
保护模式下的内存管理#
要为每个进程提供一个独立的4G大小的虚拟地址空间。逻辑地址空间(多个进程的地址空间总和)大于物理地址空间。采用分页机制来实现:
-
将物理地址映射到进程的逻辑地址空间上;
-
只有映射了物理内存的逻辑地址空间才能访问;
-
每次映射的物理内存不大,且用完后可释放,再重新映射到新的逻辑地址空间上,因此能够让多个进程同时运行;
-
分页由系统基于页目录和页表,自动完成

特权级与分段机制
- 80x86有4个特权级:0,1,2,3(0级最高,3级最低),能够在虚拟内存的基础上实现进一步的内存保护机制

-
80x86通过分段机制来实现特权级的访问控制。
用“段选择符:EA”的形式表示逻辑地址,段选择符(16位)存放在段寄存器中,指向一个段描述符(64位)

用逻辑地址访问内存时,段选择符中的特权级需要高于段描述符中的特权级。
- 由逻辑地址生成物理地址的过程:

- EA要小于“段界限 * 4K”。一般应用程序中,段基址为0,段界限为0FFFFFH。所以对线性地址的形成不产生影响。
具体寻址方式(重要)#

-
立即寻址
操作数直接存放在指令中,紧跟在操作码之后,它作为指令的一部分存放在代码段里,这种操作数称为立即数。
汇编格式:$value
功能:指令的下一单元的内容为操作数 n,即:

例子 movl $0x3064, %eax
目的操作数采用寄存器寻址,为寄存器 eax
源操作数采用立即寻址,即:

注意事项:
-
立即数指令作为源操作数,不能作为目的操作数!
-
立即数不能作为单操作数指令的操作数
-
立即数只有大小,没有类型,未分配内存单元
- 寄存器寻址
在这种寻址方式中,指令所指明的寄存器中存放操作数
汇编格式:%R
功能:寄存器R的内容就是操作数。

例子 1 incl %eax
操作数在 eax 中

执行前:EAX=12H
执行:EAX+1 → EAX。
执行后:EAX= 13H
例子 2 add %ebx, %eax
EAX为目的操作数地址,EBX为源操作数地址。

执行前:EAX=1234H,EBX=5620H
执行: EAX+EBX→ EAX。
执行后:EAX=6854H,EBX=5620H
目的操作数、源操作数都是用寄存器寻址
注意事项:
- 用寄存器操作比较快!如果可能的话尽量多用
- 采用寄存器寻址方式,目的、源操作数类型必须一致。如:movw %bx, %ah; ERROR
-
直接寻址
直接寻址时,操作数的偏移地址就在指令操作码后面,而操作数则存放在内存中。
汇编格式:EA(有效地址) 或 地址表达式
功能:指令下一字单元的内容是操作数的偏移地址。

段基址为 0 时,EA 等于操作数的线性地址。
操作数的线性地址到物理地址的转换由系统自动完成,因此在实际应用中,只需要着重考虑操作数的偏移地址。
例子 3 movl 0x2000, %eax
执行前 (0x2000) = 1234H

执行时:(2000H) -> EAX
执行后:EAX = 0x1234
注意事项:
- EA 格式表示的操作数没有类型
- 地址表达式 格式中,若还有变量,则表示的操作数有类型。
movw 0x20, %ax ; 源操作数无类型
movw %ax, buf ; 目的操作数为直接寻址,其中buf为已定义的字类型的变量。则目的操作数有类型asm- 寄存器间接寻址
操作数的偏移地址在寄存器中,而操作数则在存贮器中。
格式:(%R)
功能:R 的内容为操作数的偏移地址 EA

R可以是EAX、EBX、ECX、EDX、ESI、EDI、EBP、ESP之一。EBP和ESP默认所指的段为堆栈段。
例子 4 mov (%esi), %eax
假定执行前:EAX = 5, ESI = 1000H, (1000H) = 50A0H

执行:(1000H) -> EAX
执行后:EAX = 50A0H,其余不变
注意事项:
- 当 R 是
ebpesp则操作对象在堆栈段中,当 R 为其他寄存器,说明对象在数据段中。 - 此种寻址无类型
-
变址寻址
操作数的偏移地址 EA 是指令中指明的寄存器的内容与指令中给出的位移量之和。操作数在存贮器中。
汇编格式:X(%R) 或 X(, %R, S)
功能:R 的内容与 X 相加,和为操作数的偏移地址 EA

寄存器的选择同寄存器间接寻址一样,可以用EAX、EBX、ECX、EDX、ESI、EDI、EBP、ESP之一。EBP和ESP所指的段默认为堆栈段。
例子 5 movl count(%esi), %eax
假定执行前:ESI=2000H,COUNT=3000H,EAX=0,(5000H)=1234H

执行:(0x5000) -> EAX
执行后:EAX = 0x1234, 其余不变
采用变址寻址可以方便地访问数组中的任一元素,使得编程更加方便。
例子 6 一维数组 a,其存放如图所示

访问其元素的源码如下:
.section .data
count .byte a0, a1,…, an
.section .text
...
mov offset count, %ebx
; 若要将a8放到ah中,可用:
movb 8(%ebx), %ah
; 若要将第 i 个元素放到AH中,可用:
movb i(%ebx), %ah
...asm注意事项:
-
段属性问题
X为常数或数值表达式时,操作对象的段由R决定
X为变量或标号时,操作数对象所在的段就是变量或标号所在的段
-
类型
当X为常量时,此种寻址无类型;
当X为含有变量或者标号的表达式时,则有类型。
如:
movw buf(%ebx), %cx若 BUF 是已定义的字类型变量, buf(%ebx) 类型为字类型。
若 BUF 定义为字节类型,则 buf(%ebx) 与 %cx 类型不一致。
- 基址加变址寻址
操作数的偏移地址 EA 是指令中指定的基址寄存器内容、变址寄存器内容及位移量 X 三者之和。操作数存放在主存之中。
格式:X(%BR, %IR, S)
功能:BR 的内容加上 IR * S,再加上 X,得到操作数的偏移地址。也就是:EA = BR + IR * S + X

其中:BR 表示基址寄存器,IR 表示变址寄存器。BR 和 IR 可以是 EAX、EBX、ECX、EDX、ESI、EDI、EBP、ESP 之一。
若 BR 为空,则简化为变址寻址。
基址寄存器决定操作数所在的段。如果选用 EBP 或 ESP,则操作数在堆栈段中;否则操作数在数据段中。
例子 7 movw mask(%ebx, %esi, ), %ax mask 为一个符号常量
源操作数采用基址加变址寻址,EA = EBX + ESI + mask
执行前:EBX = 0x2000, ESI = 0x1000, MASK = 0x250, (0x3250) = 0x1234

执行:(3250H) -> AX
执行后:AX = 0x1234, 其它不变
注意事项:
段的默认情况:当 X 为常数时,BR 为 BP 默认段为 SS ,其它默认段为 DS。操作数无类型。
当 X 为变量或标号表达式时,操作对象所在的段就是变量或标号所在的段。操作数有类型,且与变量或标号类型相同。
带有比例因子的寻址方式
X(%BR, %IR, S)
(1) 只在变址寄存器 IR 上可以加比例因子 S
(2) 比例因子 S 可以为 1,2,4,8
CPU提供的指令和寻址方式,需要能够将高级语言翻译成高效的机器代码。
例子 movw buf(%ebx, %esi, 2), %ax
则 EA = EBX + 2 * ESI + BUF 的偏移地址
- 寻址方式的有关问题

某些指令的执行要涉及到缺省的操作数,其寻址方式也是隐含的。
例: cbw
隐含源和目的操作数。源操作数为 AL ,寄存器寻址;目的操作数为 AX ,寄存器寻址。
双操作数指令中,源和目的操作数不能同为存储器寻址方式
movb (%esi), (%edi) ; ERROR
addl 10(%ebx, %edi, 4), count ;ERRORasm- 寻址方式举例
例: 已知数据段定义和存储示意图如下:
.section .data
buf: .byte 10, 20, 40, 80, 30
buf1: .byte 0, 0, 0, 0, 0asm
(1) 分别利用直接寻址,寄存器间接寻址,变址寻址和基址加变址寻址,将该段中 BUF+3 单元中的内容送到 AL 中。
a) 直接寻址
mov buf(3), %al
b) 寄存器间接寻址
lea buf(3), %ebx ;将 BUF+3相对于段首地 址的偏移地址EA=3 送入BX中
mov (%ebx), %al
c) 寄存器变址寻址
leal buf, %ebx
movb 3(%ebx), %al
d) 基址加变址寻址
leal buf, %ebx
movl $3, %esi
movb (%ebx, %esi, ), %alasm (2) 利用寄存器间接寻址,变址寻址和基址加变址寻址,将 BUF 为首址的连续字节单元的内容分别送到以 BUF1 为首址的连续字节单元中。
a) 寄存器间接寻址
…
leal buf, %esi
leal buf1, %edi
movl $5, %ecx
l1:
movb (%esi), %al
movb %al, (%edi)
inc %esi
inc %edi
dec %ecx
jnz l1
…
b) 变址寻址
…
movl $0, %esi
movl $5, %ecx
l1:
movb buf(%esi), %al
movb %al, buf1(%esi)
inc %esi
dec %ecx
jnz l1
…
c) 基址加变址寻址
…
leal buf, %ebx
leal buf1, %ebp
movl $0, %esi
movl $5, %ecx
l1:
movb (%ebx, %esi, 1), %al
movb %al, (%ebp, %esi, 1)
inc %esi
dec %ecx
jnz l1
…asm说明:
- 存储器间接寻址,变址寻址和及指甲变址寻址都能用来传送一片连续存储区的内容。
- 上述 3 种方式中,b 的可读性最好
常量和变量的定义#
汇编程序的语句中具体的操作数,可以分为常量和变量两种:

表达式:由常数、寄存器、标号、变量加上运算符构成的式子
-
常量:汇编时已有确定数值的量。
用途:赋值、作立即数、位移量。

表达式:由常数、寄存器、标号、变量加上运算符构成的式子
-
等价伪指令
.equ -
等号伪指令
=使用:定义后直接引用符号名。
# 太抽象了,举一个实际例子,这里先举.equ 的
# Before
MOV AH, 2 ; 功能号2
MOV DL, 65 ; 字符'A'
INT 21h ; 调用DOS中断
# After
; --- 在程序开头定义常量 ---
DOS_PRINT_CHAR .equ 2 ; 定义“打印字符”功能的编号
CHAR_A .equ 65 ; 定义'A'的ASCII码
DOS_INTERRUPT .equ 21h ; 定义DOS中断号
; --- 在代码中使用 ---
MOV AH, DOS_PRINT_CHAR ; 功能:打印字符
MOV DL, CHAR_A ; 内容:'A'
INT DOS_INTERRUPT ; 执行
# 再来一个 = 的
; --- 在程序开头定义一个“开关” ---
; 0 = 发行版, 1 = 调试版
DEBUG_MODE = 1
; --- 根据开关的值,来设置真正的常量 ---
IF DEBUG_MODE == 1
TIMEOUT_VALUE = 5 ; 调试版:超时设为 5
ELSE
TIMEOUT_VALUE = 60 ; 发行版:超时设为 60
ENDIF
; --- 在你的代码中 ---
; 假设有一个等待超时的函数
CALL WaitForInput
CMP AX, TIMEOUT_VALUE ; 检查是否超时
JE DidTimeoutasm-
数值表达式
常量与运算符组成的式子。数值表达式在汇编期间进行运算,结果为常量。
-
算术运算
+-*/MODSHRSHL -
逻辑运算
ANDORXORNOT -
关系运算
EQNELTGTLEGE关系不成立结果为 0, 成立为 -1 (0FFFFH)
-
-
变量
数据段或附加数据段中一个数据存贮单元的名字, 是这个存储单元的地址的符号表示。可代表一批存储单元的首址。
变量的属性
-
段属性:定义变量所在段
-
偏移地址:该变量所占存储单元到所在段的段首址的字节距离
-
类型:类型是指存取该变量中的数据所需要的字节数, 变量的类型由定义该变量时所使用的伪指令确定
变量的定义
-
格式:[变量名:] 变量定义伪指令 表达式[,…]
-
变量定义伪指令:
| 伪指令 | 数据类型 |
|---|---|
| .ascii | 文本字符串 |
| .asciiz/.string | 以0结尾的文本字符串 |
| .byte | 8位字节变量 |
| .word/.short/.hword | 16位字变量 |
| .long/.int | 32位双字变量 |
| .quad | 64位四字变量 |
| .octa | 128位八字变量 |
| .float/.single | 32位单精度浮点数 |
| .double | 64位双精度浮点数 |
-
表达式:
a 数值表达式
b ASCII 字符串
c 地址表达式
上述 a ~ c 组成的系列,各表达式之间用逗号隔开
示例
在上例中分别执行语句后AL的结果。
movb buf, %al ; %al = 0x01
movw buf + 2, %ax ; %ax = 0x3103asm指令 mov buf, %edx 是否正确?
正确,执行 movl buf, %edx
注意:
伪指令 EQU 及 “=” 不分配存贮单元
使用直接寻址方式时,变量的类型必须与指令的要求相符
-
地址表达式
由变量、标号、常量、寄存器(名加括号)及一些运算符(数值表达式的运算符和特殊运算符)所组成的有意义的的式子。
地址表达式也具有段、EA、类型等三个属性。
简单的地址表达式:直接寻址、间接寻址、变址、基址加变址等。
取偏移地址
$地址表达式
功能:分离出变量、标号的偏移地址
例子 movl $buf, %esi
将变量buf的偏移地址,传送到esi中
$buf是一个常量
-
当前即将分配的地址
功能:表示汇编程序当前即将分配的地址
格式:.
例子
.section .data
buf: .ascii “12345”
con = . - bufasm “.“表示当前即将分配的地址,“. - buf”表示当前地址减去buf的偏移地址,结果即为变量 buf 的字节长度
“. - buf” 在汇编时计算,结果为一个常数值
-
填充伪指令
格式:
.fill repeat, size, value
功能:使用初始值,重复 repeat 次填充 size 个字节
例子 .fill 10, 2, 0x5656
分配 20 个字节,用 word 值 0x5656, 填充 10 次