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