x86usbapache-phoenixbiosmbr

Phoenix BIOS skips MBR code, what is the ACTUAL BIOS standard?


I want to learn assembler to use the ultra-minimum bare code to get any x86 compatible machine running to display a hardcoded text on the screen on power on. I care about full compatibility with x86/IBM machines.

I followed tutorials on simple bootcodes and failed miserably to run them physically from my USB on my notebook. It was like my notebook wasn't even touching the MBR code. I spent two weeks reading tons of articles which led me nowhere. I learnt many things about BIOS Parameter Blocks, Partition entries, this old Phoenix BIOS specification, and UEFI.

My notebook boots correctly with my bootable FreeDOS USB. Here is the first sector of my FreeDOS USB with the MBR: Nice and full of real useful codey-code

I tried to boot it from a desktop computer with UEFI and it worked. I tried to boot it from a notebook with Phoenix BIOS and it worked.

Then I wiped ALL the boot instructions: Nice and full of NOTHING but one partition entry

I tried to boot it from the same desktop computer with UEFI and it failed as expected. I tried to boot it from a notebook with Phoenix BIOS and it worked. I swear.

I wanna know why is there a BIOS which takes matters on its own hands and skips the MBR instructions, if there are more like it and where can I find documentation on them so I can make my boot codes fully compatible with all BIOS.

The version of my notebook's BIOS is 1.08 and its EC version is 1.07. I don't want to update it. If FreeDOS boots fine with it, then I want my bootcode to boot fine with it too.


Solution

  • This answer is based on experience and an educated guess. It is rather difficult to test without the actual hardware. I'm assuming that the OP is writing his boot sector to the MBR and not a VBR.


    BIOSes have been created over the years that have been dumb and smart (some too smart IMHO). Some try to distinguish between media that might be a floppy disk drive or a hard drive based on the presence of a BIOS Parameter Block and/or a partition table with a bootable partition. Unfortunately this isn't standardized and many BIOS manufacturers only test their code against disks partitioned by Windows.

    Some BIOSes that believe they see a valid partition table will assume HDD emulation and attempt to load the Volume Boot Record (VBR) from the active partition rather than execute the code on the MBR. I have a suspicion this is the case on the machine that seemed to still boot despite zeroing out the code and keeping a partition table. The code executed probably came directly from the VBR.

    If using devices that are acting as hard drives (and not floppies) you can create an MBR with a partition table where the only active partition starts from the beginning of the drive (CHS=0,0,1 or LBA=0); is marked as bootable; and has a non-zero partition type. If you encounter a machine intent on loading a VBR directly then this method would trick it into loading the MBR as a VBR.

    Example code that should boot on your USB as HDD media on the machines you seem to be testing could look like:

    bits 16
    org 0x7c00
    
    boot_start:
        xor ax, ax                  ; DS=0 since we use ORG 0x7c00. 0x0000<<4+0x7c00=0x7c00
        mov ds, ax
        mov es, ax
    
        ; If you will be reading data into memory outside of 0x7c00 to 0x7dff
        ; then you want to set the stack SS:SP - uncomment these lines
        ; mov ss, ax                ; Stack at 0x0000:0x7c00
        ; mov sp, 0x7c00            ;     Just below bootloader
    
        cld                         ; Forward movement of string instructions
                                    ;     (MOVSB, SCASB, etc)
    
        mov si, HelloWorldMsg       ; Print hello world
        call print_string
    
    end_loop:                       ; Loop forever to terminate
        hlt
        jmp end_loop
    
    ; Function: print_string
    ;           Display a string to the console on display page 0
    ;
    ; Inputs:   SI = Offset of address to print
    ; Clobbers: AX, BX, SI
    
    print_string:
        mov ah, 0x0e                ; BIOS tty Print
        xor bx, bx                  ; Set display page to 0 (BL)
        jmp .getch
    .repeat:
        int 0x10                    ; print character
    .getch:
        lodsb                       ; Get character from string
        test al,al                  ; Have we reached end of string?
        jnz .repeat                 ;     if not process next character
    .end:
        ret
    
    HelloWorldMsg:   db "Hello, world!", 0x0d, 0x0a, 0
    
    times 446-($-$$) db 0   ; Pad with 0s up until first partition entry
    part1_entry:
    db 0x80                 ; 0x80 = Active boot partition, 0x00=inactive
    db 0x00, 0x01, 0x00     ; CHS of first absolute sector (MBR) of hard drive
                            ;     Head=0, Sector=1, Cylinder=0
    db 0x0c                 ; Partition type (has to be non-zero)
                            ;     0x0c = Win 95 FAT32 (LBA)
    db 0x00, 0x01, 0x00     ; CHS of last absolute sector (MBR) of hard drive
                            ;     Head=0, Sector=1, Cylinder=0
                            ;     We are effectively saying Size of partition is 1 sector
    dd 0x0                  ; LBA of first absolute sector (0=MBR)
    dd 0x1                  ; Number of sectors in partition. We set it to 1 but if you
                            ;     wish you could set it to the number of sectors on the disk
    
    times 510-($-$$) db 0   ; Pad remainder of boot sector up to boot signature. This zeroes
                            ;     partition entries 2,3,4 effectively making them inactive
    
    dw 0xAA55               ; The standard PC boot signature after partition table
    

    You can build the MBR with:

    nasm -f bin boot.asm -o boot.bin
    

    If successfully placed on the media this code should print:

    Hello, world!