assembly - 引导加载程序在 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 加载无声无息地失败(错误分支没有被调用),我现在希望解决这个问题两周但没有成功。
阶段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
解决方案
您的引导加载程序实际上看起来不错。正如@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 寻址加载的内容非常有限。
推荐阅读
- javascript - 了解“滚动时隐藏导航栏”代码 - Javascript
- javascript - 服务器未收到从客户端发送的 socket.io 消息
- tensorflow - 为什么“I tensorflow/compiler/xla/service/service.cc:176] StreamExecutor device (0): Host, Default Version”无法完成
- asp.net-core - 坚持 IEnumerable
在服务器端验证 ASP.Net Core MVC 之后 - python - 如何在解决方案生成期间更改 Cplex 模型?
- docker - 由于无效的 docker build 标签引用,GitLab 管道失败
- jmeter - JMeter:确定每秒的线程数
- php - 用 CSS 隐藏 PHP 变量
- typescript - 我可以对 Typescript 中的变量进行类型断言,该变量应该对整个块/函数有效吗?
- ios - 强制将不兼容的 Cordova 插件与电容器一起使用