Protected Mode
Disclaimer
Guide Reference
Protected Mode is the main operating mode of modern Intel processors.
This mode, also called 32-bit mode, when enabled permit to unleash the real power of the CPU. However, it will prevent really low-level calls like BIOS interrupts.
Limitations
The limitations are:
- Very complicated initial setup.
- No direct access to hardware from userspace.
- Complex virtual memory management.
- Debugging very difficult.
- Not compatible with Real Mode code.
Advantages
The advantages are:
- Memory isolation and protection with segmentation and paging.
- Support for 3 Privilege Levels.
- Secure Multitasking Support.
- Kernel bug protection.
Intel Manual Reference
To enter in Protected Mode, following the official Intel manual, you have to follow these steps:
- Disable interrupts
- Load the GDT
- Set the PE flag in the register cr0 (the Protected mode bit is set, so with value 1) (The cr0 is the Control Register 0)
- Execute a far jump immediately after the cr0 set
- If paging is enabled, the cr0 set and the far jump must be from a page that is identity mapped (that is, the linear address before the jump is the same as the physical address after paging and protected mode is enabled)
- If a LDT is going to be used, load the LDT
- After the jump, in Protected Mode, reset the segment registers values (DS, SS, ES, FS and GS)
- If an IDT is going to be used, load the IDT
- If loaded an IDT enable maskable hardware interrupts and NMI interrupts (Non Maskable interrupts) (STI command)
The Manual also say that if other instructions exists between the cr0 set and the far jump, there may be random failures.
Code example
[ORG 0x1000]
[BITS 16]
CODE_SEG equ codeDescriptor - GDTStart
DATA_SEG equ dataDescriptor - GDTStart
cli
lgdt [GDTDescriptor]
mov eax, cr0
or eax, 1
mov cr0, eax
jmp CODE_SEG:startProtectedMode
jmp $
align 8
GDTStart:
nullDescriptor:
dd 0
dd 0
codeDescriptor:
dw 0xffff
dw 0
db 0
db 0b10011010
db 0b11001111
db 0
dataDescriptor:
dw 0xffff
dw 0
db 0
db 0b10010010
db 0b11001111
db 0
GDTEnd:
GDTDescriptor:
dw GDTEnd - GDTStart - 1
dd GDTStart
[BITS 32]
startProtectedMode:
jmp $Explanation
Okay, I know, it's scary, but don't let this get you down. This code took me months to write, but now I'll explain it to you in the simplest and most complete way possible.
This program enter in Protected Mode. Yes, it doesn't do anything else.
If you try to copy the entire code, compile it, and run it, it won't work because it's not designed to run in the first-stage bootloader. I ran it in the second stage of my bootloader, so I know it works perfectly. For more information on multi-stage bootloaders, I refer you to the dedicated guide.
Note that all parts of this code are actually required to properly enter Protected Mode. Certain values can be changed, and there's obviously room for programming style, but skipping steps described in this code risks crashing the system.
Before to pass to the code explanation, you need to know the concept of GDT (Global Descriptor Table). I recommand reading the Global Descriptor Table guide before continue.
Below is a simple but very detailed explanation of the program.
[ORG 0x1000] ; Organise the program with an offset of 0x1000 (starts from 0x1000)
[BITS 16] ; The code is in Real ModeCODE_SEG equ codeDescriptor - GDTStart ; CODE_SEG = relative address of the code descriptor in the GDT
DATA_SEG equ dataDescriptor - GDTStart ; DATA_SEG = relative address of the data descriptor in the GDTcli ; Clear Interrupt - Disable BIOS interrupt
lgdt [GDTDescriptor] ; Load the GDT, saved at the GDTDescriptor addressmov eax, cr0 ; Save in eax (32-bit register) the value of cr0 (Control Register 0)
or eax, 1 ; Set the bit 0 to 1 (cr0 has 32 bits, the bit 0 is the "Protected Mode Enable" bit)
mov cr0, eax ; Update the cr0 value with the eax updated valueThe piece of code above is the main of the Protected Mode enable process: it enable the Protected Mode by setting the last bit of the cr0 register (the Control Register 0). To do this you need to use these three lines of code as you cannot directly access the value in cr0.
jmp CODE_SEG:startProtectedMode ; Jump to the 32-bit code part using the GDT code info
jmp $ ; Fallback if something get wrongalign 8 ; Align the GDT to 8-bit blocks (REQUIRED!)
GDTStart: ; The address of the start of the GDT
nullDescriptor: ; The null descriptor
dd 0 ; Allocate 32 bits of 0
dd 0 ; Allocate 32 bits of 0
codeDescriptor: ; The code descriptor
dw 0xffff ; Allocate 16 bits with value 0xFFFF (all bits to 1)
dw 0 ; Allocate 16 bits of 0
db 0 ; Allocate 8 bits of 0
db 0b10011010 ; Allocate 8 bits with value 10011010
db 0b11001111 ; Allocate 8 bits with value 11001111
db 0 ; Allocate 8 bits of 0
dataDescriptor: ; The data descriptor
dw 0xffff ; Allocate 16 bits with value 0xFFFF (all bits to 1)
dw 0 ; Allocate 16 bits of 0
db 0 ; Allocate 8 bits of 0
db 0b10010010 ; Allocate 8 bits with value 10010010
db 0b11001111 ; Allocate 8 bits with value 11001111
db 0 ; Allocate 8 bits of 0
GDTEnd: ; The address of the end of the GDTFor the detailed explaination of the GDT, please read the Global Descriptor Table guide.
GDTDescriptor:
dw GDTEnd - GDTStart - 1 ; The size of the GDT in 16 bits
dd GDTStart ; The 32 bits address of the start of the GDT[BITS 32] ; Finally we are in 32-bits mode (Protected Mode)
startProtectedMode: ; The address where we have to arrive with the far jump
jmp $ ; Stop the programThis part must be at the end of the file, because is the only part in Protected Mode.