NknSのSitE

Back

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, 0movl $0, %eax
ADD EAX, EBXaddl %ebx, %eax
INC ECXincl %ecx

两者目的操作数和源操作数的顺序不同!

AT&T 指令长度后缀#

后缀操作数例子
b8位movb $0, %al,
w16位movw $0, buf
l32位movl $0, %eax

若指令中有一个操作数是寄存器,则操作数长度已确定,长度后缀可省略(不推荐)

寄存器组织#

image-20251026123928449

寻址方式#

  1. 主存储器
主存辅存
容量
存取速度
  • 基本单位是 bit
  • 为了区别各个存贮单元,给每个单元编号,称为地址
    • 字节单元的编号,也称为物理地址
    • 低字节放地位,高字节放高位

image-20251026124203772

image-20251026124408496

存储器物理地址的形成#

早期的 8086 微处理:

  • 20 位地址总线,寻址范围:220=1M2^{20} = 1M
  • 与地址相关的寄存器均为 16 位(SP, BP, SI, DI),寻址范围:216=64K 2^{16} = 64K

问题:如何通过 16 位寄存器访问 1 MB 的内存

解决:将 1 M 字节主存分段,每段最多 64 K 字节

  • 用 CS, DS, SS, ES 保存当前可用段的段首地址

  • 计算物理地址时,应将段寄存器内容左移 4 位,然后再与偏移地址相加,得到待访问单元的物理地址。

image-20251026125908224

80x86 的寻址范围:

  • 80x86 地址总线 32 位,寻址范围 4 G
  • 80x86 通用寄存器 32 位,寻址范围也是 4 G

所以 80x86 的一个通用寄存器能够存放一个完整的 32 位地址

保护模式下的内存管理#

要为每个进程提供一个独立的4G大小的虚拟地址空间。逻辑地址空间(多个进程的地址空间总和)大于物理地址空间。采用分页机制来实现:

  • 将物理地址映射到进程的逻辑地址空间上;

  • 只有映射了物理内存的逻辑地址空间才能访问;

  • 每次映射的物理内存不大,且用完后可释放,再重新映射到新的逻辑地址空间上,因此能够让多个进程同时运行;

  • 分页由系统基于页目录和页表,自动完成

image-20251026130228471

特权级与分段机制

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

image-20251026130514801

  1. 80x86通过分段机制来实现特权级的访问控制。

    用“段选择符:EA”的形式表示逻辑地址,段选择符(16位)存放在段寄存器中,指向一个段描述符(64位)

image-20251026130500434

用逻辑地址访问内存时,段选择符中的特权级需要高于段描述符中的特权级

  1. 由逻辑地址生成物理地址的过程:

image-20251026131624700

  • EA要小于“段界限 * 4K”。一般应用程序中,段基址为0,段界限为0FFFFFH。所以对线性地址的形成不产生影响。

具体寻址方式(重要)#

image-20251026131700566

  1. 立即寻址

    操作数直接存放在指令中,紧跟在操作码之后,它作为指令的一部分存放在代码段里,这种操作数称为立即数。

    汇编格式:$value

    功能:指令的下一单元的内容为操作数 n,即:

    image-20251026131902152

例子 movl $0x3064, %eax

目的操作数采用寄存器寻址,为寄存器 eax

源操作数采用立即寻址,即:

image-20251026132347724

注意事项:

  • 立即数指令作为源操作数,不能作为目的操作数

  • 立即数不能作为单操作数指令的操作数

  • 立即数只有大小,没有类型,未分配内存单元

  1. 寄存器寻址

​ 在这种寻址方式中,指令所指明的寄存器中存放操作数

​ 汇编格式:%R

​ 功能:寄存器R的内容就是操作数。

image-20251026132547362

例子 1 incl %eax

​ 操作数在 eax 中

image-20251026132620740

​ 执行前:EAX=12H

​ 执行:EAX+1 → EAX。

​ 执行后:EAX= 13H

例子 2 add %ebx, %eax

