Writing A Simple Bootloader
“Low-level programming is good for the programmer’s soul.”
— John Carmack
Hello to everyone reading this. As the technology around us advances daily, we sometimes forget to appreciate the beauty of the very core basics on which our computers are built. So, today in this article I will try to explain what is a bootloader and how to build a basic one in as simple way as possible. Before moving forward I would like to appreciate this site for its amazing content and concisely delivered information. This article is also heavily inspired from this site. I would highly recommend reading the articles here for more details on the topic. To understand the code in this article a basic level of knowledge about assembly language is required. It is better if you have required knowledge in assembly language, however, i’ll try to cover most of the required concepts. So, let’s begin..
- How does my computer start?
Well, most of us might have thought that what actually happens when we press that power button. There is something known as PSU(Power Supply Unit) in your system hardware. When your press your power button, it sends signal to PSU which converts original AC current to DC current and supplies it to other components of the system. If it is able to provide proper power supply it sends a ‘power-good’ signal to the BIOS indicating that you may take charge now.
The BIOS on receiving this signal starts another process knows as POST(Power On Self Test).It sort of checks that we are ready to go or not. It tests that whether proper power supply is there or not, memory is not corrupted, what devices are connected etc. If anything is wrong it reports error either by showing a number to I/O port or by a sequence of beeps etc.
Now, after this last check if POST finds that everything is OK, it shows green flag to BIOS(just an analogy :) ). Hence, control is now transferred to BIOS.
Finally, we have reached BIOS. The BIOS now creates an Interrupt Vector Table(IVT). This table stores the addresses of various Interrupt Service Routines. Interrupt is the mechanism used by hardware to signal to the processor the occurrence of an event.
Now, the main job of BIOS is to load the bootloader. But how do we know where is it located in our storage device?
The answer to above question is that we have a sector on our storage device that is called boot sector.(Note: For more info on what are sectors read this). The boot sector is generally the first sector of the disk or sector 0 on track 0 on head 0. Now, BIOS moves the data in boot sector into main memory at address 0x7c00 and we will use interrupt 0x19 to jump to it.
If the above paragraph was not clear to you then don’t worry. When we discuss more on bootloaders you might understand it better.
2. Gathering necessary stuff..
Now, before we move forward and discuss more on bootloaders, we need to install a few things. First up, as we are going to write some code in assembly language we need an assembler to convert it to machine language. NASM is one such assembler for Intel x86 architecture. Second, we will install qemu, an emulator to test our bootloader as we don’t want to test this directly on our hardware.
To install NASM and qemu(on debian based OSs):
sudo apt-get install nasm qemu
For other OSs you may refer to internet.
3. Writing our Bootloader..
org 0x7c00bits 16Start: cli
hlt times 510 — ($-$$) db 0dw 0xAA55
The code might look a bit awkward to you or might look familiar. Whatever the case, let’s discuss what it means line by line.
First, we have ‘org 0x7c00’. As we have discussed earlier, our bootloader is loaded into memory at 0x7c00, so this line tells NASM to set all addresses according to the fact that first instruction is at 0x7c00.
Next line tells us that we are in 16 bit real mode. As x86 architecture has 16 bit registers, by default we boot into 16 bit real mode only. We can however switch to 32 bit protected mode, but I will not be discussing this here. You may however refer ‘this’ for more info on switching modes.
Now, cli and hlt. cli is used to clear all interrupts and hlt is used to halt the system. This ensures that CPU doesn’t run random instructions beyond our code.
Now, ‘times 510 — ($-$$) db 0’ . This needs some discussion. As we know that one sector contains only 512 bytes, we can’t write a bootloader that is bigger in size as we copy only one sector from disk(i.e. the boot sector). Also, we cannot have size less than 512 bytes as it may lead to random instructions or faults. So, to ensure that our code is 512 bytes in size we replace any of the remaining bytes with 0. $ represents the address of the current line while $$ represents address of the first line. Hence, ($-$$) represents size of the program. But why do we use only 510 and not 512?
We ensure that our code is of size 510 bytes because last bytes are reserved for some specific use. The last 2 bytes store the boot signature. The last line ensures 0xAA is stored at 511th byte and 0x55 is stored at 512th byte. These 2 bytes tell the BIOS that this sector is bootable.
Now, how to test our bootloader? Very Simple.
First, we need to assemble our code to machine code. Just enter this command.(considering name of your file in which write your program is myboot.asm, you may however use any name).
nasm -f bin -o myboot.bin myboot.asm
After this, run below command. It copies our bootloader to first sector of virtual floppy disk image using ‘dd’ utility
dd status=noxfer conv=notrunc if=myboot.bin of=myboot.flp
Finally, boot up your first bootloader :).
qemu-system-i386 -fda myboot.flp
Output: