Entering Long Mode Directly: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
m (→‎Demo Code: Style up code)
(Re-wrote the entire article such that it describes the bare minimal required. Also included a example bootsector if anyone wants that.)
Line 1: Line 1:
For some reason, you might want to switch to 64 bit mode in your bootloader without entering [[protected mode]]. The following article practically described how to do so, mainly demonstrating it by example assembly code.
== Demo Code ==


== Switching to Long Mode ==
The following NASM code demonstrates how to boot into 64 bit mode without entering [[protected mode]]:

The following can be used to switch directly to Long Mode. The code is well documented through comments, and nearly explains almost everything it does.


<source lang="asm">
<source lang="asm">
%define PAGE_PRESENT (1 << 0)
[ORG 0x00007C00]
%define PAGE_WRITE (1 << 1)
[BITS 16]


%define CODE_SEG 0x0008
%define DATA_SEG 0x0010


ALIGN 4
IDT:
.Length dw 0
.Base dd 0


; Function to switch directly to long mode from real mode.
boot_loader:
; Identity maps the first 2MiB.
;Parameter from BIOS: dl = boot drive
; Uses Intel syntax.


; es:edi Should point to a valid page-aligned 16KiB buffer, for the PML4, PDPT, PD and a PT.


SwitchToLongMode:
;Set default state
; Zero out the 16KiB buffer.
; Since we are doing a rep stosd, count should be bytes/4.
push di ; REP STOSD alters DI.
mov ecx, 0x1000
xor eax, eax
rep stosd
pop di ; Get DI back.


cli
xor bx,bx
mov es,bx
mov fs,bx
mov gs,bx
mov ds,bx
mov ss,bx
mov sp,0x7C00
sti


; Build the Page Map Level 4.
jmp 0:.clear_cs
; es:di points to the Page Map Level 4 table.
.clear_cs:
lea eax, [es:di + 0x1000] ; Put the address of the Page Directory Pointer Table in to EAX.
or eax, PAGE_PRESENT | PAGE_WRITE ; Or EAX with the flags - present flag, writable flag.
mov [es:di], eax ; Store the value of EAX as the first PML4E.




; Build the Page Directory Pointer Table.
;Load kernel from floppy disk
lea eax, [es:di + 0x2000] ; Put the address of the Page Directory in to EAX.
or eax, PAGE_PRESENT | PAGE_WRITE ; Or EAX with the flags - present flag, writable flag.
mov [es:di + 0x1000], eax ; Store the value of EAX as the first PDPTE.


mov ax,0x020D ;ah = Function 0x02 ;al = Number of sectors
; Build the Page Directory.
mov bx,startLongMode ;es:bx = Destination
lea eax, [es:di + 0x3000] ; Put the address of the Page Table in to EAX.
mov cx,0x0002 ;cx = Cylinder and sector
or eax, PAGE_PRESENT | PAGE_WRITE ; Or EAX with the flags - present flag, writeable flag.
xor dh,dh ;dx = Head and drive number
mov [es:di + 0x2000], eax ; Store to value of EAX as the first PDE.
int 0x13 ;Int 0x13 Function 0x02 (Load sectors)
push di ; Save DI for the time being.
lea di, [di + 0x3000] ; Point DI to the page table.
mov eax, PAGE_PRESENT | PAGE_WRITE ; Move the flags into EAX - and point it to 0x0000.




; Build the Page Table.
;Enable A20 via port 92h
.LoopPageTable:
mov [es:di], eax
add eax, 0x1000
add di, 8
cmp eax, 0x200000 ; If we did all 2MiB, end.
jb .LoopPageTable


pop di ; Restore DI.
in al,92h
or al,02h
; Disable IRQs
out 92h,al
mov al, 0xFF ; Out 0xFF to 0xA1 and 0x21 to disable all IRQs.
out 0xA1, al
out 0x21, al
nop
nop


lidt [IDT] ; Load a zero length IDT so that any NMI causes a triple fault.


; Enter long mode.
;Build page tables
mov eax, 10100000b ; Set the PAE and PGE bit.
;The page tables will look like this:
mov cr4, eax
;PML4:
;dq 0x000000000000b00f = 00000000 00000000 00000000 00000000 00000000 00000000 10010000 00001111
mov edx, edi ; Point CR3 at the PML4.
;times 511 dq 0x0000000000000000
mov cr3, edx
mov ecx, 0xC0000080 ; Read from the EFER MSR.
rdmsr


