assemblyx86nasmbootloaderusb-drive

Assembly boot sector working on VirtualBox but not on real hardware


I am trying to write a simple bootloader in assembly. What I am trying to do is print a few strings, then wait for a keypress. This is my code:

[org 0x7c00]
mov al, 0
mov ah, 0
mov ax, 0
mov bx, 0
mov bp , 0x8000 ; Set the base of the stack a little above where BIOS
mov sp , bp ; loads our boot sector - so it won ’t overwrite us.
jmp main
message:
    db 0x0a,0x0d,'Enderbyte Programs',0x0a,0x0d,0
bmessage:
    db 0x0a,0x0d,'Booting...',0x0a,0x0d,0
cmessage:
    db 0x0a,0x0d,'Just kidding this does nothing ',0
kmessage:
    db 'Or does it?',0
print_string:
    mov al, [bx]
    inc bx
    cmp al, 0 
    jne print_char
    mov bx, 0
    ret
print_char: 
    int 0x10; Print AL register
    jmp print_string
main:
    mov ah, 0x0e ; int 10/ ah = 0 eh -> scrolling teletype BIOS routine
    mov bx, message
    call print_string
    mov bx, bmessage
    call print_string
    mov bx, cmessage,
    call print_string
    mov ah, 00h
    int 16h; Wait for keypress?
    mov ah, 0x0e
    mov bx, kmessage
    call print_string
    ;jmp $

This code works perfectly on VirtualBox, but when I try to run it on my PC (by copying it into a flash drive and booting from it), something very strange happens. The cursor is moved to the bottom-right area of the screen and prints off a strange character (An O but with an arrow coming off at the NE corner). Pressing a key causes it to clear the screen, the cursor jumps around before printing off another one of these characters. What is going on?

I have tried to follow the advice in Assembly boot loader working on virtual PC, not on real PC (setting all registers to zero, validating the stack) but it still did not work on the real hardware. (Still works fine on Virtualbox).

B0 00 B4 00 B8 00 00 BB 00 00 BD 00 80 89 EC EB
63 0A 0D 45 6E 64 65 72 62 79 74 65 20 50 72 6F
67 72 61 6D 73 0A 0D 00 0A 0D 42 6F 6F 74 69 6E
67 2E 2E 2E 0A 0D 00 0A 0D 4A 75 73 74 20 6B 69
64 64 69 6E 67 20 74 68 69 73 20 64 6F 65 73 20
6E 6F 74 68 69 6E 67 20 00 4F 72 20 64 6F 65 73
20 69 74 3F 00 8A 07 43 3C 00 75 04 BB 00 00 C3
CD 10 EB F1 B4 0E BB 11 7C E8 E9 FF BB 28 7C E8
E3 FF BB 37 7C E8 DD FF B4 00 CD 16 B4 0E BB 59
7C E8 D1 FF 25 00 00 00 00 00 00 00 00 00 00 00
*
00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 AA

