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 : 栈中的数据指针

通用寄存器的高字节和低字节可以通过下面的方式引用 :

picture 1

64 位模式 , 虽然默认的操作数大小为 32 位 , 通用寄存器可以工作在 32 位或 64 位模式 , REX 前缀用于产生 64 位大小的操作数 .

picture 2

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 寄存器 , 寄存器结构如下 :

picture 3

挂起一个应用时 , 处理器自动保存 EFLAGS 寄存器的状态到被挂起进程的 TSS ; 执行新进程时 , 处理器从新进程的 TSS 加载 EFLAGS 寄存器 .

2.2.1. 状态标志

EFLAGS 寄存器的状态标志 ( bit 0 , 2 , 4, 6 , 7 和 11 ) 表明算术指令的结果 , 比如 ADD , SUB , MULDIV 指令 . 状态标志的功能为 :

  • 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 , CLCCMC 指令修改 .

2.2.2. DF 标志

direction flag ( DF , bit 10 ) 控制字符串指令 ( MOVS , CMPS , SCAS , LODSSTOS ) , 设置这个标志导致字符串指令自动减少 ( 从高地址向低地址处理字符串 ) ; 清除导致字符串指令自动增加 ( 从低地址向高地址处理字符串 ) .

STDCLD 指令设置和清除这个标志 .

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 地址空间 . POPFIRET 指令只有运行在 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 .
    所有的算术指令 ( 除了 DIVIDIV ) 允许源操作数为立即数 .

  • 寄存器
    一些指令用一对 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

处理器根据下表中的规则自动选择一个段 :

picture 4

向内存中保存数据 , 或者从内存中加载数据时 , 默认的 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 .

这些组件的组合使用方式如下 :

picture 5

这些组件的受到以下限制 :

  • 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 位值 , 计算下一条指令的有效地址 .