or eax, 0x00000100 ; Set the LME bit.
;PDP:
wrmsr
;dq 0x000000000000c00f = 00000000 00000000 00000000 00000000 00000000 00000000 10100000 00001111
;times 511 dq 0x0000000000000000
mov ebx, cr0 ; Activate long mode -
or ebx,0x80000001 ; - by enabling paging and protection simultaneously.
mov cr0, ebx


lgdt [GDT.Pointer] ; Load GDT.Pointer defined below.
;PD:
;dq 0x000000000000018f = 00000000 00000000 00000000 00000000 00000000 00000000 00000001 10001111
jmp CODE_SEG:LongMode ; Load CS with 64 bit segment and flush the instruction cache
;times 511 dq 0x0000000000000000
; Global Descriptor Table
GDT:
.Null:
dq 0x0000000000000000 ; Null Descriptor - should be present.


.Code:
;This defines one 2MB page at the start of memory, so we can access the first 2MBs as if paging was disabled
dq 0x0020980000000000 ; 64-bit code descriptor.
dq 0x0000900000000000 ; 64-bit data descriptor.
ALIGN 4
dw 0 ; Padding to make the "address of the GDT" field aligned on a 4-byte boundary


.Pointer:
xor bx,bx
dw $ - GDT - 1 ; 16-bit Size (Limit) of GDT.
mov es,bx
dd GDT ; 32-bit Base Address of GDT. (CPU will zero extend to 64-bit)
cld
mov di,0xa000


mov ax,0xb00f
stosw


[BITS 64]
xor ax,ax
LongMode:
mov cx,0x07ff
mov ax, DATA_SEG
rep stosw
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax


; Blank out the screen to a blue color.
mov ax,0xc00f
mov edi, 0xB8000
stosw
mov rcx, 500 ; Since we are clearing QWORDs over here, we put the count as Count/4.
mov rax, 0x1F201F201F201F20 ; Set the value to set the screen to: Blue background, white foreground, blank spaces.
rep stosq ; Clear the entire screen.
; Display "Hello World!"
mov edi, 0x00b8000
mov rax, 0x1F6C1F6C1F651F48
mov [edi],rax
mov rax, 0x1F6F1F571F201F6F
mov [edi + 8], rax


mov rax, 0x1F211F641F6C1F72
xor ax,ax
mov [edi + 16], rax
mov cx,0x07ff
rep stosw
jmp Main.Long ; You should replace this jump to wherever you want to jump to.
</source>


=== Notes about the above code ===
mov ax,0x018f
# The above code assumes that the caller has checked whether the CPU supports x86_64 or not.
stosw
# As noted in code comments, only 2MB is mapped. The code can be easily modified to map the kernel to higher half.
# The code doesn't include BPB for floppies, or any such thing. This also can be easily added.


== Example bootsector ==
xor ax,ax
mov cx,0x07ff
rep stosw


The following example bootsector has been provided for completeness. It checks whether the CPU supports x86_64 or not. It then calls SwitchToLongMode to switch to long mode.


<source lang="asm">
;Enter long mode
%define FREE_SPACE 0x9000


ORG 0x7C00
mov eax,10100000b ;Set PAE and PGE
BITS 16
mov cr4,eax


; Main entry point where BIOS leaves us.
mov edx, 0x0000a000 ;Point CR3 at PML4
mov cr3,edx


Main:
mov ecx,0xC0000080 ;Specify EFER MSR
jmp 0x0000:.FlushCS ; Some BIOS' may load us at 0x0000:0x7C00 while other may load us at 0x07C0:0x0000.
; Do a far jump to fix this issue, and reload CS to 0x0000.


.FlushCS:
rdmsr ;Enable Long Mode
cli ; Clear interrupts, direction flag and the carry flag.
or eax,0x00000100
cld
wrmsr
; Set up segment registers.
xor ax, ax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax


mov ss, ax
mov ebx,cr0 ;Activate long mode
; Set up stack so that it starts below Main.
or ebx,0x80000001 ;by enabling paging and protection simultaneously
mov sp, Main
mov cr0,ebx ;skipping protected mode entirely


sti ; Enable interrupts again.
lgdt [gdt.pointer] ;load 80-bit gdt.pointer below


call CheckCPU ; Check whether we support Long Mode or not.
jmp gdt.code:startLongMode ;Load CS with 64 bit segment and flush the instruction cache
jc .NoLongMode


