Bootloader - cupidthecat/cupid-os GitHub Wiki
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.
Overview
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
Boot Process
BIOS Handover
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 0x7C00
Real Mode Initialization
The 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)
Loading the Kernel
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
Protected Mode Transition
Disabling Interrupts
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.
Global Descriptor Table (GDT)
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)
Code Segment Descriptor
- 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
Data Segment Descriptor
- Base: 0 (starts at beginning of memory)
- Limit: 0xFFFFF (4GB, with 4KB granularity)
- Access: Present, Ring 0, Data, Writable
- Flags: 32-bit, Page granularity
Enabling Protected Mode
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 mode
The CR0.PE bit enables protected mode. Once set, the CPU operates in 32-bit protected mode.
Far Jump
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
32-Bit Initialization
Segment Setup
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 top
Verification
The 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 memory
This displays a white 'P' in the top-left corner of the screen.
Kernel Handover
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
Bootloader Structure
Memory Layout
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.
Disk Layout
Sector 0: Master Boot Record (MBR) - unused
Sector 1: Bootloader (512 bytes)
Sector 2+: Kernel binary
Build Process
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)
Debugging the Bootloader
Common Issues
"No bootable device found"
- Ensure the disk image was created correctly
- Check that the boot signature (
0xAA55) is present
"Disk error!"
- The BIOS
int 0x13call failed - Check disk geometry parameters
- Verify kernel sectors exist on disk
Triple fault after mode switch
- GDT not loaded correctly
- Invalid segment selectors
- Stack corruption during transition
Debugging Techniques
QEMU Debugging
# 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
Visual Debugging
- Add
int 0x10calls to print characters during boot - Use different colors to mark progress points
- Check video memory contents with GDB
Memory Inspection
# Check if kernel was loaded
(gdb) x/32xb 0x1000
# Examine GDT
(gdb) x/8xb gdt_start
# Check segment registers
(gdb) info registers
Extending the Bootloader
Adding Features
Multistage Loading
- Load additional stages if bootloader grows too large
- Implement better disk drivers (LBA addressing)
Hardware Detection
- Detect available memory with
int 0x15 - Probe for CPU features (CPUID)
- Identify attached hardware
Advanced GDT
- Add more segment descriptors
- Implement task state segments (TSS)
- Set up separate user/kernel segments
Limitations
Sector Count
The current bootloader loads exactly 50 sectors. As the kernel grows, this limit may need to be increased:
mov dh, 50 ; Increase this value
Error Handling
Currently errors result in infinite loops. Consider implementing retry logic or fallback mechanisms.
Memory Constraints
The bootloader must fit in 512 bytes. Complex features may require a multistage design.
Technical Details
BIOS Interrupts Used
| Interrupt | Function | Purpose |
|---|---|---|
0x10 |
Video Services | Printing characters |
0x13 |
Disk Services | Reading kernel sectors |
CPU State Changes
| Register | Real Mode | Protected Mode |
|---|---|---|
CS |
0x0000 | CODE_SEG (0x08) |
DS |
0x0000 | DATA_SEG (0x10) |
SS |
0x0000 | DATA_SEG (0x10) |
ESP |
0x9000 | 0x90000 |
Memory Addressing
- Real Mode: Segment:Offset (physical = segment × 16 + offset)
- Protected Mode: Selector:Offset with GDT translation
References
- 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