【问题标题】:bootloader works in qemu but fails in virtualbox and on hardware引导加载程序在 qemu 中工作,但在 virtualbox 和硬件中失败
【发布时间】:2020-10-10 12:31:59
【问题描述】:

我的引导加载程序由两个 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 输出

硬件和虚拟盒输出

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

【问题讨论】:

  • 您可能需要添加 BPB。见this answer
  • 添加了一个 BPB 部分,就像它在链接中描述的那样,并用十六进制编辑器确认它实际上是写入二进制文件 -> 相同的结果,仍然无法正常工作
  • @MichaelPetch 这解决了它!非常感谢您为此以及您就引导加载程序发表的所有其他精彩帖子,我从他们身上学到了很多!

标签: assembly x86-64 boot mbr


【解决方案1】:

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

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

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

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

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

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

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


其他观察

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

【讨论】:

  • 我会对否决票感到好奇。有什么我可能做错了吗?如果是这样,我很乐意调整答案。
  • 当然不是我的反对意见。关于 INT13h/AH42h:我以前使用过它,但随着我越来越绝望,我切换到“旧”版本以排除那里的错误。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-09
  • 2012-12-23
  • 2016-02-14
  • 1970-01-01
  • 1970-01-01
  • 2015-11-28
相关资源
最近更新 更多