首页 > 解决方案 > 引导加载程序在 qemu 中工作,但在 virtualbox 和硬件中失败

问题描述

我的引导加载程序由两个 512 字节的阶段组成。阶段 1 由 bios 加载到 MBR 区域。然后 stage1 从驱动器加载 stage2 并跳转到它。

我用十六进制编辑器确认最终二进制“program.bin”的大小正好是 1024 字节长,并且包含两个“签名”(每个阶段的最后两个字节,0xAA55 用于 stage1(MBR 签名)和 0xCC77 用于 stage2)。

预期产出是:

1 // stage1 started
0000 or 0080 // drive# in hex
CC77 // stage2 "signature" in hex
2 // stage2 started

这在 QEMU 中运行良好,但在 virtualbox 和硬件中失败。在我看来,stage2 加载无声无息地失败(错误分支没有被调用),我现在希望解决这个问题两周但没有成功。

QEMU 输出
QEMU 输出

硬件和虚拟盒输出
VBox 输出

阶段1.asm:

global _start
extern _stage2
extern _stage2data

BITS 16

_start:
; init registers
    xor ax, ax
    mov es, ax
    mov gs, ax
    mov ss, ax
    mov sp, 0x7C00      ; right before MBR, counting upwards

    mov ax, 0x7C0       ; set DS to 0x7c0 so pointing at 0x0 resolves to 0x7C0:x0000 = 0x7C00
    mov ds, ax

    cld                 ; set direction flag to make string operations count forward

 ; mark start of stage 1 by printing "1"
    mov al, '1'
    call real_mode_print_char
    call real_mode_new_line

print_drive_number:
; drive# is put into DL by BIOS
    mov dh, 0x0
    mov bx, dx
    call real_mode_print_hex

load_sector2:
    mov  al, 0x01           ; load 1 sector
    mov  bx, 0x7E00         ; destination, right after your bootloader
    mov  cx, 0x0002         ; cylinder 0, sector 2
    ; mov  dl, [BootDrv]      ; boot drive
    xor  dh, dh             ; head 0
    call read_sectors_16
    jnc execute_stage2           ; if carry flag is set, disk read failed
    jmp error

execute_stage2:
    mov bx, [_stage2data]       ; print data at _stage2data to confirm stage 2 was loaded
    call real_mode_print_hex

    jmp _stage2                 ; start execude instructions of _stage2

error:
; print "E" if an error occurs
    mov al, 'E'
    call real_mode_print_char

; infinite loop
loop:
    jmp loop

; read_sectors_16
;
; Reads sectors from disk into memory using BIOS services
;
; input:    dl      = drive
;           ch      = cylinder[7:0]
;           cl[7:6] = cylinder[9:8]
;           dh      = head
;           cl[5:0] = sector (1-63)
;           es:bx  -> destination
;           al      = number of sectors
;
; output:   cf (0 = success, 1 = failure)

read_sectors_16:
    push ax
    mov si, 0x02    ; maximum attempts - 1
.top:
    mov ah, 0x02    ; read sectors into memory (int 0x13, ah = 0x02)
    int 0x13
    jnc .end        ; exit if read succeeded
    dec si          ; decrement remaining attempts
    jc  .end        ; exit if maximum attempts exceeded
    xor ah, ah      ; reset disk system (int 0x13, ah = 0x00)
    int 0x13
    jnc .top        ; retry if reset succeeded, otherwise exit
.end:
    pop ax
    retn

# print a number in hex
# IN
#   bx: the number
# CLOBBER
#   al, cx
real_mode_print_hex:
    mov cx, 4
.lp:
    mov al, bh
    shr al, 4

    cmp al, 0xA
    jb .below_0xA

    add al, 'A' - 0xA - '0'
.below_0xA:
    add al, '0'

    call real_mode_print_char

    shl bx, 4
    loop .lp

    call real_mode_new_line

    ret

real_mode_new_line:
    mov al, 0x0D
    call real_mode_print_char
    mov al, 0x0A
    call real_mode_print_char
    ret

real_mode_print_char:
    push bx
    xor bx, bx              ; Attribute=0/Current Video Page=0
    mov ah, 0x0e
    int 0x10                ; Display character
    pop bx
    ret

; boot signature
TIMES 510-($-$$) db 0

mbr_id:
dw 0xAA55

阶段2.asm:

global _stage2
global _stage2data

BITS 16

_stage2:
    mov al, '2'
    call bios_print_char

loop:
    jmp loop

    bios_print_char:
    push bx
    xor bx, bx              ; Attribute=0/Current Video Page=0
    mov ah, 0x0e
    int 0x10                ; Display character
    pop bx
    ret

; boot signature
TIMES 510-($-$$) db 0
_stage2data:
dw 0xCC77

链接器脚本“linker.ld”:

ENTRY(_start)
OUTPUT_FORMAT(binary)

SECTIONS
{
  output :
    {
      stage1.elf(.text)
      stage2.elf(.text)
    }
}

我使用以下命令编译并将所有内容链接在一起:

nasm -f elf64 stage1.asm -o stage1.elf
nasm -f elf64 stage2.asm -o stage2.elf
ld -m elf_x86_64 -o program.bin stage2.elf stage1.elf -nostdlib -T linker.ld

我在 QEMU 上运行二进制文件:

qemu-system-x86_64 -drive format=raw,file=program.bin

要在硬件上运行它,我将二进制文件写入 USB:

dd if=program.bin of=/dev/sdb1 && sync

标签: assemblyx86-64bootmbr

解决方案


您的引导加载程序实际上看起来不错。正如@jester 在真实硬件上指出的那样,如果您使用软盘仿真 (FDD) 引导 USB,那么您很可能需要一个BPB。您的屏幕截图中有迹象表明您正在将 USB 作为硬盘仿真 (HDD) 引导,因为驱动器号似乎是 0x0080。如果是这种情况,则不需要 BPB。

使用 USB HDD 仿真时,您可能需要一个分区表,其中一个分区标记为活动/可引导,以便某些 BIOS 将驱动器识别为可引导。如果没有它,一些 BIOS 可能会拒绝将驱动器识别为应该启动的驱动器,即使它0xaa55在最后 2 个字节中具有正确的磁盘签名 ( )。

我相信真正的问题在于您如何写入 USB 驱动器。您正在使用:

dd if=program.bin of=/dev/sdb1 && sync 

/dev/sdb1实际上是第一个分区,而不是驱动器的开头。您似乎想要的是写入驱动器的开头:

dd if=program.bin of=/dev/sdb && sync

您可能会问:如果您编写的引导加载程序没有写入驱动器的开头,它是如何实际运行的?我怀疑您的 USB 驱动器已使用主引导记录 (MBR) 进行格式化,该主引导记录 (MBR)在分区 1中链式加载卷引导记录 (VBR)引导加载程序,然后开始执行它。如果 U 盘在 Windows 中被格式化和分区,那么这种链式加载 MBR 是非常可能的。Windows 通常将 USB 格式化为一个大分区,并在驱动器的第一个扇区中放置一个 MBR,从第一个分区的第一个扇区链接加载 VBR。


其他观察

由于您似乎将所有内容作为硬盘媒体引导,您可能希望考虑使用扩展磁盘功能,如Int 13h/AH=42h而不是Int 13h/AH=2h。在处理较大的媒体(通常大于约 8GiB)时, Int 13/AH=2 可以通过CHS 寻址而不是LBA 寻址加载的内容非常有限。


推荐阅读