I am writing a simple bootloader (and, hopefully soon, an operating system). I have been making progress, but this part is stumping me a bit.
I am writing this operating system and bootloader into a disk image, which consists of:
kernel.bin
My goal is to load kernel.bin into memory and jump to it, and in order to do this I must traverse the FAT tables.
A lot of logic involved in reading the FAT32 partition requires 32-bit values (like many BPB values)
Since I am essentially locked in real mode for now, I'm not sure how to handle these 32-bit numbers when performing calculations like multiplication. I know I can use 32-bit registers using the operand size prefix and/or address size prefix, but I can't really use arithmetic operations on them in a meaningful way.
Example
Here is a section of assembly that stores important values from the BPB:
BPB_info:
; BPB info from the FAT32 boot sector
BPB_NumFATs: db 0 ; Number of FATs (1 byte)
BPB_FATSz32: dd 0 ; Size of each FAT in sectors (4 bytes)
BPB_RootClus: dd 0 ; First cluster of the root directory (4 bytes)
BPB_SecPerClus: db 0 ; Sectors per cluster (1 byte)
BPB_RsvdSecCnt: dw 0 ; Reserved sectors count (2 bytes)
; Calculated values used in the bootloader
FAT_root_dir_start: dq 0 ; Start of the root directory in sectors (calculated)
FAT_lba: dq 0 ; Logical block address of the FAT tables (calculated)
In order to calculate FAT_root_dir_start
, I must use the following formula:
BPB_RsvdSecCnt + ((BPB_RootClus - 2) * BPB_SecPerClus)
But since BPB_RootClus
is a doubleword (32 bits) I'm not sure how to carry out this operation.
Any advice would be appreciated! Am I on the right track? Should I approach this completely differently? Maybe switch to protected mode? I'm not sure.
If you want to stick to a strict x86 instruction set then you are looking at a typical long multiplication problem (Multiplying 64-bit number by a 32-bit number in 8086 asm for an explanation as pointed out by @user207421). In assembly:
mov cx, [BPB_RootClus + 2]
mov ax, [BPB_RootClus]
sub ax, 2 ; cx:ax = BPB_RootClus - 2 = X; cx = Xh, ax=Xl
sbb cx, 0
mov bl, [BPB_SecPerClus] ; bx = y
mov bh, 0
mul bx ; dx:ax = Xl * y
xchg ax, bx ; dx:bx = Xl * y, ax = y
mov bp, dx ; bp:bx = Xl * y
mul cx ; dx:ax = Xh * y
add ax, bp
; adc dx, 0 - that'd be a carry-over but we'll assume that mutiplication result can fit into 32 bit
; now X * y is in ax:bx pair, add BPB_RsvdSecCnt
add bx, [BPB_RsvdSecCnt]
adc ax, 0
However if you are OK with using 80386 32-bit instructions (using OPERAND size override, not an address size override though) then I don't really see any problem with simply using 32 bit arithmetics:
bits 16
cpu 386
...
mov eax, [BPB_RootClus]
sub eax, 2
movzx edx, byte [BPB_SecPerClus]
mul edx
movzx edx, word [BPB_RsvdSecCnt]
add eax, edx
That ought to compile and work just fine.