This morning Gilson and I were talking about the structure of disk’s partition table/master boot record (mbr)/boot loaders.
After reading a bit about it [1] [2], we learned where the boot loader code goes in, and decided to change that code to do something else – to print ‘Hello World’, of course!
Maluta was around at the moment and suggested me to post about it. Here it goes :-)
In this post I detail assembly examples (using NASM [3]) printing text on boot, note some points on mbr and testing with QEMU [4].
The Boot Process
When switched on or reset, an x86 processor executes instructions at address FFFFh:0000h.
In IBM PC compatibles, this address is mapped to a ROM chip that contains the computer’s BIOS code.
After tests and initializations, the BIOS executes the actual boot loading, performing (among other things) the following steps:
- Choose boot-device (setup-configured priority)
- Check for boot-device signature (the value 0xAA55 in the last 2 bytes of device’s first sector)
- Load the first sector (or boot sector) of boot-device into RAM (at address 0000:7C00) and initiate its execution
Testing with QEMU
To test the examples, the only thing needed is a virtual disk – a disk image file.
Actually, the only thing needed is it’s first sector; a 512-byte image file.
One way is to create the image file by using dd:
dd if=/dev/zero of=disk.img bs=512 count=1
The file is really small, tough.
It is possible to create such a file from the assembler too, allocating bytes in the code so that it end up with a 512-byte file.
Actually the latter is better in this case, as it also provides a simple way to insert the boot-device signature in the image file.
The assembly code can be assembled with:
nasm example.asm -o disk.img
Then launch QEMU using the image file as primary hard disk.
qemu -hda disk.img
Examples
The examples use the interrupts [5] [6] 10h (Video BIOS Services) and 16h (Keyboard BIOS Services), respectively used to write a single character to the video screen and read a single character from the keyboard (actually used to pause the program).
In the 2 first examples, for legibility’s sake, the code for generating a 512-byte file and the boot signature will be omitted; that code is presented in the 3rd example.
- Printing a single character (‘H‘)
; Print Character ('H')
mov ah, 0Eh
mov al, 'H'
int 10h
; Read Character
mov ah, 00h
int 16h
QEMU Output
Booting from Hard Disk...
H
- Printing 10 characters (‘ABCDEFGHIJ‘) in a loop
start: mov cx, 0Ah ; Loop Counter: 10 (0Ah) times
mov ah, 0Eh ; Set 'int 10h' Operation
mov al, 'A' ; Start with character 'A'
loop: int 10h ; Print character
inc al ; Change to next character
dec cx ; Check Loop End
jnz loop ; Loop Again
done: mov ah, 00h ; Read Character
int 16h
QEMU Output
Booting from Hard Disk...
ABCDEFGHIJ
- Generating a 512-byte file with boot signature
; These lines go after your code.
zero times 512 -($-$$) -2 db 0 ; zero remaining-to-512 bytes, except the last 2 bytes.
signature dw 0xAA55 ; last 2 bytes: boot signature
Try opening disk.img in an hexadecimal editor. ;-)
This is the final example, based on [7]. It is a bit more complex, as it is complete and 3 new features were used:
- Zero-ended String
- Pointers
- Effective Addresses
To ease understanding the code, it is commented with a C-style algorithm.
org 7C00h ; needed as using effective addresses (lea)
start: mov ah, 0Eh ; operation op = PRINT_CHAR;
lea si, [message] ; for (char *currentChar = &message[0]; ...
; {
print: mov al, [si] ; char buffer = *currentChar;
cmp al, 0 ; ... *currentChar != 0; ...
jz done
int 10h ; executeOperation(VIDEO, op, buffer);
; }
inc si ; ... currentChar++)
jmp print
done: mov ah, 00h ; op = READ_CHAR;
int 16h ; executeOperation(KEYBOARD, op, NULL)
message db 'Hello World!', 0 ; char message[] = "Hello World!"
zero times 512 -($-$$) -2 db 0 ; char empty[512 - bytesUntilHere() - 2] = 0;
signature dw 0xAA55 ; char signature[2] = 0xAA55;
QEMU Output
Booting from Hard Disk...
Hello World!
Related Errors
- Image file smaller than 512-byte
QEMU Output
Booting from Hard Disk...
Boot failed: could not read the boot disk
Reason
The disk need at least one complete sector, and that is 512 bytes.
- Image file without boot signature
QEMU Output
Booting from Hard Disk...
Boot failed: not a bootable disk
Reason
The BIOS checks for the boot signature.
Special Thanks
- Gilson, for the chatting
- John, for joining me in this ‘assembly day’
- Maluta, for the post suggestion
- Vantuil, for assembly classes that made Hello World really seem easy like Hello Worlds (lol..)
Ending
I hope this post may be useful for you.
Until next post. ;-)
References
[1] Wikipedia; Master Boot Record
[2] Wikibooks; x86 Assembly/Bootloaders
[3] NASM: The Netwide Assembler
[4] QEMU; About
[5] Interrupt Services DOS, BIOS, EMS und Mouse
[6] Interrupt 10h
[7] Make your own operating system with x86 assembly (part 1)