Entering Long Mode Directly: Difference between revisions
[unchecked revision] | [unchecked revision] |
No edit summary |
m Bot: Replace deprecated source tag with syntaxhighlight |
||
(27 intermediate revisions by 14 users not shown) | |||
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. Additionally, some may find it easier to switch directly to long mode on additional CPUs as they are initialized for [[Symmetric Multiprocessing]]. |
|||
== 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. |
|||
<pre> |
|||
[ORG 0x00007C00] |
|||
[BITS 16] |
|||
<syntaxhighlight lang="asm"> |
|||
%define PAGE_PRESENT (1 << 0) |
|||
%define PAGE_WRITE (1 << 1) |
|||
%define CODE_SEG 0x0008 |
|||
%define DATA_SEG 0x0010 |
|||
ALIGN 4 |
|||
boot_loader: |
|||
IDT: |
|||
;Parameter from BIOS: dl = boot drive |
|||
.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. |
|||
;Set default state |
|||
; ss:esp Should point to memory that can be used as a small (1 uint32_t) stack |
|||
SwitchToLongMode: |
|||
cli |
|||
; Zero out the 16KiB buffer. |
|||
xor bx,bx |
|||
; Since we are doing a rep stosd, count should be bytes/4. |
|||
mov es,bx |
|||
push di ; REP STOSD alters DI. |
|||
mov fs,bx |
|||
mov |
mov ecx, 0x1000 |
||
xor eax, eax |
|||
mov ds,bx |
|||
cld |
|||
mov ss,bx |
|||
rep stosd |
|||
mov sp,0x7C00 |
|||
pop di ; Get DI back. |
|||
sti |
|||
jmp 0:.clear_cs |
|||
.clear_cs: |
|||
; 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. |
|||
;Load kernel from floppy disk |
|||
; Build the Page Directory Pointer Table. |
|||
mov ax,0x020D ;ah = Function 0x02 ;al = Number of sectors |
|||
lea eax, [es:di + 0x2000] ; Put the address of the Page Directory in to EAX. |
|||
mov bx,startLongMode ;es:bx = Destination |
|||
or eax, PAGE_PRESENT | PAGE_WRITE ; Or EAX with the flags - present flag, writable flag. |
|||
mov cx,0x0002 ;cx = Cylinder and sector |
|||
mov [es:di + 0x1000], eax ; Store the value of EAX as the first PDPTE. |
|||
int 0x13 ;Int 0x13 Function 0x02 (Load sectors) |
|||
; 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. |
|||
;Enable A20 via port 92h |
|||
; Build the Page Table. |
|||
in al,92h |
|||
.LoopPageTable: |
|||
or al,02h |
|||
mov [es:di], eax |
|||
out 92h,al |
|||
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. |
|||
;Build page tables |
|||
; Enter long mode. |
|||
;The page tables will look like this: |
|||
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. |
|||
;PML4: |
|||
wrmsr |
|||
;dq 0x000000000000b00f = 00000000 00000000 00000000 00000000 00000000 00000000 10010000 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. |
|||
;PDP: |
|||
;dq 0x000000000000c00f = 00000000 00000000 00000000 00000000 00000000 00000000 10100000 00001111 |
|||
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: |
|||
;PD: |
|||
dq 0x00209A0000000000 ; 64-bit code descriptor (exec/read). |
|||
;dq 0x000000000000018f = 00000000 00000000 00000000 00000000 00000000 00000000 00000001 10001111 |
|||
dq 0x0000920000000000 ; 64-bit data descriptor (read/write). |
|||
;times 511 dq 0x0000000000000000 |
|||
ALIGN 4 |
|||
dw 0 ; Padding to make the "address of the GDT" field aligned on a 4-byte boundary |
|||
.Pointer: |
|||
;This defines one 2MB page at the start of memory, so we can access the first 2MBs as if paging was disabled |
|||
dw $ - GDT - 1 ; 16-bit Size (Limit) of GDT. |
|||
dd GDT ; 32-bit Base Address of GDT. (CPU will zero extend to 64-bit) |
|||
xor bx,bx |
|||
mov es,bx |
|||
cld |
|||
mov di,0xa000 |
|||
[BITS 64] |
|||
mov ax,0xb00f |
|||
LongMode: |
|||
stosw |
|||
mov ax, DATA_SEG |
|||
mov ds, ax |
|||
mov es, ax |
|||
mov fs, ax |
|||
mov gs, ax |
|||
mov ss, ax |
|||
; Blank out the screen to a blue color. |
|||
xor ax,ax |
|||
mov |
mov edi, 0xB8000 |
||
mov rcx, 500 ; Since we are clearing uint64_t over here, we put the count as Count/4. |
|||
rep stosw |
|||
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 ax,0xc00f |
|||
mov [edi + 16], rax |
|||
stosw |
|||
jmp Main.Long ; You should replace this jump to wherever you want to jump to. |
|||
</syntaxhighlight> |
|||
=== Notes about the above code === |
|||
xor ax,ax |
|||
# The above code assumes that the caller has checked whether the CPU supports x86_64 or not. |
|||
mov cx,0x07ff |
|||
# As noted in code comments, only 2MB is mapped. The code can be easily modified to map the kernel to higher half. |
|||
rep stosw |
|||
# The code doesn't include BPB for floppies, or any such thing. This also can be easily added. |
|||
== Example bootsector == |
|||
mov ax,0x018f |
|||
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. |
|||
xor ax,ax |
|||
mov cx,0x07ff |
|||
rep stosw |
|||
<syntaxhighlight lang="asm"> |
|||
%define FREE_SPACE 0x9000 |
|||
ORG 0x7C00 |
|||
;Enter long mode |
|||
BITS 16 |
|||
; Main entry point where BIOS leaves us. |
|||
mov eax,10100000b ;Set PAE and PGE |
|||
mov cr4,eax |
|||
Main: |
|||
mov edx, 0x0000a000 ;Point cr3 at PML4 |
|||
jmp 0x0000:.FlushCS ; Some BIOS' may load us at 0x0000:0x7C00 while other may load us at 0x07C0:0x0000. |
|||
mov cr3,edx |
|||
; Do a far jump to fix this issue, and reload CS to 0x0000. |
|||
.FlushCS: |
|||
mov ecx,0xC0000080 ;Specify EFER MSR |
|||
xor ax, ax |
|||
; Set up segment registers. |
|||
rdmsr ;Enable long mode |
|||
mov ss, ax |
|||
or eax,0x00000100 |
|||
; Set up stack so that it starts below Main. |
|||
wrmsr |
|||
mov sp, Main |
|||
mov ds, ax |
|||
mov es, ax |
|||
mov fs, ax |
|||
mov gs, ax |
|||
cld |
|||
call CheckCPU ; Check whether we support Long Mode or not. |
|||
jc .NoLongMode |
|||
or ebx,0x80000001 ;by enabling paging and protection simultaneously |
|||
mov cr0,ebx ;skipping protected mode entirely |
|||
; Point edi to a free space bracket. |
|||
lgdt [gdt.pointer] |
|||
mov edi, FREE_SPACE |
|||
; Switch to Long Mode. |
|||
jmp SwitchToLongMode |
|||
jmp gdt.code:startLongMode ;Load CS with 64 bit segment and flush the instruction cache |
|||
BITS 64 |
|||
.Long: |
|||
hlt |
|||
jmp .Long |
|||
BITS 16 |
|||
;Global Descriptor Table |
|||
.NoLongMode: |
|||
gdt: |
|||
mov si, NoLongMode |
|||
dq 0x0000000000000000 ;Null Descriptor |
|||
call Print |
|||
.Die: |
|||
.code equ $ - gdt |
|||
hlt |
|||
dq 0x0020980000000000 |
|||
jmp .Die |
|||
.data equ $ - gdt |
|||
dq 0x0000900000000000 |
|||
%include "LongModeDirectly.asm" |
|||
.pointer: |
|||
BITS 16 |
|||
dw $-gdt-1 ;16-bit Size (Limit) |
|||
dq gdt ;64-bit Base Address |
|||
;Changed from "dd gdt" |
|||
;Reference to Intel System Programming Manual Part 1 - 2.1.1.1 |
|||
NoLongMode db "ERROR: CPU does not support long mode.", 0x0A, 0x0D, 0 |
|||
;Fill boot sector |
|||
; Checks whether CPU supports long mode or not. |
|||
times 510-($-$$) db 0 |
|||
dw 0xAA55 ;Boot loader signature |
|||
; 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 |
|||
[BITS 64] |
|||
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 |
|||
startLongMode: |
|||
.NoLongMode: |
|||
cli ;Interupts are disabled because no IDT has been set up |
|||
stc |
|||
ret |
|||
mov edi,0x00b8000 ;Display:Put long mode kernel here. |
|||
mov rax,0x0720077407750750 |
|||
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 |
|||
; Prints out a message using the BIOS. |
|||
jmp $ |
|||
</pre> |
|||
; 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 |
|||
</syntaxhighlight> |
|||
The above code can be compiled (and tested using QEMU) by the following: |
|||
<syntaxhighlight lang="bash"> |
|||
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 |
|||
</syntaxhighlight> |
|||
=== 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== |
|||
===Forum Threads=== |
|||
* [http://www.osdev.org/phpBB2/viewtopic.php?t=11093 Related forum thread] |
|||
== See Also == |
|||
[http://www.osdev.org/phpBB2/viewtopic.php?t=11093 Related forum thread] |
|||
[[Category:X86-64]] |
[[Category:X86-64]] |
Latest revision as of 05:14, 9 June 2024
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. Additionally, some may find it easier to switch directly to long mode on additional CPUs as they are initialized for Symmetric Multiprocessing.
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.
; ss:esp Should point to memory that can be used as a small (1 uint32_t) stack
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
cld
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 0x00209A0000000000 ; 64-bit code descriptor (exec/read).
dq 0x0000920000000000 ; 64-bit data descriptor (read/write).
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
mov ss, ax
; Blank out the screen to a blue color.
mov edi, 0xB8000
mov rcx, 500 ; Since we are clearing uint64_t 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
- The above code assumes that the caller has checked whether the CPU supports x86_64 or not.
- 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
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:
xor ax, ax
; Set up segment registers.
mov ss, ax
; Set up stack so that it starts below Main.
mov sp, Main
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
cld
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
- 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