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
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:
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
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:
BITS 16 instruction so our code segment also becomes
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