Usage of SS register in Long Mode
Posted: Tue Jun 02, 2026 10:35 am
Hello,
I am implementing a toy user space (R3) and having trouble to understand the usage of SS in long mode.
Here what I have found:
Windows segments in R0 (source: Windows Internals)
Since AMD explicitly says that DS, ES, and SS are ignored and Windows keep the R3 DS selector in kernel to avoid cost, why Windows save the old (R3) SS selector in syscall?
source: Windows internals
Thanks.
I am implementing a toy user space (R3) and having trouble to understand the usage of SS in long mode.
Here what I have found:
Windows segments in R0 (source: Windows Internals)
AMD on Segment Registers in 64-Bit Mode:
Lazy segment loading
Based on the description and values of the segments described earlier, it may be
surprising to investigate the values of DS and ES on an x86 and/or x64 system
and find that they do not necessarily match the defined values for their
respective ring levels. For example, an x86 user-mode thread would have the
following segments:
CS = 1Bh (18h | 3)
ES, DS = 23 (20h | 3)
FS = 3Bh (38h | 3)
Yet, during a system call in Ring 0, the following segments would be found:
CS = 08h (08h | 0)
ES, DS = 23 (20h | 3)
FS = 30h (30h | 0)
Similarly, an x64 thread executing in kernel mode would also have its ES and
DS segments set to 2Bh (28h | 3). This discrepancy is due to a feature known as
lazy segment loading and reflects the meaninglessness of the Descriptor
Privilege Level (DPL) of a data segment when the current Code Privilege Level
(CPL) is 0 combined with a system operating under a flat memory model. Since
a higher CPL can always access data of a lower DPL—but not the contrary—
setting DS and/or ES to their “proper” values upon entering the kernel would
also require restoring them when returning to user mode.
Although the MOV DS, 10h instruction seems trivial, the processor’s
microcode needs to perform a number of selector correctness checks when
encountering it, which would add significant processing costs to system call and
interrupt handling. As such, Windows always uses the Ring 3 data segment
values, avoiding these associated costs.
DS, ES, and SS Registers in 64-Bit Mode. In 64-bit mode, the contents of the ES, DS, and SS
segment registers are ignored. All fields (base, limit, and attribute) in the hidden portion of the
segment registers are ignored.
Address calculations in 64-bit mode that reference the ES, DS, or SS segments are treated as if the
segment base is 0. Instead of performing limit checks, the processor checks that all virtual-address
references are in canonical form.
Neither enabling and activating long mode nor switching between 64-bit and compatibility modes
changes the contents of the visible or hidden portions of the segment registers. These registers remain
unchanged during 64-bit mode execution unless explicit segment loads are performed.
Since AMD explicitly says that DS, ES, and SS are ignored and Windows keep the R3 DS selector in kernel to avoid cost, why Windows save the old (R3) SS selector in syscall?
source: Windows internals
Has anyone tried to fill SS in STAR ( C000_0081h ) with a R3 selector? Or keep R0 SS selector in user mode?Now that the kernel stack is loaded, the function builds a trap frame, using the
format described earlier in the chapter. This includes storing in the frame the
SegSs set to KGDT_R3_DATA (0x2B), Rsp from the UserRsp in the PCR,
EFlags from R11, SegCs set to KGDT_R3_CODE (0x33), and storing Rip from
RCX. Normally, a processor trap would’ve set these fields, but Windows must
emulate the behavior based on how syscall operates.
Thanks.