Why segmentation cannot be completely disable?

旧时模样 提交于 2019-12-01 22:39:26

Introduction

In 64-bit mode, whenever a non-null segment selector is loaded into any of the segment registers, the processor automatically loads the corresponding segment descriptor in the hidden part of the segment register, just like in protected/compatibility mode. However, the segment descriptors selected by the DS, ES, or SS selectors are completely ignored. Also the limit and attribute fields of the segment descriptors selected by the FS and GS selectors are ignored.

Intel Manual V3 3.4.4:

Because ES, DS, and SS segment registers are not used in 64-bit mode, their fields (base, limit, and attribute) in segment descriptor registers are ignored. Some forms of segment load instructions are also invalid (for example, LDS, POP ES). Address calculations that reference the ES, DS, or SS segments are treated as if the segment base is zero.

...

In 64-bit mode, memory accesses using FS-segment and GS-segment overrides are not checked for a runtime limit nor subjected to attribute-checking.

Other than that, it's assumed that the base address of each of these segments to be 0 and the length to be 264. However, some parts of the segment descriptors selected by the CS, FS, or GS selectors still take effect. In particular, the base addresses of FS and GS specified in their respective descriptors are used.

Intel Manual V3 3.4.4:

When FS and GS segment overrides are used in 64-bit mode, their respective base addresses are used in the linear address calculation.

In addition, the following fields of the CS descriptor are used: D (default bit), L (64-bit sub-mode bit), AVL (OS bits), P (present bit), DPL (privilege level bits), S (system bit), D/C (data/code bit), and C (conforming bit). Note that the base address of CS is fixed at 0 and the lengths of CS, FS, and GS are all fixed at 264. As Peter indicated in his comment, the L and D bits of the CS descriptor are required to be able to switch between the different sub-modes of the long mode. The other active fields of CS are also useful. Supporting different base addresses for FS and GS is useful for things like thread-local storage.

Intel Manual V3 5.2.1:

Code segments continue to exist in 64-bit mode even though, for address calculations, the segment base is treated as zero. Some code-segment (CS) descriptor content (the base address and limit fields) is ignored; the remaining fields function normally (except for the readable bit in the type field).

Code segment descriptors and selectors are needed in IA-32e mode to establish the processor’s operating mode and execution privilege-level.

I think that both the readable bit and accessed bit are ignored in 64-bit mode. These attributes are replaced by the corresponding attributes in the paging structures. Although I couldn't find anywhere in the Intel manual that says that the accessed bit is ignored. But the AMD manual does state that clearly.

Descriptor table limit checks are still performed.

Intel Manual V3 5.3.1:

In 64-bit mode, the processor does not perform runtime limit checking on code or data segments. However, the processor does check descriptor-table limits.

So you could say that segmentation is completely disabled for the DS, ES, and SS segments. But not exactly for the other three segments. That's what segmentation cannot be completely disabled means.

Intel Manual V2 Says Otherwise

I quote from the description of the POP instruction.

64-Bit Mode Exceptions

#GP(0) If the memory address is in a non-canonical form.
#SS(0) If the stack address is in a non-canonical form.
#GP(selector) If the descriptor is outside the descriptor table limit.
If the FS or GS register is being loaded and the segment pointed to is not a data or readable code segment.
If the FS or GS register is being loaded and the segment pointed to is a data or nonconforming code segment, but both the RPL and the CPL are greater than the DPL.
#AC(0) If an unaligned memory reference is made while alignment checking is enabled.
#PF(fault-code) If a page fault occurs.
#NP If the FS or GS register is being loaded and the segment pointed to is marked not present.
#UD If the LOCK prefix is used.

Note that POPs to DS, ES, SS are not valid in 64-bit mode, and there is no POP CS. That's why it only talks about FS and GS. Although this implies that the attributes of the descriptors selected by FS and GS are not completely ignored.

Similarly, the description of the MOV instruction says:

64-Bit Mode Exceptions

