I Made My PC Boot Into My Code — And It Was Gloriously Boring

Shuja AhmadShuja Ahmad
7 min read

So it all started one day when this crazy thought struck my mind: "Can someone write their own operating system from scratch?" I thought to myself that even if it was possible, it must be really hard and time-consuming.

I booted up my desktop running Parrot OS, opened YouTube, and began typing "How to write your own OS?" At the time, I didn't know that this single search would impact my life in a way nothing else has.

I clicked on a video by nanobyte about making his own OS and started watching it. I had some experience with assembly, so most of it wasn't too hard to grasp. But as soon as he got to reading from disks, everything went over my head. Up to this point, I could barely create a bootable image - and now we're reading disks? My brain overloaded, and I stopped the project for a while, but it stayed in my mind.

Fast forward about six months, I searched for the same thing again. This time I clicked on a video by Daedalus Community - and I hit the jackpot. I'm not saying nanobyte's way was wrong; it was just way too advanced for me at the time.

After watching for a couple of minutes, I could write my own bootloader and print an 'A' onto the BIOS screen. The read-disk part still gave me trouble, but I didn't give up. It took me about six to eight months just to understand BIOS functions, but looking back, it was all worth it.

Fast forward to today, and what once seemed impossible - bootloaders, disk I/O - is now completely clear. I'm ready to show you how.


Prerequisites

Before we dive in, make sure you have:

  1. A running PC - For writing and testing our bootloader.

  2. NASM (Netwide Assembler) - For assembling our code.

  3. QEMU - For safely testing our bootloader.

  4. A text editor - Unless you plan on writing bits directly to the disk with a magnet.

  5. Patience and curiosity - If you don't have these two, you've got a rough road ahead.


Step 1: Bootloader Basics - Before laying hands on keyboard

Wait wait wait, I know you are excited but there are a few things that you need to know before you start smashing the life out of your keyboard.

Here are some rules you need to remember by the heart (or any other organ):

  1. Size matters: The bootloader must fit exactly 512 bytes (one sector). Anything less or more won't boot.

  2. Boot Signature: The last 2 bytes of your bootloader must end with 0x55 and 0xAA. This lets the BIOS know that it's legit bootloader code and not something random.

  3. Load Address: BIOS loads the bootloader at address 0x7C00, so we need to start our code with org 0×7C00

  4. CPU mode: Remember, the bootloader runs in 16-bit real mode, so only 16 bit instructions are allowed (I know you're getting grandpa vibes but we are bound by the rules).

  5. Padding (Filling the space): Any leftover bytes between our code and the boot signature should be padded with 0's to make the total size exactly 512 bytes. Thankfully we don't have to do this ourselves, we can direct the assembler to do this for us.

  6. Infinite loop: After our code runs, the CPU will blindly continue executing whatever garbage data is in the next memory locations, which will crash the system. We need to stop it by putting the CPU into an infinite loop or halting it.


Step 2: Writing the Boot Sector - Let the smashing begin

Enough theory and if you are anything like me, you hated that part. Now let's get some real work done.

Our goal will be to successfully create a binary file that BIOS recognizes as a valid bootloader and that runs without crashing.

Now we will write the absolute minimal NASM assembly code that satisfies all the rules from step 1.

So open up your favorite editor (vim), write the following code as it is and save it as boot.asm:

org 0x7c00  ; Set the origin point. The BIOS will load us at this address.

main:
    jmp $   ; Freeze the CPU right here. (We'll replace this later!)

; --- Padding & Magic Boot Signature ---
; Our little 'jmp' instruction doesn't fill up the whole sector.
; We need to pad the rest of the file up to 510 bytes with zeros.
times 510 - ($-$$) db 0

; Finally, the all-important two-byte boot signature.
; This is the secret handshake that tells the BIOS, "Yes, I'm bootable."
dw 0xaa55

What did we just do?

Let me break it down, line by line:

  • org 0x7c00: This isn't an instruction for the CPU, it's for the assembler. It's us saying, "Hey NASM, assume all our labels and data are starting at address 0x7C00." The BIOS made a promise to put us here, so we gotta make sure our calculations are right.

  • jmp $: This is our whole "program" for now. jmp means jump. $ means "the current address". So this literally translates to "jump to this line forever." It's an infinite loop. It stops the CPU from getting lost and executing random garbage after our code. Think of it as a parking brake.

  • times 510 - ($-$$) db 0: This looks scary, but it's just math.

    • $ is where we are now.

    • $$ is where we started.

    • So $-$$ is the size of our code so far.

    • We need to get to 510 bytes total. So 510 - (size_of_our_code) tells us how many zeros we need to add.

    • times repeats db 0 (which means "declare a byte of zero") that many times.

    • In simple terms: "Fill the rest of the space with zeros until we hit byte 510."

  • dw 0xaa55: This is the boot signature. The last two bytes must be 0x55 and 0xAA. We write it as 0xAA55 because the CPU is little-endian-it writes the little byte first. So it'll automatically store it as 55 AA. This is the secret handshake that tells the BIOS we're legit.


Step 3: Let's See It in Action!

After all this stuff, we gotta make sure if this thing is not just a fancy text file. For that, we need to follow a few steps (only 2, actually).

  1. Assemble it with NASM: Fire up the terminal, navigate to where you saved boot.asm and smash this command:

     nasm -f bin boot.asm -o boot.bin
    

    This tells NASM to pack our code into a raw binary .bin file, no fancy headers, just the pure 512 bytes we wrote.

  2. Boot it with QEMU: Now for the moment of truth. Run this:

     qemu-system-x86_64 -fda boot.bin
    

    This tells QEMU to treat our boot.bin as a floppy disk (-fda) and boot from it.

  3. Looking for signs of life: A QEMU window will pop up. And it will look... profoundly boring. It's probably just a black screen with maybe a "_" cursor or a "Booting from Floppy..." message that does nothing.

You might even think that I have successfully wasted your precious time and have manipulated you into thinking that you can write a bootloader. But...

This black screen is what we were looking for. This is the sign of life I mentioned earlier. It means the BIOS liked our signature, loaded up our code, and is now peacefully stuck in our infinite loop. Congratulations! We've successfully hijacked the CPU. It's ours now.

If the window instantly closes or you get an error, I'm 100% sure that you made a mistake. So I recommend you get your eyes checked and read this blog once again.

Remember, a single typo will be enough to sink this Titanic.


You've just written a freaking bootloader!

Take a deep breath… just kidding! Go ahead, scream your lungs out and wake up your neighbors (if you have any) — you just wrote a bootloader from scratch.

Seriously, stop and think about it for a second. You write code that BIOS understands, loads and runs.

You're literally communicating with the hardware and that, that's insanely cool.

This was my first time writing a blog, so pardon the rough edges—but I wanted to share the excitement raw and unfiltered.

In the next part, we're gonna make this thing actually do something. We are gonna tear out that boring jmp $ and make it talk to the screen.

Get ready to print some text and finally give that black screen a reason to exist.

Got it working? Smashed your keyboard in a fit of glory? Found a typo that sunk your own personal Titanic? Let me know in the comments!

Until next time, go see your therapist and happy hacking!

1
Subscribe to my newsletter

Read articles from Shuja Ahmad directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Shuja Ahmad
Shuja Ahmad

Passionate about low-level programming and system internals. Currently exploring bootloader development and sharing my journey with the developer community.