In this tutorial we will learn how to create a bootloader that loads some sectors and executes them.
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.
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.