; Point edi to a free space bracket.
mov edi, FREE_SPACE
; Switch to Long Mode.
jmp SwitchToLongMode




BITS 64
;Global Descriptor Table
.Long:
gdt:
hlt
dq 0x0000000000000000 ;Null Descriptor
jmp .Long


.code equ $ - gdt
dq 0x0020980000000000


BITS 16
.data equ $ - gdt
dq 0x0000900000000000


.NoLongMode:
.pointer:
mov si, NoLongMode
dw $-gdt-1 ;16-bit Size (Limit)
call Print
dq gdt ;64-bit Base Address
;Changed from "dd gdt"
;Ref: Intel System Programming Manual V1 - 2.1.1.1


.Die:
hlt
jmp .Die


times 510-($-$$) db 0 ;Fill boot sector
dw 0xAA55 ;Boot loader signature


%include "LongModeDirectly.asm"
BITS 16


[BITS 64]


NoLongMode db "ERROR: CPU does not support long mode.", 0x0A, 0x0D, 0
startLongMode:


cli ;Interupts are disabled because no IDT has been set up


; Checks whether CPU supports long mode or not.
mov edi,0x00b8000 ;Display:"Put long mode kernel here.__"
mov rax,0x0720077407750750 ;at the top left corner of screen
mov [edi],rax
mov rax,0x0767076e076f076c
mov [edi+8],rax
mov rax,0x0764076f076d0720
mov [edi+16],rax
mov rax,0x0765076b07200765
mov [edi+24],rax
mov rax,0x076c0765076e0772
mov [edi+32],rax
mov rax,0x0772076507680720
mov [edi+40],rax
mov rax,0x07200720072e0765
mov [edi+48],rax


; Returns with carry set if CPU doesn't support long mode.
jmp $ ;Hang the system

CheckCPU:
; Check whether CPUID is supported or not.
pushfd ; Get flags in EAX register.
pop eax
mov ecx, eax
xor eax, 0x200000
push eax
popfd

pushfd
pop eax
xor eax, ecx
shr eax, 21
and eax, 1 ; Check whether bit 21 is set or not. If EAX now contains 0, CPUID isn't supported.
push ecx
popfd

test eax, eax
jz .NoLongMode
mov eax, 0x80000000
cpuid
cmp eax, 0x80000001 ; Check whether extended function 0x80000001 is available are not.
jb .NoLongMode ; If not, long mode not supported.

mov eax, 0x80000001
cpuid
test edx, 1 << 29 ; Test if the LM-bit, is set or not.
jz .NoLongMode ; If not Long mode not supported.

ret

.NoLongMode:
stc
ret


; Prints out a message using the BIOS.

; es:si Address of ASCIIZ string to print.

Print:
pushad
.PrintLoop:
lodsb ; Load the value at [@es:@si] in @al.
test al, al ; If AL is the terminator character, stop printing.
je .PrintDone
mov ah, 0x0E
int 0x10
jmp .PrintLoop ; Loop till the null character not found.
.PrintDone:
popad ; Pop all general purpose registers to save them.
ret


; Pad out file.
times 510 - ($-$$) db 0
dw 0xAA55
</source>
</source>



== Notes about the code ==
The above code can be compiled (and tested using QEMU) by the following:
# This code should work fine if being run as MBR (with all segment registers set to zero)

# As noted in code comments, only 2MB is mapped
<source lang="asm">
# The code doesn't check CPU type, so x86 CPU (ie .486 or before) won't fit to run
nasm -fbin Main.asm -o LongModeDirectly ; The main file's name should be Main.asm
qemu-system-x86_64 -hda LongModeDirectly ; The secondary file's name should be LongModeDirectly.asm and should be in the same directory
</source>

=== Notes about the above bootsector ===
# The above bootsector has been written just for completeness.
# The above bootsector can't be used as an actual bootsector as it doesn't:
# Enable [[A20]]
# Detect memory
# Load required files from disk using BIOS


