long mode 下的 segment descript
注意:
|
对于 gate descriptor 来说,在 long mode 下是固定 16 bytes,但是对于 segment descriptor 来说在 long mode 下的 64bit mode 和 compatibility mode 是表现是不同的。
segment descriptor
|
legacy x86 mode
|
long mode
| |
comatibility mode
|
64-bit mode
| ||
user segment descriptor
(code/data segment) |
32 位 descriptor
|
32 位 descriptor
|
32 位 descriptor
|
system segment descriptor
(LDT/TSS segment) |
32 位 descriptor
|
32 位 descriptor
|
64 位 descriptor
|
仅仅在 64 位模式下,LDT 和 TSS segment descriptor 被看作是 64 位的 descriptor
再一次说明 user segment 是指:
- code segment
- data segment
system segment 是指:
- LDT segment
- TSS segment
1. 64 位模式下的 user segment descriptor 结构
在 long mode 下对 user segment descriptor 有两种解释结果:
- 64 位模式下的 descriptor
- compatibility 模式下的 descriptor
在 compatibility 模式下 code segment descriptor 与 legacy x86 的 code segment descriptor 在意义在只有一点差异:
- L 属性位
在 legacy x86 模式下不存在 L 属性,但是这个 L 位在 legacy x86 模式下是 0 值。而 compatibility 模式下的 L 属性是 0 值。实际上它们是相等的。
下面是在 64 位模式下的解释:
它们的 segment descriptor 的 S = 1 指示它们是 user segment descriptor
上图灰色部分的 limit 和 base 在 user segment descriptor 里是无效被忽略的,有部分属性是支持的。
然而 attribute 部分对于 Code segment descriptor 和 Data segment descriptor 有着不同的表现,粉红色部分在 code segmnt descriptor 里是有效的,在 data segment descriptor 里是无效的。
1.1 Code segment descriptor
上图中的白色部分和红色部分在 code segment descriptor 里是有效的,它们是:
- C(Conforming):指示 code segment 是 conforming 还是 non-conforming 类型,它们在权限控制上的表现是不一样的。
- DPL(Descriptor Privilige Level): 指示访问 code segment 需要的权限
- P(Present):指示 code segment 是否加载在内存中
- L(Long):指示 code segment 是 64 位模式代码还是 compatibility 模式代码
- D(Default operand size):指示 code segment 的 default operand size
这些 attribute 位加载到 CS 寄存器后,在 CS 寄存器的 attribute 里同样是有效的。
虽然 x64 体系非常想抛弃 segmentation 机制,但是为了整个 x86 架构的兼容性不得以而为之:
- C 和 DPL 为权限控制和转移而保留
- L 和 D 为 processor 模式和指令操作数而设
- P 恐怕是最没有异议
图中绿色部分比较特别:
- S(system) 标志
- C/D(Code/Data)标志
虽然这两个标志是无效的,但是您必须为它设置初始值,在设置初始值后你不能进行更改,这是无效的一面。
对于 Code segment descriptor 来说,它必须设为(注意是:必须):
- S = 1
- C/D = 1
说明这个 descriptor 是 code segment descriptor,如果你尝试加载一个 S = 0 或者 C/D = 0 的 descriptor 进入 CS 寄存器,将会产生 #GP 异常。
而下面两个类型属性是无效的:
- R(Readable)
- A(Accessed)
那么 Code segment 在 64 位模式下强制为 Readable 可读。
1.2 Data segment descriptor
在 data segment descriptor 情况有些特别。
对于加载到 ES, DS, SS 寄存器的 data segment descriptor 来说仅有一个属性是有效的:
- P(Present)
对于加载到 FS 和 GS 寄存器的 data segment descriptor 来说 base 是有效的,那么可以在 FS 和 GS 寄存器的 base 里设置非 0 的 segment base 值
同样必须设置 S 和 C/D 属性,在 data segment descriptor 里它们必须为:
- S = 1
- C/D = 0
指示该 descriptor 是 data segment descriptor,如果尝试加载 S = 0 或者 C/D = 1 的 descriptor 进入 DS,ES,SS,FS 以及 GS 寄存器会产生 #GP 异常
下面的类型属性是无效的:
- E(Expand-Down)
- W(Writable)
- A(Accessed)
那么在 64 位模式下,data segment 被强制为 Expand-Up 和 Writable 的。
2. 64 位模式下的 System segment descriptor 结构
64 位模式下的 system segment descriptor 是 16 bytes 共 128 位,包括:
- 20 位的 segment limit
- 64 位的 segment base
- 12 位的 segment attribute
system segment descriptor 包括了 LDT segment descriptor 和 TSS segment descriptor
在 64 位模式下 user segment descriptor 是 8 bytes,而 system segment descriptor 是 16 bytes 的,它们存放在 GDT 表中就可能会产生了跨 descriptor 边界的问题
2.1 GDT 表中的跨 descriptor 边界
上图显示了在 64 位模式下的一个跨 descriptor 边界产生 #GP 异常的示例:
当使用 selector = 0x20 企图访问一个 user segment descriptor,但是并不如愿,+0x20 位置上并不是一个有效 user segment descriptor,它只是 LDT descriptor 的高半部分
结果会怎样?答案是:未知
因此为了防止这种未知结果的产生,x64 体系中建议:须将 system descirptor(包括 call gate descriptor)的高 64 位中的对应 S 标志和 type 位置上置 00000,但是不包括 interrupt gate 和 trap gate
由于 00000(代表 0 类型的 system descriptor)是无效的 descriptor 类型,因此访问这样的 descriptor 会导致 #GP 异常的发生。从而避免未知结果的产生。
这就是上图中上半部分的 S 和 type 域为何置为 00000 的原因。
当然这个跨 descriptor 边界的情况在 LDT 也可能发生。但是在 IDT 是不可能发生的,那是因为 IDT 只能存在 system descriptor 不可存放 user segment descriptor。因此 IDT 表的索引因子固定为 16,这就是 interrupt gate 和 trap gate 的高半部分 s 和 type 域不用置为 00000 的原因。
2.2 system segment descriptor 属性位
LDT/TSS segment descriptor 大部分属性是有效的,包括:
- type
- S
- DPL
- P
- AVL
- G
它们的 type 包括:
- type = 2 :64-bit LDT
- type = 9 :available 64-bit TSS
- type = B :busy 64-bit TSS
64 位模式的 system segment descriptor 已经不支持 16 位的 TSS,原来的 32 位 TSS 变成了 64 位的 TSS
3. comaptibility mode 下的 system segment descriptor
system segment descriptor 在 compatibility mode 下依旧是 32 位的 descriptor,这和 64 bit 模式下区别很大
在一个可以执行 legacy 32 位程序的 OS 里,应该要准备两份 LDT/TSS segment descriptor:64 位的 LDT/TSS segment descriptor 和 32 位的 LDT/TSS segment descriptor