​ EAX为目的操作数地址,EBX为源操作数地址。

image-20251026132715349

​ 执行前:EAX=1234H,EBX=5620H

​ 执行: EAX+EBX→ EAX。

​ 执行后:EAX=6854H,EBX=5620H

​ 目的操作数、源操作数都是用寄存器寻址

注意事项:

  • 用寄存器操作比较快!如果可能的话尽量多用
  • 采用寄存器寻址方式,目的、源操作数类型必须一致。如:movw %bx, %ah; ERROR
  1. 直接寻址

    直接寻址时,操作数的偏移地址就在指令操作码后面,而操作数则存放在内存中。

    汇编格式:EA(有效地址) 或 地址表达式

    功能:指令下一字单元的内容是操作数的偏移地址。

image-20251111210446867

​ 段基址为 0 时,EA 等于操作数的线性地址。

​ 操作数的线性地址到物理地址的转换由系统自动完成,因此在实际应用中,只需要着重考虑操作数的偏移地址。

例子 3 movl 0x2000, %eax

​ 执行前 (0x2000) = 1234H

image-20251111210643821

​ 执行时:(2000H) -> EAX

​ 执行后:EAX = 0x1234

注意事项:

  • EA 格式表示的操作数没有类型
  • 地址表达式 格式中,若还有变量,则表示的操作数有类型。
movw  0x20, %ax	; 源操作数无类型
movw  %ax, buf		; 目的操作数为直接寻址,其中buf为已定义的字类型的变量。则目的操作数有类型
asm
  1. 寄存器间接寻址

​ 操作数的偏移地址在寄存器中,而操作数则在存贮器中。

​ 格式:(%R)

​ 功能:R 的内容为操作数的偏移地址 EA

image-20251111221328566

​ R可以是EAX、EBX、ECX、EDX、ESI、EDI、EBP、ESP之一。EBP和ESP默认所指的段为堆栈段。

例子 4 mov (%esi), %eax

​ 假定执行前:EAX = 5, ESI = 1000H, (1000H) = 50A0H

image-20251111223045715

​ 执行:(1000H) -> EAX

​ 执行后:EAX = 50A0H,其余不变

注意事项:

  • 当 R 是 ebp esp 则操作对象在堆栈段中,当 R 为其他寄存器,说明对象在数据段中。
  • 此种寻址无类型
  1. 变址寻址

    操作数的偏移地址 EA 是指令中指明的寄存器的内容与指令中给出的位移量之和。操作数在存贮器中。

    汇编格式:X(%R) 或 X(, %R, S)

    功能:R 的内容与 X 相加,和为操作数的偏移地址 EA

image-20251111224824841

​ 寄存器的选择同寄存器间接寻址一样,可以用EAX、EBX、ECX、EDX、ESI、EDI、EBP、ESP之一。EBP和ESP所指的段默认为堆栈段。

例子 5 movl count(%esi), %eax

​ 假定执行前:ESI=2000H,COUNT=3000H,EAX=0,(5000H)=1234H

image-20251111225151603

​ 执行:(0x5000) -> EAX

​ 执行后:EAX = 0x1234, 其余不变

采用变址寻址可以方便地访问数组中的任一元素,使得编程更加方便。

例子 6 一维数组 a,其存放如图所示

image-20251111225557596

访问其元素的源码如下:

.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 类型不一致。

  1. 基址加变址寻址

​ 操作数的偏移地址 EA 是指令中指定的基址寄存器内容、变址寄存器内容及位移量 X 三者之和。操作数存放在主存之中。

​ 格式:X(%BR, %IR, S)

​ 功能:BR 的内容加上 IR * S,再加上 X,得到操作数的偏移地址。也就是:EA = BR + IR * S + X

image-20251111230425958

​ 其中: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

image-20251111230638210

