CLOSE

Entering protected mode in x86 assembly involves several steps, including setting up the Global Descriptor Table (GDT), loading its address into the GDTR register, and then performing a far jump to switch to protected mode. Here's a basic outline of how to enter protected mode:

1. Set up the Global Descriptor Table (GDT)

The GDT is a table that contains descriptors for the segments used in memory management in protected mode. Each descriptor specifies the base address, limit, access rights, and other attributes of a segment.

2. Load the address of the GDT into the GDTR register

The GDTR register holds the base address and limit of the GDT. Before you can use the GDT, you need to load its address into the GDTR register using the lgdt instruction.

3. Set the PE (Protected Mode Enable) bit in the CR0 register

The CR0 register is the control register that controls the operating mode of the CPU. Setting the PE bit in CR0 enables protected mode. You also need to clear the EM (Emulation) bit to disable real mode emulation.

4. Perform a far jump to switch to protected mode

Once the PE bit is set, you can perform a far jump to a new code segment descriptor in the GDT to switch to protected mode. The new code segment descriptor should have the desired privilege level (ring) and point to the code you want to execute in protected mode.

1 Define GDT - utils32/gdt.asm:

; Define the start of the GDT
gdt_begin:

; Null descriptor - mandatory, but not used
gdt_null:
    dd 0x00                   ; 4 bytes: 0x00000000 (low 32 bits)
    dd 0x00                   ; 4 bytes: 0x00000000 (high 32 bits)

; Code segment descriptor
gdt_code:
    dw 0xffff                 ; Segment limit (low 16 bits): 0xFFFF (4 GB limit)
    dw 0x0000                 ; Base address (low 16 bits): 0x0000
    db 0x0                    ; Base address (next 8 bits): 0x00
    db 10011010b              ; Access byte: 10011010b
                              ;  1 0 0 1 1 0 1 0
                              ;  P DPL S Type (Code Segment, Executable, Readable)
                              ;  P = 1 (Segment Present)
                              ;  DPL = 00 (Privilege Level 0)
                              ;  S = 1 (Descriptor Type - Code/Data Segment)
                              ;  Type = 1010 (Code Segment, Executable, Readable)
    db 11001111b              ; Flags and limit (high 4 bits): 1100 (4 KB Granularity, 32-bit)
                              ;  G = 1 (4 KB Granularity)
                              ;  D = 1 (32-bit Protected Mode)
                              ;  0 (Always 0)
                              ;  A = 1 (Accessed)
                              ;  Limit (high 4 bits) = 1111
    db 0x0                    ; Base address (high 8 bits): 0x00

; Data segment descriptor
gdt_data:
    dw 0xffff                 ; Segment limit (low 16 bits): 0xFFFF (4 GB limit)
    dw 0x0000                 ; Base address (low 16 bits): 0x0000
    db 0x0                    ; Base address (next 8 bits): 0x00
    db 10010010b              ; Access byte: 10010010b
                              ;  1 0 0 1 0 0 1 0
                              ;  P DPL S Type (Data Segment, Writable)
                              ;  P = 1 (Segment Present)
                              ;  DPL = 00 (Privilege Level 0)
                              ;  S = 1 (Descriptor Type - Code/Data Segment)
                              ;  Type = 0010 (Data Segment, Writable)
    db 11001111b              ; Flags and limit (high 4 bits): 1100 (4 KB Granularity, 32-bit)
                              ;  G = 1 (4 KB Granularity)
                              ;  D = 1 (32-bit Protected Mode)
                              ;  0 (Always 0)
                              ;  A = 1 (Accessed)
                              ;  Limit (high 4 bits) = 1111
    db 0x0                    ; Base address (high 8 bits): 0x00

; Define the end of the GDT
gdt_end:

; GDT descriptor - tells the CPU the size and location of the GDT
gdt_descriptor:
    dw gdt_end - gdt_begin - 1 ; Limit (size of GDT - 1)
    dd gdt_begin               ; Base address of the GDT

; Segment offsets within the GDT
CODE_SEG equ gdt_code - gdt_begin
DATA_SEG equ gdt_data - gdt_begin

Explanation:

1 Null Descriptor (gdt_null):

  • This is a mandatory entry in the GDT but is not used. It must be all zeros.

2 Code Segment Descriptor (gdt_code):

  • Limit: 0xFFFF (4 GB limit).
  • Base Address: 0x00000000 (not relevant as we are using flat memory model).
  • Access Byte: 10011010b
    • P: Segment Present (1)
    • DPL: Descriptor Privilege Level (0, for kernel mode)
    • S: Descriptor Type (1, for code/data segment)
    • Type: Code Segment, Executable, Readable (1010)
  • Flags and High Limit: 11001111b
    • G: Granularity (1, 4 KB)
    • D: Default Operation Size (1, 32-bit segment)
    • Limit High: (1111, continues from the low limit)

3 Data Segment Descriptor (gdt_data):

  • Similar to the code segment descriptor but with a different access byte to indicate a writable data segment.
  • Access Byte: 10010010b
    • P: Segment Present (1)
    • DPL: Descriptor Privilege Level (0)
    • S: Descriptor Type (1)
    • Type: Data Segment, Writable (0010)

4 GDT Descriptor (gdt_descriptor):

  • Limit: Size of the GDT minus 1 (because the GDT is zero-indexed).
  • Base Address: Starting address of the GDT in memory.