== See Also ==
== See Also ==
[http://www.osdev.org/phpBB2/viewtopic.php?t=11093 Related forum thread]
[http://www.osdev.org/phpBB2/viewtopic.php?t=11093 Related forum thread]



[[Category:X86-64]]
[[Category:X86-64]]

Revision as of 12:11, 10 July 2011

For some reason, you might want to switch to 64 bit mode in your bootloader without entering protected mode. The following article practically described how to do so, mainly demonstrating it by example assembly code.

Switching to Long Mode

The following can be used to switch directly to Long Mode. The code is well documented through comments, and nearly explains almost everything it does.

%define PAGE_PRESENT    (1 << 0)
%define PAGE_WRITE      (1 << 1)

%define CODE_SEG     0x0008
%define DATA_SEG     0x0010

ALIGN 4
IDT:
    .Length       dw 0
    .Base         dd 0

; Function to switch directly to long mode from real mode.
; Identity maps the first 2MiB.
; Uses Intel syntax.

; es:edi    Should point to a valid page-aligned 16KiB buffer, for the PML4, PDPT, PD and a PT.

SwitchToLongMode:
    ; Zero out the 16KiB buffer.
    ; Since we are doing a rep stosd, count should be bytes/4.   
    push di                           ; REP STOSD alters DI.
    mov ecx, 0x1000
    xor eax, eax
    rep stosd
    pop di                            ; Get DI back.


    ; Build the Page Map Level 4.
    ; es:di points to the Page Map Level 4 table.
    lea eax, [es:di + 0x1000]         ; Put the address of the Page Directory Pointer Table in to EAX.
    or eax, PAGE_PRESENT | PAGE_WRITE ; Or EAX with the flags - present flag, writable flag.
    mov [es:di], eax                  ; Store the value of EAX as the first PML4E.


    ; Build the Page Directory Pointer Table.
    lea eax, [es:di + 0x2000]         ; Put the address of the Page Directory in to EAX.
    or eax, PAGE_PRESENT | PAGE_WRITE ; Or EAX with the flags - present flag, writable flag.
    mov [es:di + 0x1000], eax         ; Store the value of EAX as the first PDPTE.

    
    ; Build the Page Directory.
    lea eax, [es:di + 0x3000]         ; Put the address of the Page Table in to EAX.
    or eax, PAGE_PRESENT | PAGE_WRITE ; Or EAX with the flags - present flag, writeable flag.
    mov [es:di + 0x2000], eax         ; Store to value of EAX as the first PDE.
    
    
    push di                           ; Save DI for the time being.
    lea di, [di + 0x3000]             ; Point DI to the page table.
    mov eax, PAGE_PRESENT | PAGE_WRITE    ; Move the flags into EAX - and point it to 0x0000.


    ; Build the Page Table.
.LoopPageTable:
    mov [es:di], eax
    add eax, 0x1000
    add di, 8
    cmp eax, 0x200000                 ; If we did all 2MiB, end.
    jb .LoopPageTable

    pop di                            ; Restore DI.
    
    ; Disable IRQs
    mov al, 0xFF                      ; Out 0xFF to 0xA1 and 0x21 to disable all IRQs.
    out 0xA1, al
    out 0x21, al
    
    nop
    nop

    lidt [IDT]                        ; Load a zero length IDT so that any NMI causes a triple fault.

    ; Enter long mode.
    mov eax, 10100000b                ; Set the PAE and PGE bit.
    mov cr4, eax
      
    mov edx, edi                      ; Point CR3 at the PML4.
    mov cr3, edx
      
    mov ecx, 0xC0000080               ; Read from the EFER MSR. 
    rdmsr    

    or eax, 0x00000100                ; Set the LME bit.
    wrmsr
      
    mov ebx, cr0                      ; Activate long mode -
    or ebx,0x80000001                 ; - by enabling paging and protection simultaneously.
    mov cr0, ebx                    

    lgdt [GDT.Pointer]                ; Load GDT.Pointer defined below.
      
    jmp CODE_SEG:LongMode             ; Load CS with 64 bit segment and flush the instruction cache
      
      
    ; Global Descriptor Table
GDT:
.Null:
    dq 0x0000000000000000             ; Null Descriptor - should be present.

.Code:
    dq 0x0020980000000000             ; 64-bit code descriptor. 
    dq 0x0000900000000000             ; 64-bit data descriptor. 
      
ALIGN 4
    dw 0                              ; Padding to make the "address of the GDT" field aligned on a 4-byte boundary

.Pointer:
    dw $ - GDT - 1                    ; 16-bit Size (Limit) of GDT.
    dd GDT                            ; 32-bit Base Address of GDT. (CPU will zero extend to 64-bit)


[BITS 64]      
LongMode:
    mov ax, DATA_SEG
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    ; Blank out the screen to a blue color.
    mov edi, 0xB8000
    mov rcx, 500                      ; Since we are clearing QWORDs over here, we put the count as Count/4.
    mov rax, 0x1F201F201F201F20       ; Set the value to set the screen to: Blue background, white foreground, blank spaces.
    rep stosq                         ; Clear the entire screen. 
 
    ; Display "Hello World!"
    mov edi, 0x00b8000              
    
    mov rax, 0x1F6C1F6C1F651F48    
    mov [edi],rax
    
    mov rax, 0x1F6F1F571F201F6F
    mov [edi + 8], rax

    mov rax, 0x1F211F641F6C1F72
    mov [edi + 16], rax
    
    jmp Main.Long                     ; You should replace this jump to wherever you want to jump to.

Notes about the above code

  1. The above code assumes that the caller has checked whether the CPU supports x86_64 or not.
  2. As noted in code comments, only 2MB is mapped. The code can be easily modified to map the kernel to higher half.
  3. The code doesn't include BPB for floppies, or any such thing. This also can be easily added.

Example bootsector

The following example bootsector has been provided for completeness. It checks whether the CPU supports x86_64 or not. It then calls SwitchToLongMode to switch to long mode.

%define FREE_SPACE 0x9000

ORG 0x7C00
BITS 16

; Main entry point where BIOS leaves us.

Main:
    jmp 0x0000:.FlushCS               ; Some BIOS' may load us at 0x0000:0x7C00 while other may load us at 0x07C0:0x0000.
                                      ; Do a far jump to fix this issue, and reload CS to 0x0000.

.FlushCS:
    cli                               ; Clear interrupts, direction flag and the carry flag.
    cld
    
    ; Set up segment registers.
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    mov ss, ax
    ; Set up stack so that it starts below Main.
    mov sp, Main

    sti                               ; Enable interrupts again.

    call CheckCPU                     ; Check whether we support Long Mode or not.
    jc .NoLongMode

    ; Point edi to a free space bracket.
    mov edi, FREE_SPACE
    ; Switch to Long Mode.
    jmp SwitchToLongMode


BITS 64
.Long:
    hlt
    jmp .Long


BITS 16

.NoLongMode:
    mov si, NoLongMode
    call Print

.Die:
    hlt
    jmp .Die


%include "LongModeDirectly.asm"
BITS 16


NoLongMode db "ERROR: CPU does not support long mode.", 0x0A, 0x0D, 0


; Checks whether CPU supports long mode or not.

; Returns with carry set if CPU doesn't support long mode.

CheckCPU:
    ; Check whether CPUID is supported or not.
    pushfd                            ; Get flags in EAX register.
    
    pop eax
    mov ecx, eax  
    xor eax, 0x200000 
    push eax 
    popfd

    pushfd 
    pop eax
    xor eax, ecx
    shr eax, 21 
    and eax, 1                        ; Check whether bit 21 is set or not. If EAX now contains 0, CPUID isn't supported.
    push ecx
    popfd 

    test eax, eax
    jz .NoLongMode
    
    mov eax, 0x80000000   
    cpuid                 
    
    cmp eax, 0x80000001               ; Check whether extended function 0x80000001 is available are not.
    jb .NoLongMode                    ; If not, long mode not supported.

    mov eax, 0x80000001  
    cpuid                 
    test edx, 1 << 29                 ; Test if the LM-bit, is set or not.
    jz .NoLongMode                    ; If not Long mode not supported.

    ret

.NoLongMode:
    stc
    ret


; Prints out a message using the BIOS.

; es:si    Address of ASCIIZ string to print.

Print:
    pushad
.PrintLoop:
    lodsb                             ; Load the value at [@es:@si] in @al.
    test al, al                       ; If AL is the terminator character, stop printing.
    je .PrintDone                  	
    mov ah, 0x0E	
    int 0x10
    jmp .PrintLoop                    ; Loop till the null character not found.
	
.PrintDone:
    popad                             ; Pop all general purpose registers to save them.
    ret


; Pad out file.
times 510 - ($-$$) db 0
dw 0xAA55


The above code can be compiled (and tested using QEMU) by the following:

    nasm -fbin Main.asm -o LongModeDirectly         ; The main file's name should be Main.asm
    qemu-system-x86_64 -hda LongModeDirectly        ; The secondary file's name should be LongModeDirectly.asm and should be in the same directory

Notes about the above bootsector

  1. The above bootsector has been written just for completeness.
  2. The above bootsector can't be used as an actual bootsector as it doesn't:
   # Enable A20
   # Detect memory
   # Load required files from disk using BIOS

See Also

Related forum thread