assemblyx86-16bootloaderreal-modefat32

Performing multiplication of 32-bit numbers in 16-bit real mode in order to traverse FAT table


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:

  1. An MBR boot sector and a first stage bootloader
  2. A 32kb reserved area for the second stage bootloader
  3. A FAT32 partition that holds the operating system, and 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.


Solution

  • 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.