When you press the power button, a complex, step-by-step procedure unfolds before your operating system (OS) appears. At the very core of this process lies the bootloader. This article guides you through building a simple, Stage-1 bootloader in x86 assembly that prints messages and reads a disk sector using BIOS interrupts.
What is a Bootloader?
A bootloader is the first program that executes after the system power-on sequence completes.
- Location: It resides in the boot sector—the very first 512-byte sector of a bootable device (like a hard drive or USB).
- Loading: The system's BIOS (Basic Input/Output System) loads this sector into memory at the specific address 0x7C00.
-
Signature: A valid boot sector must end with the signature
0xAA55. - Role: Its primary function is to prepare the system environment and load the "next stage" of code, which could be the OS kernel or a more advanced second-stage bootloader.
Analogy: Think of the bootloader as the table of contents of a book—it’s the first thing the system sees and it points to where the essential content (the OS) can be found.
Without a bootloader, the CPU wouldn't know where the OS is, how to load it into memory, or what instruction to execute next. The BIOS does the basic hardware checks and initialization; the bootloader takes the hand-off and directs execution.
Project Goal: Stage-1 Bootloader
You will create a minimal Stage-1 bootloader with the following sequence:
- BIOS loads the boot sector to 0x7C00.
- Bootloader prints an initial message.
- Bootloader uses the BIOS disk service (INT 0x13) to read a specific sector (e.g., Sector 2).
- Bootloader prints the contents of the newly loaded sector from memory.
- Bootloader halts in an infinite loop.
Tools Required
| Tool | Description | Installation Command (Linux) |
|---|---|---|
| NASM | The Assembler used to convert assembly code into a binary file (.bin). |
sudo apt install nasm |
| QEMU | A fast and reliable system emulator for testing the bootloader. | sudo apt install qemu-system |
| Optional: Bochs | For detailed, low-level debugging. | - |
You'll run the final assembly code using QEMU:
qemu-system-i386 -fda boot.bin
Background Knowledge for Beginners
The Computer Boot Sequence
- POST: The BIOS runs the Power-On Self-Test (POST) to check hardware.
- Load: The BIOS loads the first 512-byte sector (the boot sector) of the bootable drive into memory at 0x7C00.
- Execute: The CPU jumps to 0x7C00 and begins executing the bootloader's code.
- Handoff: The bootloader loads the next stage of the OS or program.
Real Mode and 16-bit Basics
Upon reset, the CPU operates in 16-bit Real Mode.
- Addressing: It uses segment:offset addressing.
- Access: It can only access the first 1 MB of memory.
- Registers: Key registers are 16-bit, including AX, BX, CX, DX (general purpose), SI, DI (index), and the segment registers DS, ES, SS, CS.
Segmentation and Addressing
The CPU calculates a 20-bit Physical Address using the 16-bit Segment and Offset registers:
Physical Address = Segment * 16 + offset
Common pairs: DS:SI for string/data manipulation, ES:BX for disk buffers, and CS:IP for code execution.
BIOS Interrupts Overview
BIOS provides services through software interrupts, which are called using the int instruction. We'll focus on two:
| Interrupt | Purpose | Example Register Setup |
|---|---|---|
| INT 0x10 | Video services (e.g., printing characters). |
mov ah, 0x0E (Teletype function) |
| INT 0x13 | Disk services (e.g., reading/writing sectors). |
mov ah, 0x02 (Read function) |
Source Code Structure and Logic
The project is split into three modular assembly files for clarity and reusability:
| File | Purpose | Key Function |
|---|---|---|
print.asm |
Reusable routines for text output. |
print_string (using INT 0x10) |
disk_read.asm |
Handles disk I/O with minimal error handling. |
read_sector (using INT 0x13) |
stage1_bootloader.asm |
The main entry point and execution logic. | Entry at 0x7C00
|
1. Printing Functions (print.asm)
These functions use INT 0x10, AH=0x0E (Teletype mode) to display characters.
; Print a single character in AL
print_char:
mov ah, 0x0E ; Teletype function
mov bh, 0x00 ; Display page 0
mov bl, 0x07 ; White on black color
int 0x10
ret
; Print a null-terminated string (DS:SI -> string)
print_string:
.print_loop:
lodsb ; Load byte from [DS:SI] into AL, increment SI
cmp al, 0 ; Check for null-terminator (0)
je .done
call print_char
jmp .print_loop
.done:
ret
2. Disk Reading Function (disk_read.asm)
This function uses INT 0x13, AH=0x02 to read one sector.
| Register | Value | Description |
|---|---|---|
| AH | 0x02 |
Function: Read Sector(s) |
| AL | 0x01 |
Number of sectors to read |
| CH | Cylinder (0-based) | |
| CL | Sector (1-based) | |
| DH | Head (0-based) | |
| DL | Drive (0x00 for floppy, 0x80 for hard disk) | |
| ES:BX | Destination buffer address |
Important: Sector numbering starts at 1.
read_sector:
; Prerequisites: ES:BX (dest), DL (drive), CH/DH/CL (CHS)
mov ah, 0x02
mov al, 0x01
int 0x13
jc .fail ; Jump if Carry Flag (CF) is set (failure)
ret
.fail:
mov si, read_error_msg
call print_string
jmp $ ; Halt forever on error
read_error_msg db "Disk Read Error", 0
3. Bootloader Entry Point (stage1_bootloader.asm)
This is the main logic. We initialize segment registers, print the message, then configure the parameters for read_sector.
| Parameter | Value | Description |
|---|---|---|
| ES | 0x0000 |
Destination segment (Data to be loaded at 0x0000:0x0500) |
| BX | 0x0500 |
Destination offset (Safe memory buffer) |
| CL | 0x02 |
Sector 2 (The sector we are reading) |
[BITS 16]
[ORG 0x7C00]
start:
; 1. Initialize segment registers to 0
xor ax, ax
mov ds, ax
mov es, ax
; 2. Print initial message
mov si, msg
call print_string
; 3. Configure and call read_sector to load Sector 2 to 0x0500
mov ax, 0x0000
mov es, ax ; Destination Segment ES=0x0000
mov bx, 0x0500 ; Destination Offset BX=0x0500
mov dl, 0x00 ; Drive 0 (Floppy)
mov ch, 0x00 ; Cylinder 0
mov cl, 0x02 ; Sector 2
mov dh, 0x00 ; Head 0
call read_sector
; 4. Print the loaded data (at 0x0500)
mov si, 0x0500
call print_string
; 5. Loop forever (halt)
jmp $
msg db "Reading sector 2...", 0
%include "asm/print.asm"
%include "asm/disk_read.asm"
; Boot sector padding and signature
times 510 - ($ - $$) db 0
dw 0xAA55
Running the Project
1. Assemble with NASM
This command converts the assembly code into a raw 512-byte binary file (boot.bin).
nasm -f bin asm/stage1_bootloader.asm -o boot.bin
2. Run in QEMU
QEMU emulates the hardware (BIOS, CPU, disk). The -fda flag tells QEMU to load our binary as the floppy disk image, which the BIOS will then boot from.
qemu-system-i386 -boot a -fda boot.bin
Expected Output:
The first line will be "Reading sector 2..." from the bootloader itself, followed immediately by the (potentially garbled) data contained within the actual Sector 2 of the virtual disk image.
Conclusion
By successfully building this basic bootloader, you've gained invaluable, low-level insight into the computer's startup process. You've directly interacted with the BIOS via interrupts, worked with real-mode addressing, and understood the critical hand-off from firmware to software.
This fundamental knowledge is the building block for all system-level development, from writing device drivers to developing a fully-fledged operating system.
Appendix: Quick BIOS Interrupt Reference
| Interrupt | AH | Purpose | Key Registers |
|---|---|---|---|
| INT 0x10 | 0x0E | Teletype Output | AL (Character), BL (Color) |
| INT 0x13 | 0x02 | Read Sectors | AL (Count), ES:BX (Buffer), CH/CL/DH (CHS) |
| INT 0x16 | 0x00 | Wait for Keypress | Returns key code in AL |
Glossary
- Bootloader: The program loaded by the BIOS to initialize the OS.
- Sector: The smallest addressable unit of disk storage (512 bytes).
- CHS: Cylinder-Head-Sector, the legacy addressing scheme for disk I/O.
- Boot Signature (0xAA55): The required 2-byte marker at the end of the boot sector.
- Real Mode: The 16-bit operating mode of the x86 CPU at reset.
GitHub Repo Link : https://github.com/aayush598/basic-bootloader-assembly

Top comments (1)
Pretty cool!!!, I hope I can find a spare time to try it. Thanks for sharing