Creating a Custom Bootloader from Scratch Using C and Assembly

Have you ever wondered what happens when you turn on your computer? Before the operating system loads, a tiny program called a bootloader takes charge. Let’s make one ourselves—from scratch—using C and Assembly. It sounds scary, but stick with me and you’ll be surprised how fun it can be!

TL;DR

A bootloader is a small program that runs when the computer starts. It lives in the first 512 bytes of your hard drive. We’ll write one using Assembly and C, compile it, and run it in a virtual machine. It’s a great way to learn how your computer boots up and loads an OS.

What Is a Bootloader?

When your computer powers on, it doesn’t just jump into Windows or Linux. It first looks at a very special part of your storage device—the Master Boot Record (MBR). This part has 512 bytes, and inside lives the bootloader.

The bootloader’s job is to prepare the system and load the operating system. It’s the bridge between your hardware and your OS.

Why Make Your Own?

Because it’s cool! And also because:

  • You learn low-level programming.
  • You understand how computers boot.
  • You can build something entirely your own.

Plus, with C and Assembly, you’ll get to control the machine directly. It’s like being the wizard behind the curtain.

Tools You’ll Need

  • NASM – The Netwide Assembler, to compile Assembly code.
  • GCC – To compile C code.
  • QEMU or VirtualBox – To test your bootloader safely.
  • DD – To create bootable disk images.

Let’s Get Started

A bootloader must be exactly 512 bytes. That’s the size of the MBR. The last two bytes must be a “magic number”: 0x55AA. This tells the BIOS, “Hey! I’m a bootable sector!”

Step 1: Writing the Bootloader in Assembly

Create a file called bootloader.asm with the following code:

bits 16
org 0x7C00

start:
    mov ah, 0x0E
    mov al, 'H'
    int 0x10
    mov al, 'i'
    int 0x10
    jmp $

times 510 - ($ - $$) db 0
dw 0xAA55

Let’s break this down:

  • bits 16 tells NASM we’re using 16-bit real mode.
  • org 0x7C00 is where BIOS loads the bootloader in memory.
  • mov ah, 0x0E and int 0x10 are BIOS interrupts to print characters.
  • jmp $ loops forever.

It will just display “Hi” on the screen, then freeze (in a good way!).

Step 2: Compiling It

Use NASM to assemble it into a binary:

nasm -f bin bootloader.asm -o bootloader.bin

This produces a raw boot sector file. Let’s test it!

Testing in QEMU

Fire up QEMU with your new bootloader:

qemu-system-x86_64 -drive format=raw,file=bootloader.bin

If all goes well, it will display “Hi” and do nothing else. That’s perfect.

Adding C into the Mix

Assembly is powerful but tough. C can make things easier. But there’s a catch: you can’t just write C code and expect it to boot. You need a minimal runtime.

Step 1: Prepare a 2-Stage Bootloader

Our first 512-byte bootloader loads the second stage that’s written in C.

Update your bootloader.asm file to load a second file:

bits 16
org 0x7C00

start:
    mov si, msg
.print:
    lodsb
    or al, al
    jz load_stage2
    mov ah, 0x0E
    int 0x10
    jmp .print

load_stage2:
    mov ah, 0x02
    mov al, 3
    mov ch, 0
    mov cl, 2
    mov dh, 0
    mov dl, 0
    mov bx, 0x8000
    int 0x13

    jmp 0x0000:0x8000

msg db 'Stage 1 Bootloader',0

times 510 - ($ - $$) db 0
dw 0xAA55

This prints a message and loads sector 2–4 into memory, then jumps there. That will be our C code!

Step 2: Writing the Stage 2 in C

Create stage2.c:

void main() {
    char *video = (char*) 0xB8000;
    video[0] = '!';
    video[1] = 0x0F;
    while (1);
}

This writes “!” at the start of the screen and halts. Simple and effective.

Step 3: Compiling the C Code

We need special flags:

i686-elf-gcc -ffreestanding -m32 -c stage2.c -o stage2.o
i686-elf-ld -Ttext 0x8000 stage2.o -o stage2.bin --oformat binary

Then combine both boot stages into one disk image:

cat bootloader.bin stage2.bin > boot.img

Step 4: Test It Again

Let’s see it in action:

qemu-system-x86_64 -drive format=raw,file=boot.img

You should see your stage 1 message and then a nice “!” pop up. You’re now officially a boot wizard.

Tips and Gotchas

  • Always keep your loader under 512 bytes.
  • C needs a flat binary—no standard libs allowed!
  • Use a cross compiler for consistent output (like i686-elf-gcc).

These things will save you hours of screaming into your keyboard.

Where to Go From Here?

This is just the beginning. You can enhance your bootloader to:

  • Load entire operating systems.
  • Use advanced graphics modes.
  • Support keyboard inputs.
  • Access file systems like FAT.

Once you understand the basics, the sky’s the limit.

Final Thoughts

Building a bootloader is one of the most exciting low-level projects you can do. It gives you great insight into how computers really work. It’s challenging, but rewarding—and now you’re part of a rare club who truly understands the magic of booting up!

So go ahead—hack the boot. 🚀