I'm working with a friend of mine on a simple bootloader, with no pretense of it becoming anything usable. After writing the on-screen input and output functions we moved on to writing the functions to read a sector from disk, and this is where the first problems arose. I state that both on qemu and bochs everything works fine. The same thing I can not say for the physical hardware, where I encounter the error 0x0001, which means Invalid Command.
However, I didn't find much information about this error. It seems to me that it could mean that I got some argument wrong, but I printed out all the logs on screen and didn't find any strange values that would justify this behavior.
I am booting from a flash drive. I thought that might be a problem, too (since it's not an actual floppy), but if the BIOS can load the bootsector it should have no problem loading the next sector as well.
However, here is the code for the read_sector function:
read_sector:
start_f
pusha
mov (drive_number), %dl # drive number is stored from the main function into a global variable
mov $0x03, %si # try three times
1:
mov $0x0201, %ax
int $disk_int
jnc end
dec %si
jz 2f
xor %ah, %ah
int $disk_int
jmp 1b
2:
movzx %ah, %dx
call printh # print error code
end:
popa
end_f
And here is the caller function (dl = 0):
# ...
mov $0x0002, %cx
xor %dh, %dh
mov $0x7e00, %bx
call read_sector
# ...
What could we be doing wrong?
The problem here is in the file init.s
. I put a value into drive_number
before initializing the code segment.
real mode memory segmentation.
When I translate this listing into machine language, the linker computes the address of drive_number
by reference to the beginning of the section where it is located (in this case the .text
section, which starts at address 7c00
, as specified in the linker script):
.text 0x7c00 :
{
*(.text);
}
This means that the instruction mov $0, (drive_number)
gets translated into mov $0, 7c2e
. However this is not an actual physical address, but only an offset.
In real mode a phisical address is computed by adding the value in a specific segment register shifted by 4 bits (which is the same as multiplying by 16) to an offset (as in this case 7c2e
). Often we see the notation AAAA:BBBB
to indicate the address AAAA * 16 + BBBB
. To determine the physical address of a place in memory used for reading or writing some sort of data, the CPU by default takes advantage of the value stored in the data segment register %ds
. This means that the actual address where we're storing our data is indeed ds * 16 + drive_number
.
When the BIOS jumps to the code written in the bootsector it does not assure us that the values in the segment registers are the ones we want. Therefore, at the beginning of each program we must initialize these registers to contain the values we need. If %ds
isn't zero, the physical address corresponding to drive_number
won't be the same after the initialization of %ds
to zero, which means that this label points to a different location in memory depending on the value contained in %ds
.
The BIOS interrupt 13, 2
requires dl to contain code indicating which drive we should read from. However, there is no need to consult manual after manual to read text from the drive we are booting from: in fact the BIOS places in %dl
the value corresponding to the drive we are booting from before jumping to 07c0:0000
and starting to execute the code in the bootsector.
Using the code passed by the BIOS in %dl
makes the code more reliable and robust.