引导加载程序在真实硬件上打印垃圾

Bootloader printing garbage on real hardware

我正在尝试编写自己的引导加载程序。虽然它在 QEMU、Bochs 和 VirtualBox 中运行良好,但我似乎无法在我的笔记本电脑上运行它。

在我的笔记本电脑上,引导加载程序的行为与所有模拟器都非常不同,挂在看似随机的地方,拒绝打印,甚至跳过一些 jmp $ 指令。

虽然我在 "real-hardware" 上遇到了很多麻烦,但我认为它们都是一个原因。

以下代码是一个简短的引导加载程序,它应该打印一条 "TEST" 消息 3 次,然后通过跳转到同一位置挂起:

[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

编译并模拟代码:

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

在模拟器上,它打印了 3 次 "TEST" 然后挂起,在我的笔记本电脑上,它打印了 "TEST" 后跟 3 个奇怪的字符:

http://wiki.osdev.org does not work either. For example, none of the code-snippets from http://wiki.osdev.org/Babystep2 中的大多数引导加载程序代码都可以在我的笔记本电脑上运行。

我的代码有什么问题?我该如何解决?


附加信息

如果我删除 2 个不必要的 mov si, msg,"TEST" 消息将打印 两次

笔记本电脑:

汇编写法:

$ 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 

编辑 1

Ross Ridge 在评论中注意到 Ω♣| 是引导加载程序的前 3 个字节。

编辑 2

更新打印函数和字符串:

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 

输出

一个TEST。另外两个丢失了。

编辑 3

根据 Ross Ridge 的建议,已添加 dumpregs 以便更好地调试。 int 0x10 修改任何寄存器。 经过一些测试后,我将 dumpregs 函数移动到 drive_number 赋值周围,并在其后移动了 jmp $ 。该代码应打印 1 行寄存器转储并停止。相反,它继续:

完整代码: https://gist.github.com/anonymous/0ddc146f73ff3a13dd35

编辑 4

当前引导加载程序的反汇编使用:

$ ndisasm -b16 bootload2 -o 0x7c00

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

现在看来 BIOS 可能正在修改 BIOS parameter block,它错误地认为是加载到内存中的引导扇区的一部分。它可能觉得它需要这样做,因为它在引导时提供给 USB 设备的几何形状可能不同于将引导扇区及其假定的 BPB 写入设备时使用的几何形状。由于引导扇区开头存在跳转指令是应用程序测试 BPB 是否存在的方法之一,因此您可以尝试在引导扇区开头插入一些其他指令(但不是 NOP)。例如:

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

请注意,远跳似乎不是 normal indicators of the presence of a BPB 之一。事实上,这是 BPB 不存在的确凿证据,因为 BPB 从偏移量 4 开始,远跳指令的长度为 5 个字节。

如果这不起作用,您可以尝试为 BPB 保留 space,如下所示:

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

您可以使用以下代码转储寄存器以帮助调试问题:

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

它使用直接视频写入来转储所有通用寄存器。它使用 PUSHA/POPA 寄存器顺序:AX CX DX BX SP BP SI DI

如果您将原始代码中的 INT 0x10 指令与调用此函数括起来,您应该得到如下输出:

尤其要确保调试输出的每一行左侧的 8 个数字与右侧的 8 个数字匹配。您正在使用的 BIOS 调用不应更改任何寄存器。

我以前见过类似的症状。场景大概是这样的:

a) 您正在从 USB 闪存启动笔记本电脑

b) BIOS 查看设备的第一个扇区并尝试决定是否应该将其视为软盘或硬盘驱动器。它找不到有效的分区 table,因此决定将其视为软盘。

c) BIOS 尝试 "helpful" 并修补 BPB 中的某些字段。

d) 你没有 BPB,所以你的代码 and/or 数据被破坏,导致奇怪的事情发生(代码中的微小变化导致不同的奇怪事情发生)。

遗憾的是,我自己没有执行此操作的硬件。我不知道为什么 BIOS 假设有一个 BPB,或者它假设存在多个不同 "BPB formats" 中的哪一个,或者它认为它可能正在修补什么或者为什么它认为有必要修补任何东西。我只知道有些 BIOS 会丢弃偏移量 0x1C 到 0x1F 处的字节和偏移量 0x24 处的字节。