How to Create a Bootable Program: Step-by-Step Tutorial

In this blog, we’ll explore how to write a minimal bootable operating system in Assembly and create a bootable ISO file that you can run in an emulator or directly on hardware. We'll detail every step, including the commands and what they do, so you can follow along easily.


Introduction

A computer boots by loading the first 512 bytes of a storage device (like a hard disk or USB). This 512-byte sector is called the boot sector, and it's where our custom bootloader resides. The BIOS loads this boot sector into memory and starts executing it. Here’s how we’ll achieve this:

  1. Write the bootloader in Assembly.
  2. Compile the bootloader to a binary file.
  3. Create a floppy disk image.
  4. Convert the floppy image into a bootable ISO file.
  5. Test it in an emulator like QEMU or VirtualBox.

Writing the Bootloader

We'll write two versions of the bootloader: one in NASM Assembly (boot.asm) and another in AT&T syntax (boot.s).

NASM Assembly (boot.asm)

org 0x7C00      ; BIOS loads boot sector to this address
bits 16         ; Use 16-bit mode

start:
    ; Set up segments
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x7C00

    ; Print message
    mov si, message
    call print_string

    ; Infinite loop
    jmp $

print_string:
    mov ah, 0x0E    ; BIOS teletype output
.loop:
    lodsb           ; Load next character
    test al, al     ; Check if end of string (0)
    jz .done        ; If zero, we're done
    int 0x10        ; Print character
    jmp .loop
.done:
    ret

message: db 'Hello, OS World!', 0

    ; Fill with zeros until 510 bytes
    times 510-($-$$) db 0
    ; Boot signature
    dw 0xAA55

AT&T Assembly (boot.s)

.code16
.global _start

.text
_start:
    # Set up segments
    xorw    %ax, %ax
    movw    %ax, %ds
    movw    %ax, %es
    movw    %ax, %ss
    movw    $0x7C00, %sp

    # Print message
    movw    $message, %si
    call    print_string

    # Infinite loop
    jmp     .

print_string:
    movb    $0x0E, %ah    /* BIOS teletype output */
1:  
    lodsb                  /* Load next character */
    testb   %al, %al      /* Check if end of string */
    jz      2f            /* If zero, we're done */
    int     $0x10         /* Print character */
    jmp     1b
2:  
    ret

message:
    .ascii  "Hello, OS World!"
    .byte   0

    /* Fill with zeros until 510 bytes */
    .org    510
    .word   0xAA55        /* Boot signature */

Both programs print "Hello, OS World!" to the screen using BIOS interrupts.


Compiling and Creating a Bootable Disk

Step 1: Assemble the Bootloader

  • For NASM Assembly (boot.asm):

    nasm -f bin boot.asm -o boot.bin
    
    • nasm: The assembler for the NASM syntax.
    • -f bin: Outputs a raw binary file.
    • boot.asm: Input file.
    • -o boot.bin: Specifies the output file.
  • For AT&T Assembly (boot.s):

    as -o boot.o boot.s
    ld -Ttext 0x7C00 --oformat binary -o boot.bin boot.o
    
    • as: GNU assembler for AT&T syntax.
    • ld: Links and creates the final binary.
    • -Ttext 0x7C00: Sets the load address to 0x7C00.
    • --oformat binary: Outputs a raw binary file.

Step 2: Create a Floppy Disk Image

Create a 1.44 MB floppy image and write the bootloader to it.

dd if=/dev/zero of=boot.img bs=1024 count=1440
dd if=boot.bin of=boot.img conv=notrunc
  • dd if=/dev/zero: Creates a blank file filled with zeros.
  • of=boot.img: Specifies the output file.
  • bs=1024 count=1440: Sets the size to 1.44 MB.
  • conv=notrunc: Ensures the bootloader overwrites the beginning without truncating the file.

Step 3: Create a Bootable ISO

Convert the floppy image into a bootable ISO using genisoimage:

mkdir -p iso
cp boot.img iso/
genisoimage -o boot.iso -b boot.img -hide boot.img iso/
  • mkdir -p iso: Creates a directory for the ISO structure.
  • cp boot.img iso/: Copies the floppy image to the ISO directory.
  • genisoimage: Generates the bootable ISO.
  • -o boot.iso: Specifies the output ISO file.
  • -b boot.img: Sets the boot image for the ISO.

Step 4: Test the ISO

Test the ISO using QEMU:

qemu-system-x86_64 -cdrom boot.iso
  • qemu-system-x86_64: Starts QEMU for 64-bit x86 systems.
  • -cdrom boot.iso: Boots from the ISO file.

Cleaning Up

To remove all generated files and directories:

rm -f boot.bin boot.img boot.iso
rm -rf iso

Conclusion

Congratulations! You've created a minimal bootable program from scratch. This guide serves as a foundation for more complex OS projects. Experiment with additional features, like reading from disk or switching to protected mode. The possibilities are endless!