#GP(0)
If the memory address is in a non-canonical form.
If an attempt is made to load SS register with NULL segment selector when CPL = 3.
If an attempt is made to load SS register with NULL segment selector when CPL < 3 and CPL ≠ RPL.
#GP(selector)
If segment selector index is outside descriptor table limits. If the memory access to the descriptor table is non-canonical.
If the SS register is being loaded and the segment selector's RPL and the segment descriptor’s DPL are not equal to the CPL.
If the SS register is being loaded and the segment pointed to is a nonwritable data segment.
If the DS, ES, FS, or GS register is being loaded and the segment pointed to is not a data or readable code segment.
If the DS, ES, FS, or GS register is being loaded and the segment pointed to is a data or nonconforming code segment, but both the RPL and the CPL are greater than the DPL.
#SS(0) If the stack address is in a non-canonical form.
#SS(selector) If the SS register is being loaded and the segment pointed to is marked not present.
#PF(fault-code) If a page fault occurs.
#AC(0) If alignment checking is enabled and an unaligned memory reference is made while the current privilege level is 3.
#UD If attempt is made to load the CS register. If the LOCK prefix is used.

But notice that #NP does not occur here! This suggests that the present bit (P) is only checked for FS, GS, CS, and SS, but not for DS and ES. (But I think that the P bit is checked for all of the segments.) These quotes also suggest that the RPL part of the selector of any segment register is also used.

Null Segment Selector

The null segment selector is a selector whose value is 0x0000, 0x0001, 0x0002, or 0x0003. To the processor, all of these values always have the same effect. These all select the same descriptor, entry 0 of GDT.

The null segment selector cannot be loaded into CS in any mode that uses segmentation (including 64-bit mode) because CS must contain an actual selector at all times. An attempt to do that generates a GP exception.

The null segment selector can be loaded into SS in 64-bit mode (in contrast to other modes), but only in certain situations. For more information, refer to the part "General Protection Exception (#GP)" of Intel Manual V3 6.15.

The null segment selector can be loaded into DS, ES, GS, and FS.

Intel Manual V3 5.4.1.1:

In 64-bit mode, the processor does not perform runtime checking on NULL segment selectors. The processor does not cause a #GP fault when an attempt is made to access memory where the referenced segment register has a NULL segment selector.

I find this very interesting as I will explain later. (I also find it weird that Chapter 3, which is dedicated for segmentation, does not state that).

It's not perfectly clear to me whether the processor loads the null descriptor from memory into the invisible part of the segment register when loading it with the null selector.

Intel Manual V3 3.4.2:

The first entry of the GDT is not used by the processor.

Does this mean that the processor will not load the null descriptor? Or perhaps it only means that the contents of the descriptor are not used. Later it says in 3.4.4:

In order to set up compatibility mode for an application, segment-load instructions (MOV to Sreg, POP Sreg) work normally in 64-bit mode. An entry is read from the system descriptor table (GDT or LDT) and is loaded in the hidden portion of the segment register. The descriptor-register base, limit, and attribute fields are all loaded. However, the contents of the data and stack segment selector and the descriptor registers are ignored.

The description of the POP instruction from the Intel Manual V2 says:

64-BIT_MODE

IF FS, or GS is loaded with a NULL selector;
THEN
SegmentRegister ← segment selector;
SegmentRegister ← segment descriptor;
FI;

The description of the MOV instruction from the Intel Manual V2 says:

IF DS, ES, FS, or GS is loaded with NULL selector
THEN
SegmentRegister ← segment selector;
SegmentRegister ← segment descriptor;
FI;

This suggests that the null descriptor does actually get loaded, but its contents are ignored. The Linux kernel defines the null descriptor to have all bits zero. I've read in many articles and textbooks that this is mandatory. However, Collins says that this is not necessary:

The first entry in the Global Descriptor Table (GDT) is called the null descriptor. The NULL descriptor is unique to the GDT, as it has a TI=0, and INDEX=0. Most printed documentation states that this descriptor table entry must be 0. Even Intel is somewhat ambiguous on this subject, never saying what it CAN'T be used for. Intel does state that the 0'th descriptor table entry is never referenced by the processor.

AFAIK, Intel does not impose any restrictions on the contents of the null descriptor. So I guess Collins is right.

Why is 5.4.1.1 interesting?

Because this means it's possible to use DS, ES, GS, and GS to hold any of the constants 0x0000, 0x0001, 0x0002, or 0x0003, in 64-bit mode. It's guaranteed that the GDT contains at least the null descriptor, so descriptor table limit check will pass (this may not be true with other selectors). In addition, all references to any of these segments will still be performed successfully. The MOV instruction can be used to move a value from a segment register to a GPR and then performing an operation on it.

AMD Manual

To be written.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!