Project 1: Bootloader


Overview

In a series of six course projects, you are going to develop a simple operating system that will cover most of the topics discussed throughout the course. In this first project, you will build the basic environment that will be needed to develop the operating system for the rest of the semester: the bootloader (bootblock). Specifically, your job is to implement bootblock.s and createimage.c.

The bootloader resides on the first sector of the booting device (a USB flash disk, hard drive, image file, etc.) and is automatically loaded and executed during the system booting process. It is responsible for loading the rest of the operating system from the booting device into memory.

Because GCC, the compiler used to compile the bootblock and kernel source, generates executable files in a specific format which needs operating system support (of course not available before the operating system itself is loaded) to run, we need the createimage tool to transform and pack them up into an image file that can be directly loaded and run on a bare machine. This file is called an image because it is loaded into the memory directly from disk.

To fulfill the requirements of this project, you must learn the:

The bootblock.s and createimage.s in this assignment will be used for future projects throughout the semester.

We have provided starter code which contains six files:


Steps

  1. Review project specs, read documentaion, plan
  2. Design Review
  3. Download the starter code
  4. Complete bootblock.s and createimage.c
  5. Compile & create the image
  6. Test
    • Run bochs with the image
    • Write the image to a USB disk or floppy, then boot off that device
  7. Complete general project requirements
  8. Submit

Design Review

First, take a look at
what we expect from you in general.

