Repository navigation
Bootloader
The cupid-os bootloader is a critical component that bridges the gap between the BIOS and the 32-bit kernel. This 16-bit assembly program handles the transition from real mode to protected mode while loading the kernel into memory.
The bootloader performs three main tasks:
- Initialization: Sets up the environment and segments
- Kernel Loading: Reads the kernel from disk into memory
- Mode Transition: Switches from 16-bit real mode to 32-bit protected mode
When the system powers on, the BIOS performs POST (Power-On Self Test) and initializes hardware. It then looks for a bootable device and loads the first 512 bytes (one sector) from the boot device into memory at address 0x7C00.
[org 0x7c00] ; Tell NASM our code will be loaded at 0x7C00The bootloader starts in 16-bit real mode with limited capabilities:
start:
; Set up segments
xor ax, ax
mov ds, ax ; Data segment = 0
mov es, ax ; Extra segment = 0
mov ss, ax ; Stack segment = 0
mov sp, 0x9000 ; Stack pointer = 0x9000- Segments: All segment registers are set to 0 for flat memory access
-
Stack: Set up a small stack at
0x9000(above the bootloader)
The kernel is stored on disk starting from sector 2 (sector 1 is the bootloader). The bootloader uses BIOS interrupt 0x13 to read disk sectors:
load_kernel:
mov bx, KERNEL_OFFSET ; Load to 0x1000
mov dh, 50 ; Load 50 sectors
mov dl, [BOOT_DRIVE] ; From boot drive
mov ah, 0x02 ; BIOS read function
mov al, dh ; Sectors to read
mov ch, 0 ; Cylinder 0
mov dh, 0 ; Head 0
mov cl, 2 ; Start from sector 2
int 0x13-
Destination: Kernel loaded to
0x1000(KERNEL_OFFSET) - Size: 50 sectors (25KB) - adjust as kernel grows
- Location: Starting from sector 2 on the boot disk
-
Error Handling: Jumps to
disk_erroron failure
Before switching modes, interrupts must be disabled:
switch_to_pm:
cli ; Clear Interrupt Flag (disable interrupts)Interrupts are disabled because the interrupt table (IVT) is about to become invalid during the mode switch.
The GDT defines memory segments for protected mode:
gdt_start:
dq 0 ; Null descriptor (required)
gdt_code:
dw 0xffff ; Limit (0-15)
dw 0 ; Base (0-15)
db 0 ; Base (16-23)
db 10011010b ; Access byte
db 11001111b ; Flags + Limit (16-19)
db 0 ; Base (24-31)
gdt_data:
dw 0xffff ; Limit (0-15)
dw 0 ; Base (0-15)
db 0 ; Base (16-23)
db 10010010b ; Access byte
db 11001111b ; Flags + Limit (16-19)
db 0 ; Base (24-31)- Base: 0 (starts at beginning of memory)
- Limit: 0xFFFFF (4GB, with 4KB granularity)
- Access: Present, Ring 0, Code, Executable, Readable
- Flags: 32-bit, Page granularity
- Base: 0 (starts at beginning of memory)
- Limit: 0xFFFFF (4GB, with 4KB granularity)
- Access: Present, Ring 0, Data, Writable
- Flags: 32-bit, Page granularity
The transition to protected mode happens in two steps:
lgdt [gdt_descriptor] ; Load Global Descriptor Table
mov eax, cr0
or eax, 0x1 ; Set PE (Protection Enable) bit
mov cr0, eax ; Enable protected modeThe CR0.PE bit enables protected mode. Once set, the CPU operates in 32-bit protected mode.
A far jump is required to flush the instruction pipeline and reload segment registers:
jmp CODE_SEG:init_pm ; Far jump to 32-bit code- CODE_SEG: Selector for code segment (offset into GDT)
- init_pm: 32-bit initialization function
In protected mode, segment registers contain selectors (not physical addresses):
[bits 32]
init_pm:
mov ax, DATA_SEG
mov ds, ax ; Data segment selector
mov es, ax ; Extra segment selector
mov fs, ax ; F segment selector
mov gs, ax ; G segment selector
mov ss, ax ; Stack segment selector
mov esp, 0x90000 ; Set up 32-bit stack
mov ebp, esp ; Base pointer = stack topThe bootloader prints a character to video memory to verify 32-bit mode:
mov ebx, 0xb8000 ; VGA text buffer address
mov al, 'P' ; Character to display
mov ah, 0x0f ; White on black attribute
mov [ebx], ax ; Write to video memoryThis displays a white 'P' in the top-left corner of the screen.
Finally, the bootloader jumps to the kernel:
jmp CODE_SEG:KERNEL_OFFSET ; Jump to kernel at 0x1000- CODE_SEG: Ensures we're in the correct code segment
-
KERNEL_OFFSET:
0x1000where the kernel was loaded
0x00000 - 0x003FF: Real Mode IVT (Interrupt Vector Table)
0x00400 - 0x004FF: BIOS Data Area
0x00500 - 0x07BFF: Free memory
0x07C00 - 0x07DFF: Bootloader (512 bytes)
0x07E00 - 0x7FFFF: Free memory
0x80000 - 0x9FFFF: Extended BIOS Data Area
0xA0000 - 0xFFFFF: Video memory, ROM, etc.
Sector 0: Master Boot Record (MBR) - unused
Sector 1: Bootloader (512 bytes)
Sector 2+: Kernel binary
The bootloader is assembled with NASM:
boot/boot.bin: boot/boot.asm
$(ASM) -f bin boot/boot.asm -o boot/boot.bin-
Input:
boot/boot.asm -
Output:
boot/boot.bin(flat binary) - Format: Raw binary (no ELF headers)
- Ensure the disk image was created correctly
- Check that the boot signature (
0xAA55) is present
- The BIOS
int 0x13call failed - Check disk geometry parameters
- Verify kernel sectors exist on disk
- GDT not loaded correctly
- Invalid segment selectors
- Stack corruption during transition
# Run QEMU with GDB
qemu-system-i386 -fda cupidos.img -s -S
# In another terminal
gdb
(gdb) target remote localhost:1234
(gdb) break *0x7c00 # Break at bootloader entry
(gdb) continue- Add
int 0x10calls to print characters during boot - Use different colors to mark progress points
- Check video memory contents with GDB
# Check if kernel was loaded
(gdb) x/32xb 0x1000
# Examine GDT
(gdb) x/8xb gdt_start
# Check segment registers
(gdb) info registers- Load additional stages if bootloader grows too large
- Implement better disk drivers (LBA addressing)
- Detect available memory with
int 0x15 - Probe for CPU features (CPUID)
- Identify attached hardware
- Add more segment descriptors
- Implement task state segments (TSS)
- Set up separate user/kernel segments
The current bootloader loads exactly 50 sectors. As the kernel grows, this limit may need to be increased:
mov dh, 50 ; Increase this valueCurrently errors result in infinite loops. Consider implementing retry logic or fallback mechanisms.
The bootloader must fit in 512 bytes. Complex features may require a multistage design.
| Interrupt | Function | Purpose |
|---|---|---|
0x10 |
Video Services | Printing characters |
0x13 |
Disk Services | Reading kernel sectors |
| Register | Real Mode | Protected Mode |
|---|---|---|
CS |
0x0000 | CODE_SEG (0x08) |
DS |
0x0000 | DATA_SEG (0x10) |
SS |
0x0000 | DATA_SEG (0x10) |
ESP |
0x9000 | 0x90000 |
- Real Mode: Segment:Offset (physical = segment × 16 + offset)
- Protected Mode: Selector:Offset with GDT translation
- OSDev Wiki: Comprehensive bootloader tutorials
- Intel Manuals: CPU architecture documentation
- BIOS Specifications: Interrupt calling conventions
For more information about the boot process:
- Home - Project overview
- Building - Build system details
- Interrupts and Exceptions - What happens after boot
Getting Started
Shell & Scripting
Compilers
- CupidC Compiler
- CupidC Language Reference
- CupidC 2D Graphics Library
- CupidASM Assembler
- Toolchain Bootstrap
- Floating Point
Programs & Docs
GUI
Kernel
Filesystems
Hardware