简介
在 64 位模式下,每当将非空段选择器加载到任何段寄存器时,处理器都会自动将相应的段描述符加载到段寄存器的隐藏部分中,就像在保护/兼容模式下一样。但是,由 DS、ES 或 SS 选择器选择的段描述符被完全忽略。 FS 和 GS 选择器选择的段描述符的限制和属性字段也被忽略。
英特尔手册 V3 3.4.4:
因为 64 位模式下不使用 ES、DS 和 SS 段寄存器,
它们在段描述符中的字段(基、限制和属性)
寄存器被忽略。某些形式的段加载指令是
也无效(例如,LDS、POP ES)。地址计算
引用 ES、DS 或 SS 段被视为该段
基数为零。
...
在 64 位模式下,内存访问使用 FS-segment 和 GS-segment
覆盖不会检查运行时限制,也不会受到
属性检查。
除此之外,假设每个段的基地址为 0,长度为 264。但是,由 CS、FS 或 GS 选择器选择的段描述符的某些部分仍然有效。特别是FS和GS各自的描述符中指定的基地址被使用。
英特尔手册 V3 3.4.4:
在 64 位模式下使用 FS 和 GS 段覆盖时,它们的
相应的基地址用于线性地址计算。
此外,还使用了 CS 描述符的以下字段:D(默认位)、L(64 位子模式位)、AVL(OS 位)、P(当前位)、DPL(特权级位) )、S(系统位)、D/C(数据/代码位)和 C(一致位)。注意CS的基地址固定为0,CS、FS、GS的长度都固定为264。正如 Peter 在他的评论中指出的那样,CS 描述符的 L 和 D 位需要能够在长模式的不同子模式之间切换。 CS 的其他活跃领域也很有用。支持 FS 和 GS 的不同基地址对于线程本地存储之类的东西很有用。
英特尔手册 V3 5.2.1:
代码段在 64 位模式下仍然存在,因为
地址计算时,段基数被视为零。一些
代码段 (CS) 描述符内容(基地址和限制
字段)被忽略;其余字段正常运行(除了
类型字段中的可读位)。
在 IA-32e 模式下需要代码段描述符和选择器来
建立处理器的操作模式和执行
特权级。
我认为可读位和访问位在 64 位模式下都会被忽略。这些属性被分页结构中的相应属性替换。虽然我在英特尔手册中找不到任何地方说访问的位被忽略了。但是 AMD 手册确实清楚地说明了这一点。
仍在执行描述符表限制检查。
英特尔手册 V3 5.3.1:
在 64 位模式下,处理器不执行运行时限制检查
在代码或数据段上。但是,处理器确实会检查
描述符表限制。
因此,您可以说 DS、ES 和 SS 细分完全禁用细分。但不完全适用于其他三个部分。这就是segmentation cannot be completely disabled 的意思。
英特尔手册 V2 另有说明
我引用 POP 指令的描述。
64 位模式异常
#GP(0) 如果内存地址是非规范形式。
#SS(0) 如果堆栈地址是非规范形式。
#GP(selector) 如果描述符超出描述符表限制。
如果正在加载 FS 或 GS 寄存器,并且指向的段不是数据或可读代码段。
如果正在加载 FS 或 GS 寄存器并且指向的段是
数据或不合格代码段,但 RPL 和 CPL 都是
大于 DPL。
#AC(0) 如果在启用对齐检查时进行了未对齐的内存引用。
#PF(fault-code) 如果发生页面错误。
#NP 如果正在加载 FS 或 GS 寄存器并且指向的段被标记为不存在。
#UD 如果使用 LOCK 前缀。
请注意,对 DS、ES、SS 的 POP 在 64 位模式下无效,并且没有 POP CS。这就是为什么它只谈论FS和GS。虽然这意味着 FS 和 GS 选择的描述符的属性并没有被完全忽略。
类似地,MOV指令的描述是这样的:
64 位模式异常
#GP(0)
如果内存地址是非规范形式。
如果尝试使用 NULL 段选择器加载 SS 寄存器,则
CPL = 3。
如果尝试使用 NULL 段加载 SS 寄存器
CPL
#GP(选择器)
如果段选择器索引超出描述符表限制。如果对描述符表的内存访问是
非规范。
如果正在加载 SS 寄存器并且该段
选择器的 RPL 和段描述符的 DPL 不等于
CPL.
如果正在加载 SS 寄存器并且所指向的段
是不可写的数据段。
如果 DS、ES、FS 或 GS 寄存器是
正在加载并且指向的段不是数据或可读代码
如果正在加载 DS、ES、FS 或 GS 寄存器并且
指向的段是数据或不合格的代码段,但两者
RPL 和 CPL 大于 DPL。
#SS(0) 如果堆栈地址是非规范形式。
#SS(selector) 如果正在加载 SS 寄存器并且指向的段被标记为不存在。
#PF(fault-code) 如果发生页面错误。
#AC(0) 如果启用对齐检查并在当前特权级别为 3 时进行未对齐的内存引用。
#UD 如果尝试加载 CS 寄存器。如果使用 LOCK 前缀。
但请注意这里没有出现#NP!这表明当前位 (P) 仅检查 FS、GS、CS 和 SS,而不检查 DS 和 ES。 (但我认为所有段都检查了 P 位。)这些引用也表明任何段寄存器的选择器的 RPL 部分也被使用。
空段选择器
空段选择器是一个选择器,其值为 0x0000、0x0001、0x0002 或 0x0003。对于处理器而言,所有这些值始终具有相同的效果。这些都选择相同的描述符,即 GDT 的条目 0。
在任何使用分段的模式(包括 64 位模式)下,都无法将空段选择器加载到 CS 中,因为 CS 必须始终包含一个实际的选择器。尝试这样做会产生 GP 异常。
空段选择器可以在 64 位模式下加载到 SS 中(与其他模式相反),但仅限于某些情况。有关详细信息,请参阅 Intel Manual V3 6.15 的“General Protection Exception (#GP)”部分。
空段选择器可以加载到 DS、ES、GS 和 FS 中。
英特尔手册 V3 5.4.1.1:
在 64 位模式下,处理器不执行运行时检查
NULL 段选择器。处理器在以下情况下不会导致#GP 故障
尝试访问引用段所在的内存
寄存器有一个 NULL 段选择器。
我觉得这很有趣,我稍后会解释。 (我也觉得奇怪的是,专门用于分段的第 3 章没有说明这一点。
我不清楚处理器是否在使用空选择器加载空描述符时将空描述符从内存加载到段寄存器的不可见部分。
英特尔手册 V3 3.4.2:
处理器不使用 GDT 的第一个条目。
这是否意味着处理器不会加载空描述符?或者也许它只意味着描述符的内容没有被使用。后来它在 3.4.4 中说:
为了设置应用程序的兼容模式,segment-load
指令(MOV to Sreg、POP Sreg)在 64 位模式下正常工作。一个
条目是从系统描述符表(GDT 或 LDT)中读取的,并且是
加载到段寄存器的隐藏部分。这
描述符寄存器基、限制和属性字段都已加载。
但是,数据和堆栈段选择器的内容和
描述符寄存器被忽略。
英特尔手册 V2 中对 POP 指令的描述说:
64-BIT_MODE
如果 FS 或 GS 加载了 NULL 选择器;
那么
SegmentRegister ← 段选择器;
SegmentRegister ← 段描述符;
FI;
英特尔手册 V2 中对 MOV 指令的描述说:
如果 DS、ES、FS 或 GS 加载了 NULL 选择器
那么
SegmentRegister ← 段选择器;
SegmentRegister ← 段描述符;
FI;
这表明空描述符确实被加载了,但它的内容被忽略了。 Linux 内核将空描述符定义为所有位为零。我在许多文章和教科书中读到这是强制性的。然而,柯林斯says 认为这没有必要:
全局描述符表 (GDT) 中的第一个条目称为
空描述符。 NULL 描述符对于 GDT 来说是唯一的,因为它有一个
TI=0,索引=0。大多数印刷文档都指出,这
描述符表项必须为 0。甚至 Intel 也有些模棱两可
这个主题,从不说它不能用于什么。英特尔确实声明
第 0 个描述符表条目永远不会被
处理器。
AFAIK,英特尔不对空描述符的内容施加任何限制。所以我猜柯林斯是对的。
为什么 5.4.1.1 很有趣?
因为这意味着可以使用 DS、ES、GS 和 GS 在 64 位模式下保存任何常量 0x0000、0x0001、0x0002 或 0x0003。保证 GDT 至少包含空描述符,因此描述符表限制检查将通过(对于其他选择器可能不是这样)。此外,对任何这些段的所有引用仍将成功执行。 MOV指令可用于将值从段寄存器移动到GPR,然后对其执行操作。
AMD 手册
待写。