For this project, at your design review, we want you to write print_char and print_string functions in assembly. The first outputs a single character to the screen, and the second outputs a full string to the screen. Both should start printing at the cursor and advance the cursor as a result. You may implement these using any BIOS interrupts. Note that these are functions: they will be called with call and return with ret, and they should save and restore any modified registers. You may choose your own calling convention. The goal for this part (what we're grading) is your understanding of the stack and using interrupt calls. This is more important than getting the two functions to work perfectly.

You should be able to describe:


Building

A Makefile is provided for you. Type make to compile everything a generate the image file. The Makefile initially uses createimage but you may want to change it to createimage.given for the first half of the project to focus just on the bootloader.

Bootblock

When compiled, bootblock.s, will be written to the first sector of your boot device (USB, an image, etc.). You must write it in IA32 assembly language and it cannot exceed 512 bytes (the size of one disk sector). Its function will be to: You are required to handle kernels of size up to 128 sectors. You will get
extra credit if your bootblock can handle greater than 128 sectors. Reading up to 128 sectors can be done with one BIOS call, but you must beware that you cannot cross a physical segment (every 64K starting at address 0) in one read. In bochs, reading more than 64 sectors will fail if your image is used as a floppy disk instead of a hard disk.

When a PC boots the image/disk that you have prepared and the system has been initialized, the first sector of the boot disk (the bootloader) is loaded at address 0x07c0:0000 (0x7c00 in real addressing mode) and control jumps to that address.

Your bootloader can safely modify memory in the range [0x0a00, 0xa0000) without having to worry about overwriting video memory, the interrupt vector table, or BIOS. The kernel should be loaded at address 0x0000:1000. You may assume that the entire kernel is no more than 128 sectors long. Notice that a loaded kernel may overlap with the bootloader, so you will have to relocate the bootloader to a higher address that can't be overwritten before loading the kernel.

In order to load sectors, you must know the boot device number, which is stored in %dl. Common values are 0x0 for a floppy drive, 0x80 for the first hard drive, span class="hex">0x81 for the second, etc. You should save this value for later use.

Testing

We have provided createimage.given which is a linux-compiled binary version of createimage so that you may test your bootloader independently of the next half of the project. You can create an image using: ./createimage.given --extended bootblock kernel.

See Testing for information on how to test the resulting image.

Reading

Createimage

A linux tool to combine the bootloader and kernel, and any number of programs in ELF format, into a bootable image file. Additionally, this tool must somehow let the bootloader know how many sectors to read in order to fully load the kernel.

When a program is compiled in linux, an ELF executable file is generated (by default) that contains the program code and information telling the OS how to load the code into memory and what resources (dynamically-linked libraries, etc.) are required. Such an executable may contain multiplei fragments of code, each expecting to be loaded to a particular offset in memory -- as specified in the ELF header. When the OS loads the executable file, it will copy each code fragment to the correct offset in memory.

However, when booting a computer, there is no OS to load the bootloader or kernel. Thus, the bootloader's and kernel's code must be extracted from the generated ELF executables and carefully laid out in the image file as if it were memory. There should be no other required resources specified in the ELF executables because there will be no OS to prepare them when the code is loaded (this requirement is reflected in the compiler flags: -nostartfiles, -nostdlib, -fno-builtin).

Because the BIOS will load the bootloader to offset 0 (of segment 0x07c0), the compiler must be told that the starting address is 0 using the -Ttext flag. Similarly, the kernel's starting offset is set to 0x1000. Thus, when the BIOS loads the first sector of the image to offset 0, the bootloader will be ready to run. When the bootloader simply copies the kernel to 0x1000, it too, will be ready to run.

Note:

For specific information on the functionality of createimage, refer to the man page provided in the code template by typing man -M man createimage. You should ignore the " -vm" option for this project.

ReadingTools
  • readelf
  • od
  • diff

Testing

While developing your bootloader, we encourage you to test it using the free bochs emulator (
bochs.sourceforge.net). However, you will be developing a real operating system so it must work on real computers. You must ensure your bootloader works when booted from a USB flash disk on the lab machines. We will provide you with USB disks.

Booting off a USB disk

One the lab machines, the USB flash disk is accessed at /dev/sdf. In general, it may be accessible at /dev/sda, /dev/sdb, /dev/sdc, etc. Make sure you have identified the correct device before writing the image, otherwise, damage may be done to other devices connected to the machine.

When you are ready to test your image:

  1. Mark the image so that the BIOS recognizes it as bootable. Write a signature consisting of two bytes, 0x55 0xaa, to the end of the first sector: address 0x1fe of image.
  2. Copy it to your USB disk using cat image > /dev/sdf, assuming /dev/sdf is the USB disk you intend to boot from
  3. Next, connect the USB disk to the test computer (which may be the same computer, a virtual machine, or emulator), restart, and boot off the USB disk.

Bochs

Bochs can make debugging your bootloader much easier. If Bochs has been configured with the --enable-disasm and --enable-debugger flags during compilation, then when it loads your image, it will enter a debug prompt where you can set breakpoints, step through assembly code, etc. The lab machines have a debugging version of Bochs in /u/318/bin/bochs. The Bochs Windows installer will install both bochs and bochsdbg.

To test image with Bochs:

  1. Setup Bochs
  2. You may need to edit bochsrc, provided with the template code, so that the BIOS is loaded at address=0xf0000 instead of address=0xe0000 if Bochs complains that the BIOS doesn't properly fit in memory.
  3. Run bochs or bochsdbg from the same directory as bochsrc (and image)
  4. Bochs will stop at a prompt. Type c to continue execution. You'll probably want to set a breakpoint at your bootloader using b 0x7c00 before continuing. Take a look at some other debugger commands.

Bochs will create a logfile called bochsout [.txt] which may become quite large (up to 6 GB!). If you are using Lab 010, make sure you delete this file when you're done so that you don't use up all the disk space! . (If the disk fills up, no one will be able to log in to their normal session). At this point, you can still log in using the "Fail Safe" session to delete the file.

ResourcesTools
  • objdump -M i8086,att [-D | -d] filename: disassembles a program, showing symbols and offsets.
  • hexdump: dumps file in hex and other formats
  • od: octal (and other formats) dump of a file

Program Submission

Don't forget to complete the general requirements for each project!

Submit via Blackboard; only one person per group should submit.


Extra Credit

You will get extra credit if your bootloader can load a kernel that is more than 128 sectors in size. This can be done using
BIOS Int 0x13 Function 8 .