A bootloader is the first code that runs when a computer is turned on. It is responsible for loading the operating system into memory and starting its execution. In this tutorial, we will create a simple bootloader that writes "Hello, World!" to the screen.
Before we begin, it is important to note that writing a bootloader requires some knowledge of low-level programming and x86 assembly language. If you are not familiar with these topics, it is recommended that you first learn the basics of assembly language and computer architecture.
Let's get started!
To create a bootloader, we will need an x86 assembly language compiler, such as NASM (Netwide Assembler), and a program to write the bootloader to the disk, such as dd (Unix) or Win32DiskImager (Windows).
We will also need a text editor to write the bootloader code. Any text editor will work, but a code editor with syntax highlighting for assembly language, such as Visual Studio Code or Sublime Text, is recommended.
The first thing we need to do is set the origin of the code to 0x7c00. This is the address where the bootloader will be loaded by the BIOS. We do this with the [org 0x7c00] directive.
Next, we clear the ax register with xor ax, ax and set the data segment and extra segment registers to 0 with mov ds, ax and mov es, ax.
We then load the address of the "Hello, World!" message into the si register with mov si, msg and call the print function.
Finally, we enter an infinite loop with jmp $ to prevent the bootloader from continuing to execute random code in memory.
Here's the complete code:
xor ax, ax ; Clear ax
mov ds, ax ; Set data segment to 0
mov es, ax ; Set extra segment to 0
mov si, msg ; Set si to point to the message
call print ; Call print function
jmp $ ; Infinite loop
lodsb ; Load character from si into al
or al, al ; Check for null terminator
jz done ; If null terminator, jump to done
mov ah, 0x0e ; Set teletype mode
int 0x10 ; Call BIOS interrupt to print character
jmp print ; Loop back to print next character
db "Hello, World!", 0 ; Null-terminated message stringtimes 510-($-$$) db 0 ; Fill the rest of the sector with 0s dw 0xaa55 ; Boot signature
The times 510-($-$$) db 0 directive fills the rest of the sector with 0s up to offset 0x1fe, where we then append the boot signature with dw 0xaa55.
To compile the code, we use the NASM assembler. We save the code in a file called bootloader.asm and run the following command:
nasm -f bin bootloader.asm -o bootloader.bin
This command assembles the code and outputs a binary file called bootloader.bin.
To test the bootloader, we write it to a disk image using the dd command (Unix) or Win32DiskImager (Windows). We assume that our disk image is called bootdisk.img.
On Linux, we run the following command to write the bootloader to the first sector of the disk image:
dd if=bootloader.bin of=bootdisk.img bs=512 count=1 conv=notrunc
On Windows, we use Win32DiskImager to write the bootloader.bin file to the disk.
To write the bootloader.bin file directly to a USB stick, you can use the dd command with the USB device as the output file. Here's an example command to do that:
dd if=bootloader.bin of=/dev/sdX bs=512 count=1 conv=notrunc
Here, /dev/sdX should be replaced with the actual device name of your USB stick. Note that you need to be very careful when specifying the output device name, as choosing the wrong device can lead to data loss.
Make sure you know which device name corresponds to your USB stick by running the lsblk command before executing the dd command. Also, note that writing directly to a USB stick will overwrite any existing data on the device, so make sure you have a backup of any important data before proceeding.
To boot the bootloader that was written to a USB stick, you will need to configure your system's BIOS to boot from the USB device. Here are the general steps to follow:
Insert the USB stick into your computer and power it on.
As the computer boots up, press the key (usually F2, F12, or Del) to enter the BIOS setup utility.
In the BIOS setup utility, navigate to the Boot options menu and set the USB device as the first boot device in the boot order. The specific steps to do this will depend on your computer's BIOS, but generally, you will need to do the following:
Save your changes and exit the BIOS setup utility. This will cause your computer to reboot, and it should now boot from the USB device.
If everything was set up correctly, you should see the "Hello, World!" message displayed on the screen.
Note that the specific steps may vary depending on your computer's BIOS, but the general idea is the same: configure the BIOS to boot from the USB device, and then start the computer to boot from the USB device.
Alternatively you might want to use an emulator instead, we recommend using QEMU emulator. Here is how you boot your bootloader using QEMU emulator.
qemu-system-x86_64 -drive format=raw,file=bootloader.bin
Note that the qemu-system-x86_64 command is used to start QEMU for x86_64 (64-bit) systems. If you're using a different architecture, you will need to adjust the command accordingly.
Note that QEMU has many options and features that you can use to customize the emulator's behavior. You can consult the QEMU documentation for more information on how to use these options.
Want to learn more about kernel development? Learn to create a multi-threaded kernel from scratch with our best selling course found here: https://dragonzap.com/course/developing-a-multithreaded-kernel-from-scratch