在跳转到保护模式时重置

Reset at jumping to Protected Mode

我正在开发一个引导加载程序来加载我的 OS(我没有使用 GRUB,因为我想学习汇编程序),我的代码出现三重故障并重置 QEMU。这是有问题的代码:

引导程序

 [ org 0x7c00 ]
 [ BITS 16 ]

jmp 0x0000:Start

%include 'PrintFunc.asm'
%include 'DiskOp.asm'

Start:
    cli
    xor ax, ax
    mov ss, ax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov sp, 500h
    mov bp, 1500h
    cld
    sti
    
    mov ax, 0007h
    int 10h
    
    mov si, A1
    call printStr
    
    mov [BootDrive], dl
    
    mov al, 2
    
    call diskLoad
    
    mov si, A2
    call printStr
    
    jmp Cont
    
A1: db 'Loading sectors...', 0xA, 0xD, 0

BootDrive: db 0x00
    
times 510-($-$$) db 0
dw 0xAA55

A2: db 'Loaded two more sectors.', 0xA, 0xD, 0xA, 0
A3: db 'Checking A20...', 0xA, 0xD, 0
A4: db 'Enabling A20...', 0xA, 0xD, 0
A5: db 'A20 Enabled.', 0xA, 0xD, 0xA, 0
A6: db 'Loaded GDT, preparing to jump into PM.', 0xA, 0xD, 0
A7: db 'Landed in 32bit Protected Mode.', 0xA, 0xD, 0xA, 0
A8: db 0xA, 'Current FlameLoader version: ', 0
Ver: db '0.1', 0xA, 0xD, 0

GDT: 
    .NULL:
        dq 0
    
    .CodeSeg:
        dw 0FFFFh
        dw 0
        
        db 0
        db 010011010b
        db 011011111b
        db 0
    .DataSeg:
        dw 0FFFFh
        dw 0
        
        db 0
        db 010010010b
        db 011011111b
        db 0
    
    .end:
    
    .desc:
        dw .end - GDT - 1
        db GDT

%include 'A20Func.asm'
        
Cont:
    mov si, A3
    call printStr

    call testA20
    cmp ax, 1
    je EA20
    
    mov si, A4
    call printStr
    
    call enableA20
    
EA20:   ; A20 Enabled
    mov si, A5
    call printStr

    
    cli
    
    lgdt [GDT.desc]
    
    sti
        
    mov si, A6
    call printStr
    
    cli

    mov eax, cr0
    or eax, 1
    mov cr0, eax
    
    jmp 0x8:Init32

 [BITS 32]
    
Init32:
    
    jmp $   ; Debug
    
    mov ax, 0x10
    mov ds, ax
    mov ss, ax
    mov es, ax
    
    mov esp, 500h
    
    jmp Start32

%include 'Print32.asm'

Start32:
    
    mov esi, A7
    call PrintStr32
    
    jmp $   ; Actual program end

基于 BOCHS 调试器并尝试使用 jmp $ 在不同点停止程序,我推断问题出在这一行:

jmp 0x8:Init32

P.S。我没有包含函数文件,因为我认为它们在这里没有用。

你没有显示你的磁盘加载函数,但最终它必须做一些等同于:

    mov ax, 0201h
    mov cx, 0002h
    mov dh, 0
    mov bx, 7e00h
    int 13h

这会将 CHS=(0,0,2) 加载到引导加载程序之后的 ES:BX (0x0000:0x7e00)。或者,您可以将 ES:BX 设置为 0x07e0:0x0000。由于您声称问题似乎是跳入保护模式,因此我假设磁盘负载正常。

这实际上只剩下 GDT 可能是问题而不是潜在的堆栈问题1。我确实在这里看到一个可能导致 FAR JMP 进入保护模式失败的重大问题:

.desc:
    dw .end - GDT - 1
    db GDT

GDT Record (GDTR) 应该是一个长度为 1 的 WORD (dw),后跟一个 DWORD (dd)。您已将基数定义为单个字节 db!您需要将其更改为 dd。它应该看起来像:

.desc:
    dw .end - GDT - 1
    dd GDT
不幸的是,

NASM 不会试图告诉您它填充到一个字节中的值是从另一个值缩小(切碎)的。此问题可能导致您遇到问题,因为代码描述符无效,导致 jmp 0x8:Init32 三重故障并导致重启。

如果使用 BOCHS 进行调试,您可以在 0x7c00 处设置一个断点并逐条执行指令,直到 lgdt [GDT.desc] 之后的指令。您本来可以使用 info gdt 查看 GDT。您可能会发现基础错误并且所有条目都不正确。


补充说明

  • 1您已将堆栈指针 SS:SP 设置为 0x0000:0x0500。堆栈从该地址向下增长。 0x0000:0x0500 包含 BIOS Data Area (BDA) and just below it is the real mode interrupt vector table (IVT) 的数据。你应该把堆栈放在更安全的地方。 0x0000:0x7c00 在引导加载程序下方向下扩展会留下大量堆栈空间,然后才会破坏 BDA。

  • 您需要在进入保护模式之前关闭中断,这样您就可以在代码的开头简单地执行 CLI 并避免所有 CLI/STI 说明。或者,您可以一直启用它们,并在进入保护模式之前执行 CLI

    通常习惯在启用中断的情况下通过首先将 SS 更新为新的来一起更新 SS:SP值和 SP。这是因为更新 SS 有一个副作用,即在下一条指令之后禁用中断。如果更新 SP 是以下指令,则操作是自动完成的,并且在更新 SSSP[= 之间不会发生中断77=]。在某些 8088 处理器上存在一个缺陷,该缺陷并未发生,但这不适用于此处,因为您的代码需要 386+。

  • 在你的实模式代码中设置 BP 没有任何效果,因为你没有在任何地方使用堆栈帧,也没有调用任何类似 BIOS 例程的东西这需要将 BP 设置为特定值。您也不需要设置 GSFS 因为您的代码没有在实模式下使用它们。

  • 您的代码开头可能显示为:

    Start:
        xor ax, ax
        mov ds, ax
        mov es, ax
        mov ss, ax     ; Set SS:SP to grow down beneath bootloader at 0x0000:0x7c00
        mov sp, 7c00h
        cld
    
  • 我在这个 中还有一些可能有用的引导加载程序提示。