我的引导加载程序在跳转到内核时遇到了问题

编程语言 2026-07-08

我在把内核跳转到我的引导加载程序时遇到了麻烦。我读取磁盘的第二扇区并在设置好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,以便让 clihlt 指令能够重新执行。(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的 关于引导加载程序开发的一般建议.

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章