Protected mode
When your brand new computer boots, it’s still using the old 16bit mode, called “real mode”. One of the first things done by an operating system is to switch to protected mode, which enables 32-bit memory access.
The GDT
The GDT (Global Descriptor Table) is represented as an array of descriptors. Each entry describes a memory segment.
The DPL field describes the privilege level at which the segment is accessible. Usually the kernel runs at privilege level 0, and the userland at privilege level 3.
The Granularity indicates if the limit is interpreted as a byte number (the flag is cleared) or if it’s interpreted as a number of pages (a page is 4KB length).
The first segment is the null segment, it has the same goal as the NULL pointer in C. The GDT entry should be filled with zero.
The TYPE field should be filled with:
Once you filled your GDT, you must inform the processor about it. This is done
with the gdtr
register which can be changed with the lgdt
assembly
instruction:
Segments selectors
The segmentation, set with the GDT, can be used with the segmentation registers:
cs
which is the code selector,eip
is relative to this segmentds
which is the data selector, data read and write are relative to this segment.ss
which is the stack selector,esp
is relative to this segmentes
,fs
,gs
are additional data segments available for the system programmer.
The format is the following:
The segments can only be changed when the RPL and the CPL (which is contained in
the cs
register) are smaller than the DPL of the requested segment.
Switching to protected mode
The first thing to do is to load a GDT into the gdtr
register with the lgdt
instruction. Next, you should set the PE (Protection Enable) bit in the cr0
register:
Since you can’t modify cr0
, you should use a temporary register:
movl $0x12, %cr0 /* It _won't_ assemble */
movl $0x12, %eax
movl %eax, %cr0 /* OK */
Lastly, you should reload all the segment selectors with good values. Like
cr0
, all the selectors except cs
should be set with a temporary register.
cs
is special, you can only alter it with an intersegment jump (ljmp
), or
with an intersegment return (lret
):
pushl $0x42 /* push %cs on the stack */
pushl $1f /* push %eip on the stack */
lret /* far return */
1: /* After the lret you will get here, with cs set to 0x42 */