[BootBoot] Part 1: Boot from a floppy drive and clear the screen

Hi there! This is my first attempt to write a program in x86 assembly. I’ll call it BootBoot and it will be a boot loader that boots a program from the first floppy drive, displays an effect, and then boots grub and the OS. I am going to write a blog post after each commit to remember the steps I followed (hoping that writing my next ASM program will be easier if every step of this one is documented). In this post (part 1) my goal is to load a program that boots from the floppy drive and clears the screen.


Prerequisites:

My tools (for the moment) are:

  • NASM: an asssembler for the x86 CPU architecture.
    To install it on Debian: sudo apt-get install nasm
  • QEMU: a generic and open source machine emulator and virtualizer.
    To install it on Debian: sudo apt-get install qemu qemu-system-x86
  • GNU Make: a utility which controls the generation of executables and other target files of a program from the program’s source files.
    I guess everyone has this one: sudo apt-get install make

I need nasm to assemble the assembly code and qemu to boot an imaginary floppy drive to avoid actual reboots of the computer I use for the development. I’ve put my initial rules in a Makefile:

ASFLAGS are the assembler flags and -f means to generate flat binary files (files without headers). I am going to use one flat file for all my code for simplicity.

The following rule creates a floppy image of 512 bytes:

 

To load it using qemu one needs to call make run which is equivalent to creating a floppy image and calling qemu-system-i386 -fda <floppy image> afterwards. More on Makefiles here and here. -fda means the first floppy disk found in the system (floppy disk a).

With this Makefile I can type make, make clean and make run to assemble, clear and run the program respectively.


Code:

I am going to push my commits in this repository. The code of this first commit is here.


Let’s start writing the code:

First thing, we need to set the address from which the assembler will begin reading the commands. We can do this by calling: org 7c00h where 7c00h (h is for hex) is the address where the bios loads the first sector.

When we boot an x86 system, it is in what is called “real mode” or “8086 mode”, which is the architecture we would have if we were booting an actual 8086 computer. As 8086 is a 16-bit microprocessor, we must tell the assembler that we are going to use 16 bits. The x86 assembly command for this is: bits 16.

We are going to use the label start: to mark the beginning of the program. I am going to follow a convention I’ve seen in other people’s code in this program: I will use labels that start with a dot for repetitive local changes (for example loops) and labels without a dot to mark pieces of code where I tend to jump often (for example a piece of code that I’d put in a function if I was writing C).

And before we begin writing the actual program I am going to push the 7c00h address in the stack pointer calling mov sp, 7c00h.

The code so far is the following:

Let’s clear the screen!

Now, we need to set video mode. I’m going to use a relatively “simple” video mode that uses 256 colors: 320x200. To select it we’ll use the video bios interrupt int 10h.

According to the documentation here we need to fill the ax register with 0 in the 16 higher bits (register ah) to select the service and with the number that corresponds to the desired video mode in the 16 lower bits (register al). The desired video mode is: 13h = G  40x25  8x8   320x200  256/256K  .   A000 VGA,MCGA,ATI VIP, and so we write:

to fill the ah with 0, the al with 13 (in hex) and to send the video mode interrupt ( int 10h).

Now we can finally clear the screen.

As we might need to do this often, we can use a label clearscreen (in C that would be a function) where we can jump everytime we need to clear the screen. Something like this:

To understand clearscreen we need to understand a few things about mode 13h and real (8086) mode.

In 13h, the video RAM is mapped in a0000 and the first 64000 bytes appear on the screen immediately after update.

The memory addresses in real-mode are using 20bits and have 2 16-bits overlapping parts: the offset part and the segment part. The segment is shifted by 4bits (to the left so it becomes 20bits) and is added to the overlapping offset.

The segment registers we can use are es, ds, cs, ss (and: fs, gs in some systems: 386 and on). In clearscreen I used the ds that is the default segment register.

We can’t write values to ds directly. Therefore we are going to use a common register (ax) to write our value and then move its content to ds:

We shifted the a0000 by 4bits (and so it become a000 and we wrote 0a000 because it starts by a letter and not a digit) and then we moved its value to ds.

Then we’ve set di (one of the registers we usually use in address calculations and stands for destination index) to 0. Note that this is because the offset of the address calculation can be paired with any register.

Now we can start a loop and fill all the 64000 bytes from the a0000 memory address and on. To do so we need a color and a counter.

The color we are going to use is the one we set to ax in this line: mov ax, 3 (it’s a light blue in the default palette).

We will need a counter to count the 64000 bytes so that we know when to exit the loop: mov cx, 64000.

Let’s see the loop:

The brackets around [di] are used to dereferrence it: we are going to modify the memory address of di. We move the lower 8 bits of al in di address to write the color in the memory and we increase di to point at the next byte ( inc di ).

Then, we decrease the counter ( dec cx ). We want to exit the loop when the counter value reaches 0, and so we jump to the beginning of the loop while cx is not 0 with jnz .loop_begin (where jnz means jump, non zero).

When we exit the loop ret will redirect the execution to the command that follows the call clearscreen. It’s similar to return in C.

After that we create an infinite loop to have our program refreshing the pixels for long so that we can check the color.

There’s one final “trick” I used at the end of the program:

This “tells” the assembler to fill the remaining bytes of the 512 bytes of the floppy disk with 0 (the bytes that aren’t occupied by the program). I did this to fill the image I use with qemu to avoid potential problems.


Result:

Voilà the program running in qemu after this first commit:


Putting things together:

Code:

Makefile:

and that’s it.

Both files will be extended in the next post.


Links:

[1]: Ralf Brown’s Interrupt List
[2]: Mode 13h in Wikipedia
[3]: X86 memory segmentation
[4]: NASM – The Netwide Assembler

Leave a Reply

Your email address will not be published. Required fields are marked *