Operating System Development: First Bootloader Code

This tutorial shows how to write Intel assembler code for a first-stage boot loader for a toy operating system. We review the structure of the boot sector, and write code for resetting the disk system, rebooting, and writing a string to the screen. Now that we know the structure of the boot sector’s boot parameter […]

This article was posted by Independent Software, a website and database application development company based in Maputo, Mozambique. Our website offers regular write-ups on technical and design issues, ranging from details at code level to 3D Studio Max rendering. Read more about Independent Software's philosophy, or get in touch with Independent Software.

This tutorial shows how to write Intel assembler code for a first-stage boot loader for a toy operating system. We review the structure of the boot sector, and write code for resetting the disk system, rebooting, and writing a string to the screen.

Now that we know the structure of the boot sector’s boot parameter block (BPB) and extended boot parameter block (EBPB), we can start writing our first boot loader code in GNU assembler.  (If you need a refresher, please have a look at the first part of this series about the structure of the boot sector).

This article is part of a series on toy operating system development.

First code in GNU assembler

We’ll be using the GNU assembler, since it’s free, comes with a boatload of options, supports AT&T and Intel assembly syntax and plays nice with gcc and ld later on. Some of the preprocessor directives used may need some explanation, but all code will be in straightforward Intel syntax.

Here’s some boilerplate code to get started:

The pile of preprocessor instructions at the top tell the assembler to assemble code for real mode. Since all (intel-based) computers start up in real mode with 16-bit instructions, we won’t be able to write 32-bit code here yet. We also instruct GNU assembler that we’ll be using Intel syntax (e.g.  mov ax, 1  instead of  movw $1, %ax  – some prefer the latter, but most readers of this text will be familiar with Intel).  The origin of our code will be 0x0, i.e. all absolute addresses start at 0x0, which will be convenient.

Then there’s the main entry point of our code, which corresponds to the first byte of actual output when assembled. The code under “main” simply jumps over the BPB and EBPB located at offset 0x3, resuming execution at the label start. We’ll flesh out the BPB/EBPB in a bit, since it’ll have to have a very exact size.

We’ve also defined a constant LOAD_SEGMENT, which is the segment where we’ll be loading our second stage boot loader (more about that later).

The Boot Parameter Block

The structure of the boot parameter block can be coded like this:

The fields in this structure correspond to the specification in the first part of this text about the structure of the boot sector, and since they’re nicely labelled, we’ll be able to refer to them later on.

Real-mode Segments

After the start label, we can write some actual code. Let’s start by defining our real mode data segments:

Here, we mask interrupts so that interrupt calls don’t mess up our sector declarations. We set ES = DS = SS = CS = 0x0, and make the stack grow down from 0x7C00  (our boot loader was loaded at 0x7C00). When done, we turn the interrupts back on. It’s important to note that the BIOS places the number of the boot drive in the DL register. We store it in our BPB for later use.

Resetting the disk system

Next, we need to prepare the floppy drive for use. This is done through BIOS interrupt 0x13, subfunction 0. We call it with the boot drive in DL:

If the reset fails, the carry flag will be set and we jump to a label where we handle a boot failure by showing a message, waiting for a key press and rebooting. Come to think of it, we’ll need a way to print a string to the screen.

Printing a string

We’ll add a short function that uses BIOS interrupt 0x10, sub-function 9 to print characters to the screen. The calling code must point DS:SI to the null-terminated string to be printed.

We can now define the “bootFailure” label:

Great. We’ve got code to reset the floppy drive, and if it fails, there’s code that prints failure strings and reboots. Although, we still have to write a Reboot function.

Rebooting

Here is some code that prints a “Press any key to reboot” message, waits for a keystroke, and reboots the machine.

Here, we use BIOS interrupt 0x16, sub-function 0 to read a key (any key). We then add a far jump to 0xffff:0000  which causes the machine to reboot.

Putting it all together

When we combine the functions above into a single source file, we end up with this:

Points of note

  • The WriteString and Reboot sections are functions, that we’ll want to call various times in the other boot code that we’ll write soon. They are not part of the main body of code. That is why they are placed before the start label, so that execution will jump over them.
  • The loader will not print a “Loading OS…” message right after it sets the segments.
  • After having reset the disk system successfully, the loader will reboot. We’re doing that only because we haven’t written any more code yet that does interesting things.
  • The source code ends with a  .fill  preprocessor directive. This causes the assembler to fill up the output file with null bytes all the way to offset 510. The final two bytes contain the magic word required by some BIOSes. Compilation will now yield a file of exactly 512 bytes, which is what we need for our boot sector.

Summary

We’ve written assembler code that prepares data and stack segments and resets the floppy drive. We’ve also added functions for writing text to the screen, waiting for a keypress, and rebooting.

I don’t know about you, but I’m just about ready to see this code in action! In the next section on setting up a toolchain and using Bochs, we’ll see how we can actually compile and test this code.

Series index Next tutorial: Setting up a toolchain and using Bochs

Save

Save

Save

Save

Save

Did this article help you out? Please help us find more time to write useful guides & articles like this by donating a buck or two. It'll keep us coffee-fueled. Thanks!

Trackbacks

  1. Writing your own boot loader for a toy operating system | Independent Software
  2. Writing your own toy operating system: Setting up a toolchain and using Bochs | Independent Software

Comments

13 13 Responses to “Writing your own toy operating system: First bootloader code”
  1. Utkarsh says:

    Awesome guide dude..!!
    When are you gonna post the next section..???

    • alex says:

      Uh, soon… My toy OS guide got a bit sidelined with other projects. I do have all the code that I wanted to discuss here, so I should be all set to write the rest. Check back later!

      Note that I did make some changes and corrections just now, and I think in the next section we should compile all this and see how it runs.

      EDIT: section 3 is done now.

  2. Isaac says:

    Can I use this code in the commercial world?

  3. Isaac says:

    Will your boot parameter block also work for fat32 or only for fat 16?

  4. gulab says:

    I have changed the comment line for compile
    ; load the boot loader to segment 1000h
    # load the boot loader to segment 1000h

    • alex says:

      Oops, my bad. I switched to GNU-style comments at some point and left that in there. It’s gone in my own code, so I forgot to update the website. Thanks!

  5. dennis says:

    Great stuff! Just a remark on the padding: The BootMagic as .int is 4 bytes long, resulting in a 514 byte boot sector. Using .word as data type fixes this.

  6. tahir says:

    i never found explanation like that on low level programming.So i request if he makes a tutorial on How to setup or install GNU assembler.it just for the sake of beginners

  7. Mohamad-Jaafar says:

    I am not assembly expert. But, why don’t you put .short instead of .int for the magic number so that it matches 512 bytes?

Leave a Reply

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