DragonFly x86 booting ===================== by Jeroen Ruigrok van der Werven ----------------------------------------------------- This incorporates information from the GAS Manual and the BIOS Boot Specification 1.01 (BBS). mbr.s: The mbr is created with: (g)as -o mbr.o mbr.s ld -N -e start -Ttext 0x600 -o mbr.out mbr.o objcopy -S -O binary mbr.out mbr Basically this means that the entry symbol is set to 'start' instead of the default entry point. Also the global symbol and entry point 'start' is set to start at 0x0600, so all symbols are offset from that address. The comment below basically says that the Master Boot Record (MBR) has to be 512 bytes in size, maximum. In fact, only 510 are really available for the code plus data, since the MBR *must* end with the signature 'AA55' to be recognised as a valid boot sector. # A 512 byte MBR boot manager that simply boots the active partition. We start by defining some symbols. GNU as uses .set (.set symbol, expression) for this. The load address is an address as mandated by the BBS. The BIOS attempts to boot from a BAID (BIOS Aware IPL (Initial Program Load) Device) by calling a boot handler address that is located within the BIOS (often INT 19h (Bootstrap loader)). If the device fails to boot it should invoke INT 18h (Diskless boot hook). When the boot handler is called, the BIOS passes a pointer to the PnP Installation Check Structure in ES:DI. This is so that once the boot handler has successfully loaded the device's boot sector into memory at address 0000:7C00h, execution control can be transferred with the following register contents: ES:DI = Pointer to PnP Installation Check Structure DL = Drive number used for the INT 13h (00h, 80h, etc.) .set LOAD,0x7c00 # Load address .set EXEC,0x600 # Execution address A partition table is located at offset 0x01be. Partition 1 - 0x01be Partition 2 - 0x01ce Partition 3 - 0x01de Partition 4 - 0x01ee Each entry is thus 16 (10h) bytes in size and follows the following structure: typedef struct partition { :1 boot_indicator :1 start_head :1 start_sector_and_high_cylinder :1 start_low_cylinder :1 system_type :1 end_head :1 end_sector_and_high_cylinder :1 end_low_cylinder :4 lba_address :4 lba_length } .set PT_OFF,0x1be # Partition table .set MAGIC,0xaa55 # Magic: bootable .set NHRDRV,0x475 # Number of hard drives Set a global symbol, which makes it visible to the linker. This starts the code at 0x0600 .globl start # Entry point Specify we want to work in real mode/16 bit protected mode by using .code16. .code16 # # Setup the segment registers for flat addressing and setup the stack. # cld is the Clear Direction Flag instruction. When set to 0 string operations increment the index registers ((E)SI and/or (E)DI). start: cld # String ops inc xorw %ax,%ax compares AX with AX (source, destination) and all bits that are the same are set to 0 in the destination register, effectively setting AX to zero. xorw %ax,%ax # Zero movw %ax,%es copies 16 bits of AX to ES, basically initialising the ES segment register. The same applies for DS, the data segment register, and SS, the stack segment register. movw %ax,%es # Address movw %ax,%ds # data movw %ax,%ss # Set up Here we copy the contents of symbol 'LOAD' into the stack pointer register, which gets set to 0x7c00, which you might remember is the address for the start of the boot sector. movw $LOAD,%sp # stack # # Relocate ourself to a lower address so that we are out of the way when # we load in the bootstrap from the partition to boot. # Copy ($main - 0x0600 + 0x7c00) to the source index register SI. $main is an immediate memory address offset within the same section. (g)as -as on the mbr.s gives me: mbr.s:59 .text:0000001a main So it seems that the 'main' label offset is 1a. Remember that the base addess is 0x0600 and thus 'main' would be at 0x061a. This would mean that the source index register SI gets the address 0x061a - 0x0600 + 0x7c00 (0x7c1a) copied into it. The destination index register DI gets 0x061a copied into it. movw $main-EXEC+LOAD,%si # Source movw $main,%di # Destination Next we copy 0x0200 - (0x061a - 0x0600) = 0x01e6 to count register cx. Then we call rep, which is the repeat string operation, followed by movsb, which moves the byte at address DS:SI to address ES:DI as often as the value in CX, in this case 0x01e6 (486) times. So in effect we copy 486 bytes from 0x7c1a to 0x061a an onwards (ending at 0x7e00 and 0x800 respectively). Every iteration will decrease the value in CX. movw $0x200-(main-start),%cx # Byte count rep # Relocate movsb # code For now the below code confuses me, we would be jumping to 0x061a - 0x7c00 + 0x0600 = -6FE6, so which address are we really jumping to? Disassembly with ndisasm shows we're doing a jmp 0x8a8e, this confuses me more. It is a near jump (encoded as E9008A: E9 cw JMP rel16, segment in CS) which starts with 0x8a followed by # # Jump to the relocated code. # jmp main-LOAD+EXEC # To relocated code # # Scan the partition table looking for an active entry. Note that %ch is # zero from the repeated string instruction above. We save the offset of # the active partition in %si and scan the entire table to ensure that only # one partition is marked active. # Reset SI to 0 (remember the xor trick in the beginning). It had Copy the address of partbl into BX (0x07be). Copy 0x4 into CL. main: xorw %si,%si # No active partition movw $partbl,%bx # Partition table movb $0x4,%cl # Number of entries main.1: cmpb %ch,(%bx) # Null entry? je main.2 # Yes jg err_pt # If 0x1..0x7f testw %si,%si # Active already found? jnz err_pt # Yes movw %bx,%si # Point to active main.2: addb $0x10,%bl # Till loop main.1 # done testw %si,%si # Active found? jnz main.3 # Yes int $0x18 # BIOS: Diskless boot # # Ok, we've found a possible active partition. Check to see that the drive # is a valid hard drive number. # main.3: cmpb $0x80,%dl # Drive valid? jb main.4 # No movb NHRDRV,%dh # Calculate the highest addb $0x80,%dh # drive number available cmpb %dh,%dl # Within range? jb main.5 # Yes main.4: movb (%si),%dl # Load drive # # Ok, now that we have a valid drive and partition entry, load the CHS from # the partition entry and read the sector from the disk. # main.5: movw %sp,%di # Save stack pointer movb 0x1(%si),%dh # Load head movw 0x2(%si),%cx # Load cylinder:sector movw $LOAD,%bx # Transfer buffer cmpb $0xff,%dh # Might we need to use LBA? jnz main.7 # No. cmpw $0xffff,%cx # Do we need to use LBA? jnz main.7 # No. pushw %cx # Save %cx pushw %bx # Save %bx movw $0x55aa,%bx # Magic movb $0x41,%ah # BIOS: EDD extensions int $0x13 # present? jc main.6 # No. cmpw $0xaa55,%bx # Magic ok? jne main.6 # No. testb $0x1,%cl # Packet mode present? jz main.6 # No. popw %bx # Restore %bx pushl $0x0 # Set the LBA pushl 0x8(%si) # address pushw %es # Set the address of pushw %bx # the transfer buffer pushw $0x1 # Read 1 sector pushw $0x10 # Packet length movw %sp,%si # Packer pointer movw $0x4200,%ax # BIOS: LBA Read from disk jmp main.8 # Skip the CHS setup main.6: popw %bx # Restore %bx popw %cx # Restore %cx main.7: movw $0x201,%ax # BIOS: Read from disk main.8: int $0x13 # Call the BIOS movw %di,%sp # Restore stack jc err_rd # If error # # Now that we've loaded the bootstrap, check for the 0xaa55 signature. If it # is present, execute the bootstrap we just loaded. # cmpw $MAGIC,0x1fe(%bx) # Bootable? jne err_os # No jmp *%bx # Invoke bootstrap # # Various error message entry points. # err_pt: movw $msg_pt,%si # "Invalid partition jmp putstr # table" err_rd: movw $msg_rd,%si # "Error loading jmp putstr # operating system" err_os: movw $msg_os,%si # "Missing operating jmp putstr # system" # # Output an ASCIZ string to the console via the BIOS. # putstr.0: movw $0x7,%bx # Page:attribute movb $0xe,%ah # BIOS: Display int $0x10 # character putstr: lodsb # Get character testb %al,%al # End of string? jnz putstr.0 # No putstr.1: jmp putstr.1 # Await reset msg_pt: .asciz "Invalid partition table" msg_rd: .asciz "Error loading operating system" msg_os: .asciz "Missing operating system" Advance the location counter to 0x1be and fill in the intermediate space with zeroes (since the fill has been omitted). .org PT_OFF Fill 0x10 * 0x4 (16 * 4 = 64) bytes with 0x0. End with the value '0xaa55'. partbl: .fill 0x10,0x4,0x0 # Partition table .word MAGIC # Magic number