Oac*_*tzl 6 boot assembly mbr x86-64
我的引导加载程序由两个 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
Run Code Online (Sandbox Code Playgroud)
这在 QEMU 中工作正常,但在 virtualbox 和硬件上失败。在我看来,stage2 加载无声无息地失败(错误分支没有被调用),我希望解决这个问题两周,但没有成功。
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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
链接器脚本“linker.ld”:
ENTRY(_start)
OUTPUT_FORMAT(binary)
SECTIONS
{
output :
{
stage1.elf(.text)
stage2.elf(.text)
}
}
Run Code Online (Sandbox Code Playgroud)
我使用以下命令将所有内容编译和链接在一起:
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
Run Code Online (Sandbox Code Playgroud)
我在 QEMU 上运行二进制文件:
qemu-system-x86_64 -drive format=raw,file=program.bin
Run Code Online (Sandbox Code Playgroud)
要在硬件上运行它,我将二进制文件写入 USB:
dd if=program.bin of=/dev/sdb1 && sync
Run Code Online (Sandbox Code Playgroud)
你的引导程序实际上看起来很不错。正如@jester 在真实硬件上指出的那样,如果您使用软盘仿真 (FDD) 启动 USB,那么您很可能需要一个BPB。您的屏幕截图中有迹象表明您正在将 USB 作为硬盘仿真 (HDD) 启动,因为驱动器编号似乎是 0x0080。如果是这种情况,则不需要 BPB。
使用 USB HDD 仿真时,您可能需要一个分区表,其中一个分区标记为活动/可启动,以便某些 BIOS 将驱动器识别为可启动。没有它,一些 BIOS 可能拒绝将驱动器识别为它应该启动的东西,即使它0xaa55在最后 2 个字节中有正确的磁盘签名 ( )。
我相信真正的问题在于您如何写入 USB 驱动器。您正在使用:
dd if=program.bin of=/dev/sdb1 && sync
Run Code Online (Sandbox Code Playgroud)
/dev/sdb1实际上是第一个分区,而不是驱动器的开头。看起来您想要的是写入驱动器的开头:
dd if=program.bin of=/dev/sdb && sync
Run Code Online (Sandbox Code Playgroud)
您可能会问:如果没有写入驱动器的开头,您编写的引导加载程序如何实际运行?我怀疑是你的USB驱动器与一个格式化的主引导记录(MBR)是链装载的卷引导记录(VBR) bootloader的分区1,然后开始执行它。如果 U 盘在 Windows 中进行了格式化和分区,那么这种链式加载 MBR 是非常可能的。Windows 通常将 USB 格式化为一个大分区,并将 MBR 放在驱动器的第一个扇区中,该驱动器从第一个分区的第一个扇区链式加载 VBR。
由于您似乎将所有内容作为硬盘介质启动,您可能希望考虑使用扩展磁盘功能,例如Int 13h/AH=42h而不是Int 13h/AH=2h。在处理较大的媒体(通常大于约 8GiB)时,Int 13/AH=2 可以使用CHS 寻址而不是LBA 寻址加载的内容非常有限。