引导加载程序在 qemu 中工作,但在 virtualbox 和硬件上失败

bootloader works in qemu but fails in virtualbox and on hardware

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

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

预期产出是:

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

这在 QEMU 中工作正常,但在 virtualbox 和硬件上失败。 在我看来,stage2 加载无声地失败(没有调用错误分支),我希望解决这个问题两周但没有成功。

QEMU 输出

硬件和虚拟盒输出

stage1.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

stage2.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 脚本“linker.ld”:

ENTRY(_start)
OUTPUT_FORMAT(binary)

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

我使用以下命令编译和 link 所有内容:

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

i 运行 QEMU 上的二进制文件:

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

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

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

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

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

我认为真正的问题在于您写入 USB 驱动器的方式。您正在使用:

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

/dev/sdb1其实是第一个分区,不是驱动器的开头。看起来你想要的是写入驱动器的开头:

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

你可能会问:你写的bootloader不写到驱动器的开头怎么会是运行呢?我怀疑您的 USB 驱动器在分区 1 中使用 Master Boot Record (MBR) that chain loaded the Volume Boot Record (VBR) 引导加载程序进行了格式化,然后开始执行它。如果 U 盘在 Windows 中被格式化和分区,那么这种链式加载 MBR 是很有可能的。 Windows 通常将 USB 格式化为一个大分区,并将 MBR 放在驱动器的第一个扇区,从第一个分区的第一个扇区链式加载 VBR。


其他观察结果

由于您似乎将所有内容都作为硬盘媒体启动,因此在处理较大的媒体(通常大于 8GiB)时,您可能希望考虑使用 Int 13h/AH=42h rather than Int 13h/AH=2h. Int 13/AH=2 is very limited in what it can load with CHS addressing rather than LBA addressing 等扩展磁盘功能。