Solution

  • You should do a better setup and probably a somehow valid MBR. You cannot assume that BIOS set up the segments and registers for you. You have to initialize the most important things from the beginning. Maybe try something like:

    This code assumes that your print_string is properly functional.

    [ORG 0x7c00]
    [BITS 16]
    
    global boot
    
    boot:
        jmp _start
        nop
    
        ; Here is a example BPB
        OEMname:           db    "mkfs.fat"  
        bytesPerSector:    dw    512
        sectPerCluster:    db    0
        reservedSectors:   dw    0
        numFAT:            db    0
        numRootDirEntries: dw    0
        numSectors:        dw    0
        mediaType:         db    0
        numFATsectors:     dw    0
        sectorsPerTrack:   dw    0
        numHeads:          dw    0
        numHiddenSectors:  dd    0
        numSectorsHuge:    dd    0
        driveNum:          db    0
        reserved:          db    0
        signature:         db    0
        volumeID:          dd    0
        volumeLabel:       db    "NO NAME    "
        fileSysType:       db    "FAT12   "
    
    _start:
        cli
        xor ax, ax ; clear your segments
        mov es, ax
        mov ds, ax
    
        jmp 0x00:main ; clear code segment
    
    main:
        sti
        mov bp , 0x8000 ; Set the base of the stack a little above where BIOS
        mov ss,  ax ; Setup Stack SS (AX=0) followed immediately by SP
        mov sp , bp 
    
        mov ah, 0x0e ; int 10/ ah = 0 eh -> scrolling teletype BIOS routine
        mov bx, message
        call print_string
        mov bx, bmessage
        call print_string
        mov bx, cmessage,
        call print_string
        mov ah, 00h
        int 16h; Wait for keypress?
        mov ah, 0x0e
    
        jmp $
    
    print_string:
        mov al, [bx]
        inc bx
        cmp al, 0 
        jne print_char
        mov bx, 0
        ret
    print_char: 
        int 0x10; Print AL register
        jmp print_string
    
    message:
        db 0x0a,0x0d,"Enderbyte Programs",0x0a,0x0d,0
    bmessage:
        db 0x0a,0x0d,"Booting...",0x0a,0x0d,0
    cmessage:
        db 0x0a,0x0d,"Just kidding this does nothing ",0
    kmessage:
        db "Or does it?",0
    
    times 446 - ($ - $$) db 0x00 ; Padding to 512 bytes
    
    ; Partitions table for a MBR
    
    partition_table_1:
        db 0x80 ; Status, 0x80 means active
        db 0x00 ; First Absolute Sector CHS
        db 0x00 ; 
        db 0x00 ;  
        db 0x00 ; Partition Type
        db 0x00 ; Last Absolute Sector CHS
        db 0x00 ; 
        db 0x00 ; 
        dd 0x00000001 ; First Absolute Sector LBA
        dd 0x00000200 ; Number of Sectors
        
    partition_table_2:
        db 0x00 ; Status, 0x80 means active
        db 0x00 ; First Absolute Sector CHS
        db 0x00 ; 
        db 0x00 ;  
        db 0x00 ; Partition Type
        db 0x00 ; Last Absolute Sector CHS
        db 0x00 ; 
        db 0x00 ; 
        dd 0x00000000 ; First Absolute Sector LBA
        dd 0x00000000 ; Number of Sectors
        
    partition_table_3:
        db 0x00 ; Status, 0x80 means active
        db 0x00 ; First Absolute Sector CHS
        db 0x00 ; 
        db 0x00 ;  
        db 0x00 ; Partition Type
        db 0x00 ; Last Absolute Sector CHS
        db 0x00 ; 
        db 0x00 ; 
        dd 0x00000000 ; First Absolute Sector LBA
        dd 0x00000000 ; Number of Sectors
        
    partition_table_4:
        db 0x00 ; Status, 0x80 means active
        db 0x00 ; First Absolute Sector CHS
        db 0x00 ; 
        db 0x00 ;  
        db 0x00 ; Partition Type
        db 0x00 ; Last Absolute Sector CHS
        db 0x00 ; 
        db 0x00 ; 
        dd 0x00000000 ; First Absolute Sector LBA
        dd 0x00000000 ; Number of Sectors
    
    dw 0xAA55 ; Boot signature required to boot
    
    

    First, we cleared the segments to ensure that BIOS didn't give us junk values that will cause trouble later. We also cleared the code segments to make sure that we are in address 0x0000:0x7c00 instead of its equivalents such as 0x07c0:0x0000. I formatted the code, and move the data down so you can manipulate them easier. Your code also lacked padding and signature which were added. I also added some MBR partition tables in case you are booting from harddrive or equivalent system. You could also add a BPB, you can find the structure to that here:

    https://wiki.osdev.org/FAT#BPB_.28BIOS_Parameter_Block.29

    The above code can be compiled with

    nasm -fbin bootloader.asm -o bootloader.bin
    

    Write this to the first sector of a harddrive/USB/CD or equivalent.

    Also check these out:

    https://wiki.osdev.org/Bootloader

    https://wiki.osdev.org/Rolling_Your_Own_Bootloader

    https://wiki.osdev.org/MBR_(x86)

    Edit:

    The above code is compiled without errors and runs on QEMU.

    Edit:

    Also added a BPB