1_chap3-basic_execution_environment
Contents
1. Overview of the Basic Execution Environment
Intel 处理器架构分为两类 : IA-32 和 Intel 64 , 前者支持保护模式 , 实地址模式 , SMM 模式 ( 通过 APIC 的 SMI 中断或者激活外部 SMI# 进入 ) ; 后者增加了 IA-32e 模式 , 包括兼容模式和 64 位模式 .
和 IA-32 相比 , 64 位模式增加了寄存器的数量 , 扩展了寄存器的宽度 , 指令使用的地址为 64 位 , 但是默认的操作数大小为 32 位 .
此外 , 64 位模式引入了新的操作码前缀 ( REX ) 以访问寄存器扩展 , 默认的操作数大小可以通过 REX 和操作数大小重载前缀覆盖 .
SMM 模式处理器切换到一个单独的地址空间 , 称作系统管理 RAM ( SMRAM ) , 采用的内存模型和实地址模式相似 .
IA-32e 模式下 , 所有的 16 位和 32 位地址会 0 扩展形成 64 位地址 , 因此兼容模式下 16 位和 32 位应用只能访问 64 位有效地址的低 4GB .
64 位模式下 , 如果一个地址的 bit 63 到架构支持的最高位都是 0 或 1 , 这个地址就被视为 canonical .
64 位架构定义了 64 的线性地址 , 但是可以实现更少 : 64 位架构的第一个 IA-32 处理器支持的线性地址是 48 位 , 其 canonical 地址的 bit 63:48 必须都是 0 或者 1 .
虽然实现可以支持少于 64 位线性地址 , 但是应该检查 bit 63 到架构支持的最高位 , 判断地址是否是 canonical . 不是需要产生一个异常 , 通常是 #GP ; 如果是隐式的栈引用 , 产生栈异常 ( #SS ) .
2. Basic Program Execution Registers
2.1. General-Purpose Registers
通用寄存器的特殊用法 :
- EAX : 操作数累加器 , 结果数据
- EBX : DS 段的数据指针
- ECX : 字符串和循环操作的计数器
- EDX : IO 指针
- ESI : 由 DS 寄存器指向的段中的数据指针 ; 字符串操作的源指针
- EDI : 由 ES 寄存器指向的段中的数据指针 ; 字符串操作的目的指针
- ESP : 栈指针
- EBP : 栈中的数据指针
通用寄存器的高字节和低字节可以通过下面的方式引用 :
64 位模式 , 虽然默认的操作数大小为 32 位 , 通用寄存器可以工作在 32 位或 64 位模式 , REX 前缀用于产生 64 位大小的操作数 .
64 位模式 , 操作数的大小决定目的通用寄存器中有效位的数量 :
- 64 位操作数产生 64 位结果 , 保存到目的通用寄存器 .
- 32 位操作数产生 32 位结果 , 0 扩展为 64 位的结构 , 保存到目的通用寄存器 .
- 8 位和 16 位操作数产生 8 位或 16 位结果 , 目的通用寄存器的高 56 位或 48 位不会被修改 . 如果操作的结果被用于 64 位地址计算 , 显式符号扩展为 64 位 .
从 64 位模式切换到 32 位模式 , 通用寄存器的高 32 位未定义 , 其内容不会保留 .
2.2. EFLAGS Register
EFLAGS 寄存器的某些标志可以通过特殊目的的指令修改 , 但是无法直接检测或修改整个寄存器 .
下列指令可以将标志移入和移出程序栈或 EAX 寄存器 : LAHF
, SAHF
, PUSHF
, PUSHFD
, POPF
, POPFD
. EFLAGS 寄存器的内容保存到程序栈或者 EAX 寄存器后 , 可以通过处理器的位操作 ( BT , BTS , BTR , BTC ) 指令检测或修改 .
许多指令在执行时需要用到 EFLAGS 寄存器 , 寄存器结构如下 :
挂起一个应用时 , 处理器自动保存 EFLAGS 寄存器的状态到被挂起进程的 TSS ; 执行新进程时 , 处理器从新进程的 TSS 加载 EFLAGS 寄存器 .
2.2.1. 状态标志
EFLAGS 寄存器的状态标志 ( bit 0 , 2 , 4, 6 , 7 和 11 ) 表明算术指令的结果 , 比如 ADD
, SUB
, MUL
和 DIV
指令 . 状态标志的功能为 :
-
CF ( bit 0 )
carry flag , 如果一个算术操作结果的最高位产生了进位或借位 , 设置这个标志 ; 否则清除 . 无符号运算指明溢出 . -
PF ( bit 2 )
parity flag , 如果结果的最低字节包含偶数个 1 , 设置 ; 否则清除 . -
AF ( bit 4 )
auxiliary carry flag , 如果算术操作结果的 bit 3 产生进位或者借位 , 设置 ; 否则清除 . 在 binary-coded decimal ( BCD ) 算术中使用 . -
ZF ( bit 6 )
zero flag , 如果结果为 0 , 设置 ; 否则清除 . -
SF ( bit 7 )
sign flag , 设置为结果的最高位 , 即有符号数的符号位 . -
OF ( bit 11 )
overflow flag , 如果整数结果是过大的正数或过小的负数 , 无法保存到目的操作数 , 设置 ; 否则清除 . 指明有符号整数的算术运算的溢出条件 .
这些状态位中 , 只有 CF 标志可以直接通过 STC
, CLC
和 CMC
指令修改 .
2.2.2. DF 标志
direction flag ( DF , bit 10 ) 控制字符串指令 ( MOVS
, CMPS
, SCAS
, LODS
和 STOS
) , 设置这个标志导致字符串指令自动减少 ( 从高地址向低地址处理字符串 ) ; 清除导致字符串指令自动增加 ( 从低地址向高地址处理字符串 ) .
STD
和 CLD
指令设置和清除这个标志 .
2.2.3. 系统标志和 IOPL 域
EFLAGS 寄存器的系统标志和 IOPL 域控制操作系统或可执行程序的操作 , 不应该被应用程序修改 , 系统标志的功能如下 :
-
TF ( bit 8 )
trap flag , 设置开启单步调试模式 ; 清除关闭单步调试模式 . -
IF ( bit 9 )
Interrupt enable flag , 设置处理器回应可屏蔽中断 ; 清除阻止可屏蔽中断 . -
IOPL ( bit 12 和 13 )
I/O privilege level filed , 指明当前正在执行的程序的 IO 特权级 , 当前程序的 CPL 必须小于等于 IOPL , 才能访问 IO 地址空间 .POPF
和IRET
指令只有运行在 CPL 0 才能修改这个域 . -
NT ( bit 14 )
nested task flag , 控制被中断的和被调用的进程链 , 设置表明当前进程链接到之前执行的进程 ; 清除表明没有链接到另一个进程 . -
RF ( bit 16 )
resume flag , 控制处理器对于调试异常的回应 . -
VM ( bit 17 )
virtual-8086 mode flag , 设置开启 virtual-8086 模式 , 清除返回保护模式 . -
AC ( bit 18 )
alignment check ( or access control ) flag , 如果设置 CR0.AM , 当且仅当这个标志为 1 时 , 开启用户模式数据访问的对齐检查 .
如果设置 CR4.SMAP , 当且仅当这个标志为 1 时 , 允许显式的管理员模式对于用户模式页的访问 . -
VIF ( bit 19 )
virtual interrupt flag , IF 标志的虚拟映像 , 和 VIP 标志搭配使用 . -
VIP ( bit 20 )
virtual interrupt pending flag , 设置表明一个中断挂起 ; 清除表明没有中断挂起 . -
ID ( bit 21 )
identification flag , 程序设置或者清楚这个标志的能力表明对于CPUID
指令的支持 .
3. Operand-Size and Address-Size Attributes
保护模式下 , 每个代码段的段描述符内的 D 标志指明操作数和地址 ( 用于内存寻址 ) 的大小 , 设置时大小为 32 位 , 清除时为 16 位 .
实地址模式 , virtual-8086 模式 , SMM 默认的操作数和地址大小总是为 16 位 .
4. Operand Addressing
IA-32 机器指令接收 0 或多个操作数 , 一些操作数显式指明 , 一些隐式指明 . 源操作的数据可以位于 :
-
指令自己 ( 立即操作数 )
指令本身编码了源操作数 , 比如ADD EAX,14
.
所有的算术指令 ( 除了DIV
和IDIV
) 允许源操作数为立即数 . -
寄存器
一些指令用一对 32 位寄存器中的内容作为一个操作数 ( 64 位 ) , 形如 EDX:EAX , EDX 包含高位 , EAX 包含低位 .
64 位模式 EDX:EAX 代表 128 位的操作数 . -
内存地址
内存中的源和目的操作数通过段选择器和偏移引用 . 段选择器指明包含操作数的段 , 偏移指明操作数的线性或有效地址 , 偏移可以是 32 位或 16 位 .
64 位模式偏移可以是 16 位 , 32 位或 64 位 . -
I/O 端口
指令返回数据到目的寄存器时 , 可以返回到 :
- 寄存器
- 内存地址
- I/O 端口
4.1. Specifying a Segment Selector
处理器根据下表中的规则自动选择一个段 :
向内存中保存数据 , 或者从内存中加载数据时 , 默认的 DS 段可以被覆盖 , 以访问其他段 . 编译器内 , 段重载通常通过 “:” 操作符完成 . 比如下面的 MOV
指令将 EAX 内的值移动到 ES 寄存器指向的段 , 偏移地址包含在 EBX 寄存器 : MOV ES:[EBX],EAX
.
机器层面通过段覆盖前缀 ( 指令开始的一个字节 ) 实现段覆盖 , 但是下面的段无法被覆盖 :
- 必须从代码段取指
- 字符串指令的目的字符串必须保存在 ES 寄存器指向的数据段
- push 和 pop 操作必须引用 SS 段
64 位模式 , 兼容模式下的段操作和 IA-32 模式相同 ; 64 位模式通常关闭分段 , 处理器将 CS , DS , ES , SS 的基地址当做 0 处理 .
4.2. Specifying an Offset
内存地址的偏移部分可以直接声明为一个静态值 ( 称为 displacement ) 或者通过一个由一个或多个下面的组件计算而来的地址 :
- displacement , 一个 8- , 16- 或 32-bit 的值
- base , 通用寄存器中的值
- index , 通用寄存器中的值
- scale factor , 乘以 Index 值的 2 , 4 或 8
通过这些组件计算而来的偏移称为有效地址 ( effective address ) , 每个组件都可以是正的或负的 , 除了 scale factor .
这些组件的组合使用方式如下 :
这些组件的受到以下限制 :
- ESP 寄存器无法作为索引寄存器 .
- base 为 ESP 或 EBP 时 , 默认段为 SS ; 其他情况下 DS 是默认段 .
下面的寻址方式指明了寻址组件的推荐用法 :
- displacement , 单独一个 displacement 代表一个相对于操作数的直接偏移 , 由于 displacement 直接编码在指令内 , 这种地址有时被称为绝对或静态地址 , 通常用于访问静态分配的标量操作数 .
- base , 单独一个 base 代表相对于操作数的间接偏移 , 由于基寄存器的值可能改变 , 可以用于变量和数据结构的动态存储 .
- base + displacement , 基寄存器和 displacement 可以共同用于两种目的 :
- 元素的大小不是 2 , 4 或 8 字节时 , 作为数组内的索引 —- displacement 组件编码静态偏移为数组的开始 , 基寄存器保存计算的结果 , 确定数组内某个元素的偏移 .
- 访问一个记录的一个域 : 基寄存器保存记录的开始地址 , displacement 是域的静态偏移 .
- (index * scale) + displacement , 元素大小为 2 , 4 或 8 字节时 , 索引静态数组的高效方式 . displacement 确定数组的开始 , index 保存想要的数组元素的下标 , 处理器应用 scaling factor 自动转化下表为索引 .
- base + index + displacement , 一起使用两个寄存器支持一个二维数组 ( displacement 保存数组的起始地址 ) 或一些记录数组实例中的一个 ( displacement 作为记录内域的偏移 ) .
- base + (index * scale) + displacement , 元素为 2 , 4 或 8 字节时 , 高效索引一个二维数组 .
64 位模式除了上面的 4 种组件外 , 还可以通过 RIP + displacement 的方式 , 符号扩展 32 位的 displacement , 加上 RIP 中的 64 位值 , 计算下一条指令的有效地址 .
Author Globs Guo
LastMod 2020-10-13