Table of Contents
- Introduction
- Starting My Learning Journey
- My First Introduction to Intel 8086
- A Simple Look at 8086 Architecture
- Setting Up the Development Environment
- Understanding the Boot Process
- Writing My First Bootloader
- Conclusion
My First Experience in the World of Low-Level Programming: Building My First Bootloader
Part 1
For a long time, one question had been stuck in my mind:
What actually happens inside a processor, and how do the code we write get executed at the lowest level?
I already knew that the code we write in high-level programming languages like Java, Python, Kotlin, and others eventually needs to be translated into a language that the processor can understand.
For example, in Java, the code we write is first compiled into Bytecode by the Compiler. Then, it is executed by the Java Virtual Machine (JVM) and eventually translated into instructions that the processor can execute.
This means that behind even the simplest programs we run, the processor is performing billions of operations on zeros and ones in a fraction of a second.
That is truly amazing!
This curiosity pushed me to challenge myself and enter a completely new world, a world much closer to the hardware than languages like Java and Python.
Starting My Learning Journey
The first thing I did was search through different resources. Like always, my first destination was YouTube.
I found a video titled:
How to build an Operating System from scratch
and opened it with a lot of excitement.
A few minutes later, I realized that I barely understood anything, so I respectfully closed the video with a little bit of fear and confusion π
I understood that building an operating system from scratch was still far beyond my current level, and I needed to start with more fundamental concepts.
I started reading articles, watching educational videos, and exploring different resources until I realized that many computer architecture and operating system tutorials start with simpler and older architectures, such as x86 8086.
My First Introduction to Intel 8086
The good news was that I did not need to manually write millions of zeros and ones inside a text file just to create a simple program.
The not-so-good news was that I had to learn Assembly π
I started by collecting information about the Intel 8086 processor.
The Intel 8086 was a 16-bit processor introduced by Intel in 1978 and became one of the most important processors in the x86 family.
To learn this architecture, educational tools like emu8086 allow us to write Assembly code, execute it, and observe the processor's Registers and Flags step by step.
This was truly fascinating for me.
Imagine being able to see exactly what instructions the processor executes, how Register values change, and what the CPU state looks like at every moment!
But there was one problem...
I had no idea what a Register was, what a Flag was, what Memory Addressing meant, or what the CPU was actually doing.
So I spent several days trying to understand the basics of x86 architecture first.
Of course, I have to admit that for someone who has just moved from:
print("hello world")
into the world of processors, looking at the internal structure of a CPU can be a little scary π
A Simple Look at the Structure of 8086

