Operating System Development from Scratch - Making our Bootloader bootable - Part 4

Hello everybody, it's me, Lovelace! In this post, we will improve our bootloader to fix some errors and make it bootable on an actual machine!

What we will do is basically set up segment registers and change the origin of our bootloader. When the BIOS loads our bootloader, we don't know what the segment registers are, because of this, having a bootloader as the one we have right now, does not guarantee it will successfully boot on most systems, for example, if the BIOS sets our data segment to 0x7c0 and our program's origin is 0x7c00, then the equation would be ds * 16 + 0x7c00, so if ds is 0x7c0 we'd end up with 0x7c00 + 0x7c00 which does not point to our message variable.

Because of these types of scenarios, we should initialise the data segment and all the other segment registers ourselves, let's do it now.

First, let's change our program's origin to 0, to do so, just change the value of the ORG instruction:

ORG 0

What we will need to do now, is to go to our start label and at the beginning of it, add the followings instructions:

cli ; Clear interrupts

sti ; Enable interrupts

As the comments explain, the cli instruction clears/disables all the interrupts and sti enables them all again. The reason we disable our interrupts is that we will change some segment registers and we don't want any interrupt to happen as the system would panic or there might be unexpected responses as some segments wouldn't be set correctly. The following piece of code is in between the cli and the sti instruction:

mov ax, 0x7c0
mov ds, ax
mov es, ax

We are setting the value of ax to 0x7c0 and then we are changing the ds and es segment values to the ax. We cannot just move 0x7c0 to either the data or extra segment, that's why we first move it to ax first, that's how the processor works.

As we have already set up the data segment, the extra segment and our origin is zero. When we reference our message label, the processor will assume that we are loaded into address 0x00 into RAM, so its offset will be pretty low, it will be where in our binary file the message label is stored. Let's assume that this offset is 14 bytes so, when we call lodsb what will happen is, it will use our data segment and the si register and as we already know we have changed our data segment to 0x7c0, it will multiply that value by 16 and the result would be 0x7c00 and then it will add the offset of our message to that address 0x7c00 + 14 = 0x7c0e which would be correct. That's why we need to change these data segments, because if the BIOS set them for us it could mean that our origin is set wrong for our program and then it won't link up correctly. We set those registers by ourselves so we are in control of their value, where they are loaded instead of hoping for the BIOS of setting them correctly for us. The entire new piece of code looks like this:

_start:
    cli
    mov ax, 0x7c0
    mov ds, ax
    mov es, ax
    sti

The next segment we want to initialise is the stack segment, we will set this up differently as we know it grows downwards. So, what we can do is to set the stack pointer equal to 0x7c00 and it will start growing down. Therefore, we will do:

mov ax, 0x00
mov ss, ax
mov sp, 0x7c00

What we did, is to set up the stack segment to 0 and the stack pointer to 0x7c00.

The last thing we have to do is to add:

jmp 0x7c0:start

After the BITS 16 instruction so our code segment also becomes 0x7c0.

That should be it, if you assemble your bootloader again and run it with qemu it should work as it did before, but now, we are in control of the situation, we don't rely on the BIOS to set everything up for us anymore.

Check this entry's change here