5 Segment Offsets (CODE_SEG, DATA_SEG):

  • Offsets for code and data segments within the GDT. These are used to set up the segment registers when switching to protected mode.

2 Load the GDT in GDT register

This is done by special instruction known as lgdt. By passing it the address of the gdt table.

	cli ; turn off interuptions
    lgdt [gdt_descriptor]

3 Set the Protected Mode Enable Bit

	; Set PE (Protection Enable) bit in CR0 to enter protected mode
    mov eax, cr0
    or eax, 0x1
    mov cr0, eax

4 Do Far Jump

[bits 16]

start:
    cli                        ; Clear interrupts

    ; Set up GDT
    lgdt [gdt_descriptor]

    ; Set PE (Protection Enable) bit in CR0 to enter protected mode
    mov eax, cr0
    or eax, 0x1
    mov cr0, eax

    ; Far jump to clear the prefetch queue and enter protected mode
    jmp CODE_SEG:init_pm

[bits 32]

init_pm:
    ; Initialize data segments
    mov ax, DATA_SEG
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    ; Initialize stack pointer
    mov esp, 0x90000

    ; Print a message in protected mode (you need a function for this)
    ; Here we simply loop to halt the CPU
    jmp $

5 utils/switch_to_pm.asm

[bits 16]
switch_to_pm:
    cli ; turn off interuptions
    lgdt [gdt_descriptor]
    mov eax, cr0
    or eax, 0x1
    mov cr0, eax
    call CODE_SEG:init_pm

[bits 32]
init_pm:
    mov ax, DATA_SEG
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    mov ebp, 0x90000 ; stack's init
    mov esp, ebp
    
    call begin_pm

6 kernel.asm

[org 0x1000]
[bits 16]

start:
    cli                        ; Clear interrupts
    cld                        ; Clear direction flag

    ; Set up GDT
    lgdt [gdt_descriptor]

    ; Set PE (Protection Enable) bit in CR0 to enter protected mode
    mov eax, cr0
    or eax, 0x1
    mov cr0, eax

    ; Far jump to clear the prefetch queue and enter protected mode
    jmp CODE_SEG:init_pm

[bits 32]

init_pm:
    ; Initialize data segments
    mov ax, DATA_SEG
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    ; Initialize stack pointer
    mov ebp, 0x90000
    mov esp, ebp

    ; Print a message in protected mode (you need a function for this)
   
    mov ebx, MSG_PROT_MODE
    call print_string_pm
    
    ; Here we simply loop to halt the CPU
    jmp $

; GDT setup
gdt_begin:

gdt_null:
    dd 0x00                   ; Null descriptor (mandatory)
    dd 0x00

gdt_code:
    dw 0xffff                 ; Segment limit (low 16 bits)
    dw 0x0000                 ; Base address (low 16 bits)
    db 0x0                    ; Base address (next 8 bits)
    db 10011010b              ; Access byte
                              ;  1 0 0 1 1 0 1 0
                              ;  P DPL S Type (Code Segment, Executable, Readable)
                              ;  P = 1 (Segment Present)
                              ;  DPL = 00 (Privilege Level 0)
                              ;  S = 1 (Descriptor Type - Code/Data Segment)
                              ;  Type = 1010 (Code Segment, Executable, Readable)
    db 11001111b              ; Flags and limit (high 4 bits)
                              ;  G = 1 (4 KB Granularity)
                              ;  D = 1 (32-bit Protected Mode)
                              ;  0 (Always 0)
                              ;  A = 1 (Accessed)
                              ;  Limit (high 4 bits) = 1111
    db 0x0                    ; Base address (high 8 bits)

gdt_data:
    dw 0xffff                 ; Segment limit (low 16 bits)
    dw 0x0000                 ; Base address (low 16 bits)
    db 0x0                    ; Base address (next 8 bits)
    db 10010010b              ; Access byte
                              ;  1 0 0 1 0 0 1 0
                              ;  P DPL S Type (Data Segment, Writable)
                              ;  P = 1 (Segment Present)
                              ;  DPL = 00 (Privilege Level 0)
                              ;  S = 1 (Descriptor Type - Code/Data Segment)
                              ;  Type = 0010 (Data Segment, Writable)
    db 11001111b              ; Flags and limit (high 4 bits)
                              ;  G = 1 (4 KB Granularity)
                              ;  D = 1 (32-bit Protected Mode)
                              ;  0 (Always 0)
                              ;  A = 1 (Accessed)
                              ;  Limit (high 4 bits) = 1111
    db 0x0                    ; Base address (high 8 bits)

gdt_end:

; GDT descriptor
gdt_descriptor:
    dw gdt_end - gdt_begin - 1 ; Limit (size of GDT - 1)
    dd gdt_begin               ; Base address of the GDT

; Segment offsets within the GDT
CODE_SEG equ gdt_code - gdt_begin
DATA_SEG equ gdt_data - gdt_begin

MSG_PROT_MODE:
    db 'Switched to prot mode', 0

7 utils32/print.asm

[bits 32]

VIDEO_MEMORY equ 0xb8000
WHITE_ON_BLACK equ 0x0f

print_string_pm:
    pusha
    mov edx, VIDEO_MEMORY
print_string_pm_cycle:
    cmp [ebx], BYTE 0
    je print_string_pm_end
    mov ah, WHITE_ON_BLACK
    mov al, [ebx]
    mov [edx], ax

    add ebx, 1
    add edx, 2

    jmp print_string_pm_cycle
print_string_pm_end:
    popa
    ret

8 Output