Image credit: ElectronicsMind - Intel 8086 Microprocessor Architecture
The 8086 processor consists of different parts, but for a beginner, we can focus on the most important ones.
Execution Unit (EU)
The Execution Unit is responsible for interpreting and executing instructions.
This unit includes components such as:
- ALU (Arithmetic Logic Unit) for performing arithmetic and logical operations
- General-purpose Registers
- Flag Registers
Bus Interface Unit (BIU)
The Bus Interface Unit is responsible for communication between the CPU and memory, as well as fetching instructions from RAM.
This unit includes:
- Segment Registers
- Instruction Pointer (IP)
- Memory address generation logic
Registers
Registers are extremely small and extremely fast storage locations inside the CPU.
The processor cannot always work directly with RAM for operations such as calculations, data transfers, and instruction execution because RAM is slower compared to the CPU.
Because of this, the CPU uses Registers as a very fast workspace to keep the data it needs closer to the processing unit.
In Low-Level Programming, Registers are one of the most important tools for directly interacting with the CPU.
Some Registers are used for storing data, some for memory addressing, some for managing the Stack, and some for controlling the execution flow of a program.
In fact, we are going to use these tiny storage locations inside the CPU to control program execution step by step.
My First Assembly Code Execution
After getting a basic understanding of the processor architecture, I told myself:
"I will not truly understand these concepts until I start writing code."
So I decided to write my first Assembly program.
At first, I tried emu8086, but I could not really connect with its environment.
Instead, I decided to use my favorite editor, Vim.
To prepare my development environment, I installed the required tools:
Ubuntu / Debian
sudo apt update
sudo apt install nasm qemu-system-x86 gdb binutils
Fedora
sudo dnf install nasm qemu-system-x86 gdb binutils
To verify the installation:
nasm -version
qemu-system-i386 --version
gdb --version
The main tools I used were:
- NASM for converting Assembly code into Binary files
- QEMU for running the Bootloader in an emulated environment
- GDB for debugging the CPU and inspecting Registers
- Binutils for analyzing Binary files
From Turning On the Computer to Executing My First Bootloader
After getting familiar with the CPU and the 8086 architecture, it was time to understand what exactly happens when a computer starts.
How can a computer that has not executed any program yet, run its first piece of code?
This is where I learned about the concept of the Boot Process.
What is a Bootloader?
Simply put, a Bootloader is a small program that is executed during the early stages of system startup and prepares the system for the next steps.
In real operating systems, the Bootloader is usually responsible for finding the Kernel from storage and loading it into RAM.
However, to start learning, I decided to write a very simple Bootloader that does only one thing:
Display a character on the screen.
It may sound simple, but this small action means that I am executing my first piece of code that runs directly on the CPU before any operating system exists.
What Happens When a Computer Starts?
The boot process of a BIOS-based system can be summarized like this:
Power On
|
β
CPU Reset
|
β
BIOS starts executing
|
β
POST (Power-On Self Test)
|
β
Find Boot Device
|
β
Load Bootloader into RAM
|
β
Jump to Bootloader Code
|
β
Bootloader executes
Now let's look at these steps in more detail.
1. Turning on the system
When we press the Power button, the CPU exits the Reset state and starts executing its first instructions.
2. Executing BIOS
In older systems based on BIOS, the initial control is transferred to the system Firmware.
BIOS stands for:
Basic Input/Output System
and its job is to check and initialize the main hardware components of the system.
During this stage, a test called:
POST (Power-On Self Test)
is performed to check components such as:
- RAM
- Keyboard
- Display
- Storage Devices
3. Finding the Boot Device
After POST is completed successfully, BIOS searches for a bootable device according to the configured Boot Order.
For example:
- Hard Drive
- USB
- CD/DVD
4. Finding the Bootloader
In traditional BIOS-based systems, BIOS checks the first sector of the bootable device.
This sector must contain a special signature in its last two bytes:
0x55AA
This is called:
Boot Signature
If this signature exists, BIOS recognizes this sector as a valid boot sector.
Then, it loads these 512 bytes into a specific memory address:
0x7C00
and transfers control of execution to the CPU.
From this moment, the code we wrote starts running.
My First Bootloader
After understanding the concept of the Boot Process, it was time to write my first piece of code.
The goal was very simple:
Just display the character:
N
on the screen.
The code:
[bits 16]
org 0x7c00
mov ah, 0x0e
mov al, 'N'
int 0x10
times 510 - ($ - $$) db 0
dw 0xAA55
Understanding the Code
This simple Bootloader runs in:
16-bit Real Mode
Defining the CPU Mode
[bits 16]
This tells the Assembler that our code is written for a 16-bit processor mode.
Bootloader Location
org 0x7c00
This tells the Assembler that this code will be loaded and executed at memory address 0x7C00.
This is the address where BIOS usually places the Boot Sector.
Loading Values into Registers
mov ah, 0x0e
mov al, 'N'
In this section, we place the required values into CPU Registers.
For a simpler understanding, we can compare this idea to assigning values to variables in high-level programming languages:
int value = 0x0e;
However, the important difference is that in Assembly, we do not work with variables created by a Compiler.
Instead, we directly control real Registers inside the CPU.
Here:
- The value
0x0Eis placed inside theAHRegister. - The character
Nis placed inside theALRegister.
The value 0x0E is a Service Number for BIOS that tells it we want to display a character using Teletype Mode.
Calling BIOS Interrupt
int 0x10
This instruction calls a:
BIOS Video Interrupt
The control is transferred to BIOS video services, which perform the character display operation.
Completing the Boot Sector
A Boot Sector must be exactly 512 bytes in size.
For this reason:
times 510 - ($ - $$) db 0
fills the remaining space of the file with zeros.
At the end:
dw 0xAA55
adds the Boot Signature so that BIOS can recognize this sector as a valid Bootloader.
Building and Running the Bootloader
After writing the code, I saved the file as:
boot.asm
Then, using NASM, I converted it into a Binary file:
nasm -f bin boot.asm -o boot.bin
This command creates a raw Binary file that can be executed as a Boot Sector.
To run it, I used QEMU:
qemu-system-i386 -fda boot.bin
or
qemu-system-x86_64 -fda boot.bin
And the result?
A simple character appeared on the screen:
N
But for me, it was much more than just a character.
This was the first time I had written code that ran directly on the CPU without an operating system, without any Runtime, and without any Framework.
At that moment, I finally understood why the world of Low-Level Programming is so fascinating.
At the lowest level, there are no ready-made layers hiding the details.
There is only:
You, the CPU, memory, and a few Assembly instructions.
Continuing the Journey
This experience was only a small beginning.
By writing this simple Bootloader, my interest in Low-Level Programming became much stronger, and I decided to continue this learning journey.
My next goals are:
- Learning Assembly more deeply
- Building more advanced Bootloaders
- Eventually writing a simple Kernel
This was only the first step...
My First Bootloader on GitHub
You can find the source code of my first Bootloader here:

Top comments (0)