Intel SDM - 分段寻址

Intel SDM — 分段寻址

字节序

在小端字节序中,一个字的字节从最低有效字节开始计数。
比如 0x12345678 在小端机器中第一个字节为 0x78。而大端机器中则相反,从最高有效字节开始计数,第一个字节为 0x12。

分段寻址

分段寻址是一种寻址方式,即一个程序可以多个独立的寻址空间。比如,程序可以将其代码和栈放置到不同的段中,这些端视为独立的寻址空间。在分段寻址中,使用如下记号来表示一个字节地址:
Segment_register:Byte_address
段寄存器一般也表述为段选择器,所以以上形式可表述为段选择器+偏移地址。段+偏移所组成的逻辑地址通常被认为是远指针。IA-32 处理器最毒哦可以寻址 16383 = 2 14 1 16383 = 2^{14} - 1 个段,每个段可以有 2 32 2^{32} 个字节。

在处理器内部,所有的段都会被映射到处理器的线性地址空间中。为了访问内存位置,处理器因此需要将逻辑地址翻译成线性地址。
分段寻址模型

保护模式下系统级别分段场景

保护模式中,所有的内存访问都要通过 GDT(全局描述符表)或 LDT(本地描述符表)见下图。
GDTR和LDTR
这些表包含段描述符。段描述符号则提供访问权限,类型,和使用信息。每一个段描述符有一个相关联的段选择器。使用 GDT 和 LDT 时,段选择器并非是段基地址,而是GDT或 LDT 中的描述符索引,段选择器包含索引、global/local flag 和访问权限信息。

为了访问段中的字节,必须提供段选择器和偏移量。段选择器用来访问段描述符表中的段描述符。处理器冲段描述符中获取线性地址空间中段的基地址。偏移量则提供相对于该基地址的相对位置。

TSS

TSS(Task State Segment) 为一个任务定义执行环境状态,包括通用目的寄存器、EFLAGS 寄存器、EIP 寄存器和三个带有栈指针的段选择器(每一个权限级别都有各自的栈)。TSS 中还包含与任务相关联的 LDT 段选择器(LDT 也属于段,GDT 不属于),如果有分页机制的话,还包含分页结构层级的基地址。

保护模式下,所有的程序都执行在当前任务环境中。TSS 相关联的段选择器保存在任务寄存器中。切换任务的最简单方法是调用或跳转到新任务中,此时新任务的 TSS 段选择器有 CALLJMP 指令给出。在切换任务时,处理器执行以下操作:

  1. 存储当前 TSS 的当前任务的状态
  2. 为新任务加载段选择器到任务寄存器中
  3. 通过 GDT 中的段描述符访问新的 TSS
  4. 从新的 TSS 中加载新任务状态到通用目的寄存器,段寄存器,LDTR,CR3控制寄存器(分页结构层级的基地址),EFLAGS 寄存器和 EIP 寄存器
  5. 开始执行新任务

段选择器

一个段选择器包含以下内容:

  1. Index – 用来选择 GDT 或 LDT 中 8192 个描述符。由于每一个描述符有 8 个字节,所以处理器会将 index 乘以 8,然后加上位于 GDTR 或 LDTR 寄存器中的 GDT 或 LDT 基地址
  2. TI(table indicator) flag – 用来表示使用的描述符表是 GDT 还是 LDT。为 0 时表示 GDT,置 1 时表示当前 LDT
  3. Requested Privilege Level (RPL) – 用来表示选择器的特权级别。特权级别范围:0 ~ 3,0 是最高特权级别。

段选择器

段寄存器

处理器一共提供了 6 个段寄存器,所以虽然可以定义数千个段,但最多只能同时使用 6 个段。段寄存器分为可见部分和隐藏部分(隐藏部分有时也被称为段描述符缓存或者影子寄存器)。当段选择器加载到段寄存器的可见部分时,处理器会将段描述符总的基地址,段界限和访问控制信息加载到隐藏部分。缓存机制可以减少处理器额外的总线周期,但如果表中数据被修改,则需要重新加载到段寄存器的隐藏部分。
段寄存器

段描述符

段描述符是 GDT 和 LDT 中的一个数据结构,可以为处理器提供段的大小和位置信息,也可以提供访问控制和状态信息。段描述符可以通过编译器,链接器,加载器或操作系统创建,无法通过应用程序来创建。下图描述了段描述符的格式:
段描述符
各字段含义如下:

Segment limit field:

用来指定段的大小。处理器会将两个段界限字段放在一起,来形成一个 20-bit 的值。处理器有两种解释该字段的方式,主要依赖于 G(细粒度) 标签

  • 如果 G 清零,段大小范围为 1 B ~ 1MB,按字节递增
  • 如果 G 置 1,段大小范围为 4KB ~ 4GB,按 4KB 递增
    处理器在使用该字段的时候还需要判断地址增长方向

Base address fields:

定义 4GB 线性地址空间中段的起始位置。三个基地址字段一起构成一个 32-bit 的值。段基地址应当(非必须) 16 字节边界对齐。

Type field:

表示段或 gate 的类型。这些类型决定了段可访问的类型和段的增长方向。该字段的解释取决于描述符类型(S 字段)。不同类型的描述符(代码、数据或系统描述符)的段类型编码不同。

S(descriptor) flag:

表示段描述符类型,如果清 0 ,则表示用于系统段,否则为代码或数据段。

DPL(descriptor privilege level) field:

表示段的权限级别。范围 0 ~ 3,0 为最高权限。

P(segment-present) flag:

表示段是否存在与内存中。置1时,段存在于内存中,否则不存在。如果不存在,则会在加载段选择器到段寄存器中时产生 #NP(segment-not-present) 异常。一般用于分页机制中。

D/B (default operation size/default stack pointer size and/or upper bound) flag:

更具段描述符是否是一个可执行代码段、向下增长数据段或栈段来执行不同的功能(该记号在 32bit 代码段和数据段中永远为 1,16bit 代码段和数据段中则永远为 0)

  • 可执行代码段 — 这时该标签被称为 D 标签,用来表示段中有效地址和操作数的默认长度。如果置 1吗,则为 32bit 地址和 32bit 或 8bit 操作数,否则为 16bit 地址和 16bit 或 8bit 操作数。
    指令前缀 66H 和 67H 则分别用于选择非默认的操作数长度和地址长度
  • 栈段(SS寄存器指向的数据段) — 此时该标签称为 B(big) 标签,用来指定隐式栈操作(比如 pushes,pops, 和 calls)的栈指针大小。如果置1,则使用 32bit 的栈指针,存储在 32bit 的 ESP 寄存器中,否则使用 16bit 栈指针,存储在 16bit的 SP 寄存器中。
  • 向下扩展的数据段 — 此时该标签也称为 B 标签,用来指定段的上边界。如果置 1,则上边界为 FFFFFFFFH (4 GB),否则为 FFFFH (64 KB)

G (granularity) flag:

细粒度标签,如果为 0,则表示段界限以字节为单位,否则以 4-KB 为单位。

L (64-bit code segment) flag:

表示代码段是否包含原生 64bit 代码。为 1 表示代码段中的指令在 64bit 模式下执行,否则为兼容模式。L 置 1 时,D 字段必须清 0。