Skip to content

Instantly share code, notes, and snippets.

@dabsamak
Last active November 9, 2025 01:53
Show Gist options
  • Select an option

  • Save dabsamak/41e7c4a20886201071ac7cf062ff4a6c to your computer and use it in GitHub Desktop.

Select an option

Save dabsamak/41e7c4a20886201071ac7cf062ff4a6c to your computer and use it in GitHub Desktop.
x86_64 Kernel bootstrap / entry
; x86_64 kernel bootstrap, commented
; int16h @ cryogenix.org
; ------------------------------------------------------------------------------
; --- Assemble in 32-bit mode
[BITS 32]
[GLOBAL start] ; Export the entry symbol for the linker.
GLOBAL long_mode_entry ; 64-bit entry shared by both boot paths.
GLOBAL uefi_entry ; Alias used by the UEFI loader.
extern kmain ; Pull in the C kernel entry point.
extern boot_info_set_multiboot
; --- multiboot2 header for GRUB
section .multiboot2
align 8 ; 8-byte alignment
mb2_hdr_start:
dd 0xE85250D6 ; mb2 magic number
dd 0 ; architecture = 0 (i386)
dd mb2_hdr_end - mb2_hdr_start ; total header length
dd -(0xE85250D6 + 0 + (mb2_hdr_end - mb2_hdr_start)) ; checksum
; required end tag (type 0)
dw 0 ; tag type (=0 end)
dw 0 ; reserved
dd 8 ; tag size (bytes)
mb2_hdr_end: ; end of header
section .text
; --- 32-bit protected mode start symbol
start:
; Remember Multiboot2 handoff registers for later.
mov [multiboot_magic], eax
mov [multiboot_info], ebx
; --- Tiny GDT (32-bit; 64-bit after ljmp) ---
lgdt [gdt_descriptor32]
mov ax, 0x10
mov ds, ax
mov es, ax
mov ss, ax
mov fs, ax
mov gs, ax
; --- Enable PAE (CR4.PAE=1) / Physical Address Extension ---
; Allows access to >4GiB memory by widening physical addresses
; from 32 to 36+ bits and changing paging structure (PML4/PDPT)
mov eax, cr4
bts eax, 5 ; PAE
mov cr4, eax
; --- Load identity-mapped PML4 into CR3 ---
mov eax, pml4
mov cr3, eax
; --- Enable LME (IA32_EFER.LME=1) / Long Mode Enable ---
; Allows the PCU to enter 64-bit / long mode once paging is enabled
mov ecx, 0xC0000080 ; IA32_EFER
rdmsr
bts eax, 8 ; LME
wrmsr
; --- Enable paging (CR0.PG=1) ---
;
mov eax, cr0
bts eax, 31 ; PG
mov cr0, eax
; --- Far jump to 64-bit code segment ---
jmp 0x08:long_mode_entry
; --- Assemble in 64-bit / long mode
[BITS 64]
long_mode_entry:
lgdt [rel gdt_descriptor64]
; Now in long mode with CS=0x08 (64-bit code). Set a stack.
mov ax, 0x10
mov ds, ax
mov es, ax
mov ss, ax
mov fs, ax
mov gs, ax
mov rsp, 0x0000000000090000 ; Point the temporary stack at 0x90000.
; Pass Multiboot2 info to common boot_info helpers if GRUB loaded us.
mov eax, dword [rel multiboot_magic]
test eax, eax
jz .no_mb
mov edi, eax
mov esi, dword [rel multiboot_info]
call boot_info_set_multiboot
.no_mb:
call kmain ; hand off to C kernel
.hang: ; halt loop if kmain ever returns
hlt ; halt CPU
jmp .hang ; keep halting!
uefi_entry:
lgdt [rel gdt_descriptor64]
lea rax, [rel long_mode_entry]
push qword 0x08
push rax
retfq
; ------------------ Tiny GDT (with a 64-bit code segment) ------------------
[BITS 32]
align 8 ; 8-byte alignment
gdt:
dq 0x0000000000000000 ; null descriptor (required)
; 64-bit code descriptor:
; Bits 0-15: 0x0000 (ignored in long mode)
; 16-31: 0x0000 (segment base address = 0)
; 32-39: 0x09A =>
; Bit 7 (0x80) P=1 - segment present
; Bit 6 (0x40) DPL[1]=0
; Bit 5 (0x20) DPL[0]=0 -> Descriptor Priv Level:
; 00 - ring 0 (highest)
; 01 - ring 1
; 10 - ring 2
; 11 - ring 3 (user mode)
; Bit 4 (0x10) S=1 - code/data segment (not LDT/TSS/etc)
; Bit 3 (0x08) Type[3]=1 (code segment)
; Bit 2 (0x04) Type[2]=1 (readable+executable)
; Bit 1 (0x02) Type[1]=0 (non-conforming CS)
; Bit 0 (0x01) Type[0]=0 (accessed bit)
; 40-43: 0x0 (limit high)
; 44: AVL=0 (available bit not used)
; 45: L=1 (enable 64-bit long mode semantics for CS)
; 46: D/B=0 (default operand size - must be 0 for 64-bit mode)
; 47: G=0 (granularity flag; 0=bytes not 4KiB chunks; ignored in long mode)
; 48-63: base high+reserved=0, seg remains at 0
;
; Succinctly: L=1, P=1, DPL=0, executable
dq 0x00209A0000000000
; 64-bit data segment (not strictly needed for pure code) - legacy/32-bit
; L=0, D/B=1, P=1, DPL=0
dq 0x0000920000000000
;
gdt_descriptor32:
dw gdt_end - gdt - 1 ; GDT size-1
dd gdt ; write 32-bit address of GDT
gdt_descriptor64:
dw gdt_end - gdt - 1
dq gdt
gdt_end:
; ------------------ Minimal page tables (identity map low 2MB) --------
; PML4: Page-Map Level-4 tablt, 4KiB-aligned (top-level in x86_64 paging)
; Entries store physical base of next paging level (PDPT)+low-bit flags
align 4096
pml4: dq pdpt + 0x003 ; P/W, present
; PDPT: Page-Directory-Pointer-Table, 4KiB-aigned (second level)
; Entries point to a PD (Page-Directory). Normally gates access to
; large regions, but here it links down to the 2 MB PD. PML4 points here.
align 4096
pdpt: dq pde + 0x003
; PDE: Page-Directory Entry, points to lower-level page table or
; directly describes a large page (as it does here).
;
; Mapped with large pages, ensures code loaded by GRUB remains accessible
; after paging is enabled
;
; 2MB page for 0..2MB:
; Flags 0x083 =>
; Bit 0 (0x01) PS=1 - present,
; Bit 1 (0x02) R/W=1 - writable,
; Bit 7 (0x80) P=1 - page-size flag (map single large (2MiB) page directly).
;
; High bits zeroed (address 9), a mapping of the first 2 MiB
align 4096
pde: dq 0x0000000000000083
; --- Boot metadata captured from firmware loaders ---
section .bss
align 8
multiboot_magic: resq 1
multiboot_info: resq 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment