我的引导加载程序在跳转到内核时遇到了问题
我在把内核跳转到我的引导加载程序时遇到了麻烦。我读取磁盘的第二扇区并在设置好GDT之后将其加载到0x8000,然后我进入了保护模式。接着我再次设置段寄存器,并把堆栈指针设为0x90000,但当我尝试对内核进行远跳时,似乎没起作用,它只是让QEMU进入一个循环,甚至近跳也不行。请问有人能帮忙吗?以下是为了说明的代码
bits 16
org 0x7c00
KERNEL equ 0x8000
global _start
section .text
_start:
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x6000
mov ax, 0x0003
int 0x10
mov si, msg
call print
call enable_a20
call rdisk
lgdt [gdt_desc]
mov si, pm
call print
mov eax, cr0
or eax, 1
mov cr0, eax
jmp 0x08:protected_mode
[bits 32]
protected_mode:
cli
mov eax, 0x10
mov ds, eax
mov es, eax
mov ss, eax
mov fs, eax
mov gs, eax
mov ebp, 0x90000
mov esp, ebp
mov byte [0xB8000], 'P'
mov byte [0xB8001], 0x0e
;jmp 0x08:KERNEL
cli
hlt
jmp $
print:
lodsb
test al, al
jz .done
mov ah, 0x0e
int 0x10
jmp print
.done:
ret
enable_a20:
in al, 0x92
or al, 2
out 0x92, al
ret
rdisk:
xor ax, ax
mov es, ax
mov bx, 0x8000
mov ah, 0x02
mov al, 1
mov ch, 0
mov cl, 2
mov dh, 0
mov dl, 0x80
;mov bx, 0x8000
int 0x13
jc .err1
cmp al, 1
jne .err2
ret
.err1:
mov si, err1
call print
cli
hlt
jmp $
.err2:
mov si, err2
call print
cli
hlt
jmp $
gdt_start:
gdt_null:
dd 0x00000000
dd 0x00000000
gdt_code:
dw 0xffff
dw 0x0000
db 0x00
db 0b10011010
db 0b11001111
db 0x00
gdt_data:
dw 0xffff
dw 0x0000
db 0x00
db 0b10010010
db 0b11001111
db 0x00
gdt_end:
gdt_desc:
dw gdt_end - gdt_start - 1
dd gdt_start
msg db "Hello from BOOT OS!", 13, 10, 0
err1 db "Failed to Read Disk", 13, 10, 0
err2 db "Error: DS:AL is not equal to one", 13, 10, 0
pm db "Entering Protected Mode!", 13, 10, 0
times 510 - ($-$$) db 0
dw 0xaa55
解决方案
All of those print, enable_a20, and rdisk subroutines get called from within the real address mode. None of them should linger beneath that [bits 32]. Put the lot in between:
jmp 0x08:protected_mode
print:
enable_a20:
rdisk:
[bits 32]
Make it count
cli hlt jmp $
无限循环 jmp $ 最好改成 jmp $-2,以便让 cli 和 hlt 指令能够重新执行。(cli 不需要在循环中;如果你在 hlt 周围使用循环,你也不需要 cli,让BIOS处理像Ctrl+Alt+Del这样重启的键盘中断也没问题。除非切换到32位模式时中断被打断,在这种情况下你应该在那之前 cli,以避免出现中断崩溃的时间窗口。)
Make it a habit
cli mov eax, 0x10 mov ds, eax mov es, eax mov ss, eax mov fs, eax mov gs, eax mov ebp, 0x90000 mov esp, ebp
即使在 cli 的保护下,养成一个好习惯:始终按这个顺序同时修改SS和 SP——你一开始就做对了一切:
cli
mov ebp, 0x90000
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax ; 1st
mov esp, ebp ; 2nd
魔法数字
xor ax, ax mov es, ax mov bx, 0x8000
如果你的程序中并不使用 KERNEL equ 0x8000,定义它又有什么用?
为何要重复在开始时就设置过的ES=0?
于是就变成了:
mov bx, KERNEL
信任BIOS给出的驱动器号
mov dl, 0x80
引导加载程序开始运行时,BIOS能提供的不多,只有通过DL寄存器接收的BootDrive编号。请使用它,而不是这一个硬编码的0x80。
不过,不要对除线性地址7C00以外的任何初始条件过于依赖BIOS。请参见Michael Petch的 关于引导加载程序开发的一般建议.