​ 执行:(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 的偏移地址


  1. 寻址方式的有关问题

image-20251111231356996

​ 某些指令的执行要涉及到缺省的操作数,其寻址方式也是隐含的。

​ 例: cbw

​ 隐含源和目的操作数。源操作数为 AL ,寄存器寻址;目的操作数为 AX ,寄存器寻址。

​ 双操作数指令中,源和目的操作数不能同为存储器寻址方式

movb (%esi), (%edi) ; ERROR
addl 10(%ebx, %edi, 4), count ;ERROR
asm
  1. 寻址方式举例

例: 已知数据段定义和存储示意图如下:

.section  .data
buf:	.byte  10, 20, 40, 80, 30
buf1:	.byte  0, 0, 0, 0, 0
asm

image-20251111233845768

​ (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, ),  %al
asm

​ (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 的可读性最好

常量和变量的定义#

汇编程序的语句中具体的操作数,可以分为常量和变量两种:

image-20251111234238408

表达式:由常数、寄存器、标号、变量加上运算符构成的式子

  1. 常量:汇编时已有确定数值的量。

    用途:赋值、作立即数、位移量。

image-20251111234823502

​ 表达式:由常数、寄存器、标号、变量加上运算符构成的式子

  • 等价伪指令 .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  DidTimeout
asm
  1. 数值表达式

    常量与运算符组成的式子。数值表达式在汇编期间进行运算,结果为常量。

    • 算术运算

      + - * / MOD SHR SHL

    • 逻辑运算

      AND OR XOR NOT

    • 关系运算

      EQ NE LT GT LE GE

      关系不成立结果为 0, 成立为 -1 (0FFFFH)

  2. 变量

    数据段或附加数据段中一个数据存贮单元的名字, 是这个存储单元的地址的符号表示。可代表一批存储单元的首址


变量的属性

  • 段属性:定义变量所在段

  • 偏移地址:该变量所占存储单元到所在段的段首址的字节距离

  • 类型:类型是指存取该变量中的数据所需要的字节数, 变量的类型由定义该变量时所使用的伪指令确定

变量的定义

  • 格式:[变量名:] 变量定义伪指令 表达式[,…]

  • 变量定义伪指令:

伪指令数据类型
.ascii文本字符串
.asciiz/.string以0结尾的文本字符串
.byte8位字节变量
.word/.short/.hword16位字变量
.long/.int32位双字变量
.quad64位四字变量
.octa128位八字变量
.float/.single32位单精度浮点数
.double64位双精度浮点数
  • 表达式:

    a 数值表达式

    b ASCII 字符串

    c 地址表达式

    上述 a ~ c 组成的系列,各表达式之间用逗号隔开

示例

在上例中分别执行语句后AL的结果。

movb  buf, %al	; %al = 0x01
movw  buf + 2, %ax	; %ax = 0x3103
asm

指令 mov buf, %edx 是否正确? 正确,执行 movl buf, %edx

注意:

伪指令 EQU 及 “=” 不分配存贮单元

使用直接寻址方式时,变量的类型必须与指令的要求相符


  1. 地址表达式

    由变量、标号、常量、寄存器(名加括号)及一些运算符(数值表达式的运算符和特殊运算符)所组成的有意义的的式子。

    地址表达式也具有段、EA、类型等三个属性。

    简单的地址表达式:直接寻址、间接寻址、变址、基址加变址等。

    取偏移地址

    $地址表达式

​ 功能:分离出变量、标号的偏移地址

例子 movl $buf, %esi

​ 将变量buf的偏移地址,传送到esi中

$buf是一个常量

  • 当前即将分配的地址

    功能:表示汇编程序当前即将分配的地址

​ 格式:.

例子

.section  .data
buf:  .ascii  “12345
con	= . - buf
asm

​ “.“表示当前即将分配的地址,“. - buf”表示当前地址减去buf的偏移地址,结果即为变量 buf 的字节长度

​ “. - buf” 在汇编时计算,结果为一个常数值

  • 填充伪指令

    格式:.fill repeat, size, value

​ 功能:使用初始值,重复 repeat 次填充 size 个字节

例子 .fill 10, 2, 0x5656

​ 分配 20 个字节,用 word 值 0x5656, 填充 10 次