引导加载程序 - 加载第二阶段 - qemu 工作,真机不工作
bootloader - load 2nd stage - qemu works, real machine doesn't
作为学习练习,我写了一点16 bit bootloader for x86 bios系统。它
似乎在 QEMU 上运行良好。我把它放到一个旧的驱动器上
amd-turion 计算机 (x86_64),当我尝试启动该计算机时,
它会显示 BIOS 屏幕,然后我会在屏幕上看到一个闪烁的光标
黑屏。
我的问题是,QEMU 的 x86 模拟器有什么不同
和一台真正的 x86(64 位)计算机,它肯定使用 BIOS 而不是
UEFI?我是否必须为一台真正的计算机编写不同于
比 QEMU?这是我将信息复制到驱动器的方式吗?计算机是否采用了某种硬件级别的安全措施?
我了解到它在 VirtualBox 上也不起作用。
加载第二阶段似乎有问题(通过在真实硬件上成功打印第一阶段的字符)。
我的第一阶段引导加载程序使用这些文件中的代码:
stage_one.asm
[bits 16]
[org 0x7c00]
LOAD_ADDR: equ 0x9000 ; This is where I'm loading the 2nd stage in RAM.
start:
xor ax, ax ; nullify ax so we can set
mov ds, ax ; ds to 0
mov sp, bp ; relatively out of the way
mov bp, 0x8000 ; set up the stack
call disk_load ; load the new instructions
; at 0x9000
jmp LOAD_ADDR
%include "disk_load.asm"
times 510 - ($ - $$) db 0
dw 0xaa55 ;; end of bootsector
disk_load.asm
; load DH sectors to ES:BX from drive DL
disk_load :
mov ah, 0x02 ;read from disk
mov al, [num_sectors] ;read sector(s)
mov bx, 0
mov es, bx
mov ch, 0x00 ;track 0
mov cl, 0x02 ;start from 2nd sector
mov dh, 0x00 ;head 0
mov dl, 0x80 ;HDD 1
mov bx, LOAD_ADDR ;Where we read to in RAM.
int 0x13 ; BIOS interrupt
jc disk_error ; Jump if error ( i.e. carry flag set )
cmp al, [num_sectors] ; if num read != num expected
jne disk_error ; display error message
mov ax, READ_SUCCESS
call print_string
ret
disk_error :
mov ax, DISK_ERROR_MSG
call print_string
; Get the status of the last operation.
xor ax, ax ; nullify ax
mov ah, 0x01 ; status fxn
;mov dl, 0x80 ; 0x80 is our drive
int 0x13 ; call fxn
;mov ah, 0 ; when we print ax, we only care about the status,
; which is in al. So, we probably want to nullify
; 'ah' to prevent confusion.
call print_hex ; print resulting status msg.
jmp $
status_error:
mov ax, STATUS_ERROR
call print_string
jmp $
num_sectors: db 0x01
; Variables
DISK_ERROR_MSG: db "Disk read error: status = " , 0
STATUS_ERROR: db 'status failed.', 0
READ_SUCCESS: db 'Read success! ', 0
;AH 02h
;AL Sectors To Read Count
;CH Cylinder
;CL Sector
;DH Head
;DL Drive
;ES:BX Buffer Address Pointer
%include "print.asm"
print.asm
print_char:
pusha
mov ah, 0x0e
int 0x10
popa
ret
print_string:
pusha ; preserve the registers
; on the stack.
mov bx, ax
print_string_loop:
mov al, [bx] ;move buffer index to al
cmp al, 0 ;if [[ al == 0 ]]; then
je print_string_end ; goto print_string_end
inc bx ;else bx++
call print_char
jmp print_string_loop ; goto print_string_loop
print_string_end:
popa
ret
print_hex_char:
pusha
print_hex_loop:
;print a single hex digit
cmp al, 0x9
jg a_thru_f
zero_thru_nine:
add al, '0'
call print_char
jmp print_hex_char_end
a_thru_f:
add al, 'A'-0xA
call print_char
print_hex_char_end:
popa
ret
print_hex:
pusha
;note on little-endianness:
; If you store 1234 in AX,
; 4 is the LSB, therefore:
; AH = 12
; AL = 34
;
; Moral of the story --
; If you print, you need to
; print AH first.
mov bl, al
and bl, 0xF
mov bh, al
shr bh, 4
and bh, 0xF
mov cl, ah
and cl, 0xF
mov ch, ah
shr ch, 4
and ch, 0xF
mov al, '0'
call print_char
mov al, 'x'
call print_char
mov al, ch
call print_hex_char
mov al, cl
call print_hex_char
mov al, bh
call print_hex_char
mov al, bl
call print_hex_char
mov al, ' '
call print_char
popa
ret
我这样生成我的内核:
nasm -f bin -o stage1.bin stage_one.asm && \
nasm -f bin -o stage2.bin stage_two.asm && \
cat stage1.bin stage2.bin > raw.bin && \
#(mkdir cdiso || :) && \
#cp stage1.bin cdiso && cp stage2.bin cdiso && \
#mkisofs -o raw.iso -b stage1.bin cdiso/
qemu-system-x86_64 raw.bin || \
echo "COULD NOT FINISH ASSEMBLING." &>/dev/stderr
通过将控制寄存器的第 0 位设置为 0 显式启用实模式可能会解决此问题(未经测试)。
cli
mov al, cr0
mov bl, 1
not bl
and al, bl
mov cr0, al
sli
我发现了导致我的代码失败的错误。
问题出在使用 int 13h
从硬盘读取时。在 QEMU 中,磁盘的设备号始终是 0x80
,因此我将其硬编码到寄存器 dl
中,认为这是标准的硬盘驱动器名称。事实证明,BIOS 方便地自动将 dl
寄存器设置为它试图从中启动的驱动器号,因此通过注释掉硬编码部分,我能够让它在 [=21] 上工作=]QEMU 和真正的硬件。
为了解决这个问题,我删除了(注释掉)文件中的这一行 disk_load.asm:
mov dl, 0x80 ;HDD 1
作为学习练习,我写了一点16 bit bootloader for x86 bios系统。它 似乎在 QEMU 上运行良好。我把它放到一个旧的驱动器上 amd-turion 计算机 (x86_64),当我尝试启动该计算机时, 它会显示 BIOS 屏幕,然后我会在屏幕上看到一个闪烁的光标 黑屏。
我的问题是,QEMU 的 x86 模拟器有什么不同 和一台真正的 x86(64 位)计算机,它肯定使用 BIOS 而不是 UEFI?我是否必须为一台真正的计算机编写不同于 比 QEMU?这是我将信息复制到驱动器的方式吗?计算机是否采用了某种硬件级别的安全措施?
我了解到它在 VirtualBox 上也不起作用。
加载第二阶段似乎有问题(通过在真实硬件上成功打印第一阶段的字符)。
我的第一阶段引导加载程序使用这些文件中的代码:
stage_one.asm
[bits 16]
[org 0x7c00]
LOAD_ADDR: equ 0x9000 ; This is where I'm loading the 2nd stage in RAM.
start:
xor ax, ax ; nullify ax so we can set
mov ds, ax ; ds to 0
mov sp, bp ; relatively out of the way
mov bp, 0x8000 ; set up the stack
call disk_load ; load the new instructions
; at 0x9000
jmp LOAD_ADDR
%include "disk_load.asm"
times 510 - ($ - $$) db 0
dw 0xaa55 ;; end of bootsector
disk_load.asm
; load DH sectors to ES:BX from drive DL
disk_load :
mov ah, 0x02 ;read from disk
mov al, [num_sectors] ;read sector(s)
mov bx, 0
mov es, bx
mov ch, 0x00 ;track 0
mov cl, 0x02 ;start from 2nd sector
mov dh, 0x00 ;head 0
mov dl, 0x80 ;HDD 1
mov bx, LOAD_ADDR ;Where we read to in RAM.
int 0x13 ; BIOS interrupt
jc disk_error ; Jump if error ( i.e. carry flag set )
cmp al, [num_sectors] ; if num read != num expected
jne disk_error ; display error message
mov ax, READ_SUCCESS
call print_string
ret
disk_error :
mov ax, DISK_ERROR_MSG
call print_string
; Get the status of the last operation.
xor ax, ax ; nullify ax
mov ah, 0x01 ; status fxn
;mov dl, 0x80 ; 0x80 is our drive
int 0x13 ; call fxn
;mov ah, 0 ; when we print ax, we only care about the status,
; which is in al. So, we probably want to nullify
; 'ah' to prevent confusion.
call print_hex ; print resulting status msg.
jmp $
status_error:
mov ax, STATUS_ERROR
call print_string
jmp $
num_sectors: db 0x01
; Variables
DISK_ERROR_MSG: db "Disk read error: status = " , 0
STATUS_ERROR: db 'status failed.', 0
READ_SUCCESS: db 'Read success! ', 0
;AH 02h
;AL Sectors To Read Count
;CH Cylinder
;CL Sector
;DH Head
;DL Drive
;ES:BX Buffer Address Pointer
%include "print.asm"
print.asm
print_char:
pusha
mov ah, 0x0e
int 0x10
popa
ret
print_string:
pusha ; preserve the registers
; on the stack.
mov bx, ax
print_string_loop:
mov al, [bx] ;move buffer index to al
cmp al, 0 ;if [[ al == 0 ]]; then
je print_string_end ; goto print_string_end
inc bx ;else bx++
call print_char
jmp print_string_loop ; goto print_string_loop
print_string_end:
popa
ret
print_hex_char:
pusha
print_hex_loop:
;print a single hex digit
cmp al, 0x9
jg a_thru_f
zero_thru_nine:
add al, '0'
call print_char
jmp print_hex_char_end
a_thru_f:
add al, 'A'-0xA
call print_char
print_hex_char_end:
popa
ret
print_hex:
pusha
;note on little-endianness:
; If you store 1234 in AX,
; 4 is the LSB, therefore:
; AH = 12
; AL = 34
;
; Moral of the story --
; If you print, you need to
; print AH first.
mov bl, al
and bl, 0xF
mov bh, al
shr bh, 4
and bh, 0xF
mov cl, ah
and cl, 0xF
mov ch, ah
shr ch, 4
and ch, 0xF
mov al, '0'
call print_char
mov al, 'x'
call print_char
mov al, ch
call print_hex_char
mov al, cl
call print_hex_char
mov al, bh
call print_hex_char
mov al, bl
call print_hex_char
mov al, ' '
call print_char
popa
ret
我这样生成我的内核:
nasm -f bin -o stage1.bin stage_one.asm && \
nasm -f bin -o stage2.bin stage_two.asm && \
cat stage1.bin stage2.bin > raw.bin && \
#(mkdir cdiso || :) && \
#cp stage1.bin cdiso && cp stage2.bin cdiso && \
#mkisofs -o raw.iso -b stage1.bin cdiso/
qemu-system-x86_64 raw.bin || \
echo "COULD NOT FINISH ASSEMBLING." &>/dev/stderr
通过将控制寄存器的第 0 位设置为 0 显式启用实模式可能会解决此问题(未经测试)。
cli
mov al, cr0
mov bl, 1
not bl
and al, bl
mov cr0, al
sli
我发现了导致我的代码失败的错误。
问题出在使用 int 13h
从硬盘读取时。在 QEMU 中,磁盘的设备号始终是 0x80
,因此我将其硬编码到寄存器 dl
中,认为这是标准的硬盘驱动器名称。事实证明,BIOS 方便地自动将 dl
寄存器设置为它试图从中启动的驱动器号,因此通过注释掉硬编码部分,我能够让它在 [=21] 上工作=]QEMU 和真正的硬件。
为了解决这个问题,我删除了(注释掉)文件中的这一行 disk_load.asm:
mov dl, 0x80 ;HDD 1