User:Glauxosdev/Tutorials/Bootloader

From OSDev Wiki
Jump to navigation Jump to search

In this tutorial we will learn how to create a bootloader that loads some sectors and executes them.

Take 0x01

A bootloader is a real mode program that resides in the first sector of the disk; let it be on a hard disk or on a USB flash disk. The BIOS loads only the bootsector, so to start we have only 512 bytes, unless we load more sectors ourselves. The bootsector is loaded at address 0x00007C00, so using real mode segmentation the address is translated to 0x0000:0x7C00 or 0x07C0:0x0000. So for a start we have this code:

bits 16    ; tell the assembler we want 16 bit code
org 0x7C00 ; tell the assembler to add 0x7C00 offset to labels, segments will be zeroed

start:

    .setup_segments:
        mov ax, 0      ; pass 0 value to ax, as segments can't be set with immediate values
        mov ds, ax     ; ds = ax = 0
        mov es, ax     ; es = ax = 0
        mov fs, ax     ; fs = ax = 0
        mov gs, ax     ; gs = ax = 0
        mov ss, ax     ; ss = ax = 0
    
    .setup_stack:
        mov sp, 0x7C00 ; stack grows downwards from the start of the bootloader

    .hang:
        jmp $          ; jump infinitely to the same instruction

end_boot_sector:

    times 510 - ($ - $$) db 0
    db 0x55
    db 0xAA

Save this file as "boot.asm" and assemble it with nasm:

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

Then copy the resulting binary to the first sector of a USB flash disk, where xxx indicates the block device corresponding to the USB flash disk.

sudo dd if=boot.bin of=/dev/xxx

Note: Don't try to copy the binary to the hard disk, as it will become unbootable.

Reboot your computer and you will see a blinking cursor. Congratulations, you have made a bootable application. But it doesn't much. So, let's continue.

Take 0x02

Let's add some more code so we can load a sector:

bits 16    ; tell the assembler we want 16 bit code
org 0x7C00 ; tell the assembler to add 0x7C00 offset to labels, segments will be zeroed

start:

    .setup_segments:
        mov ax, 0      ; pass 0 value to ax, as segments can't be set with immediate values
        mov ds, ax     ; ds = ax = 0
        mov es, ax     ; es = ax = 0
        mov fs, ax     ; fs = ax = 0
        mov gs, ax     ; gs = ax = 0
        mov ss, ax     ; ss = ax = 0
    
    .setup_stack:
        mov sp, 0x7C00 ; stack grows downwards from the start of the bootloader
    
    .save_boot_device_number:
        mov byte [bootdev], dl ; boot device number is passed in dl by BIOS

    .check_lba_extensions:
        mov ah, 0x41
        mov bx, 0x55AA
        int 0x13
        jc .hang       ; carry flag is set if bios lba extensions don't exist
        cmp bx, 0xAA55 ; bx = 0xAA55 if bios lba extensions exist
        jne .hang

    .load_next_sector:
        mov eax, 1          ; load sector index 1
        mov bx, next_sector ; at address indicated by next_sector label
        mov cx, 1           ; load only 1 sector
        call read_sectors   ; call the routine to load sectors
        jmp 0x7E00          ; jump to newly loaded sector

    .hang:
        jmp $          ; jump infinitely to the same instruction

read_sectors:
    
    .save_registers:
        pusha ; registers are preserved

    .build_disk_address_packet:
        mov si, 0x8000             ; disk address packet is at 0x00008000 (ds = 0)
        mov word [ds:si], 0x10     ; size of packet is 16 bytes
        mov word [ds:si + 2], cx   ; sector count is in cx
        mov word [ds:si + 4], bx   ; memory offset is in bx
        mov word [ds:si + 6], fs   ; memory segment is in fs
        mov dword [ds:si + 8], eax ; low starting lba sector is in eax
        mov dword [ds:si + 12], 0

    .invoke_disk_int:
        mov ah, 0x42           ; bios extended read is used here
        mov dl, byte [bootdev] ; boot device will be read
        int 0x13               ; bios disk interrupt is invoked

    .return:
        popa ; registers are restored
        ret  ; return

variables:

    bootdev db 0

end_boot_sector:

    times 510 - ($ - $$) db 0
    db 0x55
    db 0xAA

; SECTOR 0x01 START ===================================================================

next_sector:
    
    .print_letter_A:
        mov bx, 0xB800       ; video memory resides at 0x000B8000
        mov es, bx
        mov bx, 0            ; print letter "A" at the first row and column
        mov byte [es:bx], 65

    .hang:
        jmp $                ; jump infinitely to the same instruction

end_second_sector:
   
    times 512 - ($ - next_sector) db 0

If you assemble this file and boot a USB flash disk with the resulting binary, you will see a letter "A" at the top-left corner of the screen, except if your computer is such old so it doesn't support BIOS LBA extensions.