Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bootloader printing garbage on real hardware [duplicate]

I am trying to write my own bootloader. While it works fine in QEMU, Bochs and VirtualBox, I cannot seem to make it work on my laptop.

On my laptop, the bootloader behaves very differently to all emulators, hanging seemingly random places, refusing to print, even skipping some jmp $ instructions.

While I have a lot of trouble with "real-hardware", I figure there is one cause to them all.

The following code is a short bootloader that should print a "TEST" message 3 times, then hang by jumping to the same location:

[BITS 16]                                                                          
[ORG 0x7C00]                                                                                                    
    jmp 0x0000:start_16  ; In case bootloader is at 0x07C0:0x0000                                                             
start_16:                                                                          
    xor ax, ax                                                                 
    mov ds, ax                                                                 
    mov es, ax                                                                 
    cli                             ; Disable interrupts                       
    mov ss, ax                                                                 
    mov sp, 0x7C00                                                             
    sti                             ; Enable interrupts                        
    cld                             ; Clear Direction Flag                     
    ; Store the drive number                                                   
    mov [drive_number], dl                                                     
    ; Print message(s)                                                         
    mov si, msg                                                                
    call print_string                                                          
    mov si, msg                                                                
    call print_string                                                          
    mov si, msg                                                                
    call print_string                                                          

    jmp $   ; HALT                                                                                   

; print_string                                                                     
;       si      = string                                                           
print_string:                                                                      
    pusha                                                                      
    mov ah, 0x0E                                                               
.repeat:                                                                           
    lodsb                                                                      
    cmp al, 0x00                                                               
    je .done                                                                   
    int 0x10                                                                   
    jmp short .repeat                                                          
.done:                                                                             
    popa                                                                       
    ret                                                                        

; Variables                                                                        
drive_number db 0x00                                                               
msg db 'TEST', 0x0D, 0x0A, 0x00                                                    
times 510-($-$$) db 0x00                                                           
db 0x55                                                                            
db 0xAA

Compile and emulate the code with:

$ nasm -f bin bootloader.asm
$ qemu-system-x86_64 bootloader

On emulators, it prints "TEST" three times and hangs, On my laptop, it prints "TEST" followed by 3 weird characters:

Bootloader output on my laptop.

Most bootloader code from http://wiki.osdev.org does not work either. For example, none of the code-snippets from http://wiki.osdev.org/Babystep2 work on my laptop.

What is wrong with my code? How can I fix it?


Additional information

If I remove the 2 unnecessary mov si, msg, the "TEST" message is printed twice.

Laptop:

  • Asus Vivobook S200,
  • CPU: Intel i3-3217U
  • BIOS: American Megatrends, Version 210.
  • The computer works just fine with any other bootloader such as Grub.

Assembly and writing:

$ nasm -f bin bootloader.asm
$ qemu-system-x86_64 bootloader # TEST 1 
$ sudo dd if=/dev/zero of=/dev/sdd bs=1M count=1 # clean the USB 
$ sudo dd if=bootloader of=/dev/sdd conv=fsync # write to USB 
$ qemu-system-x86_64 /dev/sdd # TEST 2 

Edit 1

Ross Ridge noticed in the comments that Ω♣| are the first 3 bytes of the bootloader.

Edit 2

Updated print function and string:

print_string:                                                                      
    pusha                                                                          
.repeat:                                                                        
    mov ah, 0x0E                                                                
    xor bx, bx                                                                  
    cld                             ; Clear Direction Flag                      
    lodsb                                                                       
    cmp al, 0x00                                                                
    je .done                                                                    
    int 0x10                                                                    
    jmp short .repeat                                                           
.done:                                                                          
    popa                                                                        
    ret 

msg db 'TEST', 0x00 

Output

A single TEST. Additional two are missing.

Edit 3

As suggested by Ross Ridge, dumpregs have been added for better debugging. The int 0x10 does not modify any registers. After some testing, I have moved the dumpregs function around drive_number assignment and a jmp $ just after. The code should print 1 line of register dump and halt. Instead, it continues: dumpregs around drive_number assignment

The full code: https://gist.github.com/anonymous/0ddc146f73ff3a13dd35

Edit 4

Disassembly of the current bootloader using:

$ ndisasm -b16 bootload2 -o 0x7c00

https://gist.github.com/anonymous/c9384fbec25513e3b815

like image 466
Janman Avatar asked Jan 23 '16 17:01

Janman


1 Answers

It's now looking like the BIOS maybe modifying the BIOS parameter block that it incorrectly assumes is a part of the bootsector loaded into memory. It might feel it needs to because the geometry it gives to the USB device when booting it might differ from the geometry used when the bootsector and its presumed BPB was written to the device. Since the presence of a jump instruction at the start of a bootsector is one of the ways applications test for the existence of a BPB, you can try inserting some other instruction (but not a NOP) at the start of your bootsector. For example:

[BITS 16]                                                                          
[ORG 0x7C00]
    xor ax,ax                                                                                                
    jmp 0x0000:start_16  ; In case bootloader is at 0x07C0:0x0000                                                             
start_16:                       

Note that a far jump doesn't appear to be one of the normal indicators of the presence of a BPB. In fact it's pretty conclusive evidence that a BPB isn't present, as the BPB starts at offset 4 and a far jump instruction is 5 bytes long.

If that doesn't work you can try reserving space for a BPB, something like this:

[BITS 16]                                                                          
[ORG 0x7C00]
    jmp start
    nop
    resb 8 + 25
start:                                                                                          
    jmp 0x0000:start_16  ; In case bootloader is at 0x07C0:0x0000                                                             
start_16:                       

Here's some code you can use to dump registers to help debug the problem:

dumpregs:
    push    es
    pusha
    push    0xb800
    pop es  
    mov di, [vidmem_ptr]
    mov bp, sp
    mov cx, 8
dump_loop:
    dec bp
    dec bp
    mov ax, [bp + 16]
    call    printhex2
    inc di
    inc di
    loop    dump_loop
    mov [vidmem_ptr], di
    popa
    pop es
    ret

printhex2:
    push    ax
    mov al, ah
    call    convhex1
    pop ax
convhex1:
    aam 16
    ; DB    0D4h, 16
    xchg    al, ah
    call    convhex
    mov al, ah
convhex:
    cmp al, 10
    jb  lessthan_10
    add al, 'A' - '0' - 10
lessthan_10:
    add al, '0'
    stosb
    mov al, 7
    stosb
    ret

vidmem_ptr dw 5 * 80 * 2 ; start at row 5

It uses direct video writes to dump all the general purpose registers. It uses the PUSHA/POPA register order: AX CX DX BX SP BP SI DI

If you bracket the INT 0x10 instruction in your original code with calls to this function, you should get output like this:

Screenshot of boot with debug output

In particular you want to make sure the 8 numbers of the left hand side match the 8 numbers on the right hand side for each of lines of debug output. The BIOS call you're using shouldn't be changing any registers.

like image 98
Ross Ridge Avatar answered Sep 28 '22 14:09

Ross Ridge