Assembly 跳入保护模式时出现三重故障

Assembly 跳入保护模式时出现三重故障,assembly,x86,bootloader,osdev,protected-mode,Assembly,X86,Bootloader,Osdev,Protected Mode,我正在开发一个引导加载程序,它将在切换到保护模式后引导到一个简单的内核中。我曾在第四章或第五章的某个地方做过指导。理论上,它应该以16位实数模式启动,将内核加载到内存中,切换到32位保护模式并开始执行内核代码 然而,当我切换到保护模式并跳远或跳转到另一段时,它会出现三重故障。以下是主引导扇区代码: [org 0x7c00] KERNEL_OFFSET equ 0x1000 mov [BOOT_DRIVE], dl ;Get the current boot drive from the

我正在开发一个引导加载程序,它将在切换到保护模式后引导到一个简单的内核中。我曾在第四章或第五章的某个地方做过指导。理论上,它应该以16位实数模式启动,将内核加载到内存中,切换到32位保护模式并开始执行内核代码

然而,当我切换到保护模式并跳远或跳转到另一段时,它会出现三重故障。以下是主引导扇区代码:

[org 0x7c00]

KERNEL_OFFSET equ 0x1000

mov [BOOT_DRIVE], dl    ;Get the current boot drive from the BIOS

mov bp, 0x9000          ;Set up stack, with enough room to grow downwards
mov sp, bp

mov bx, REAL_MODE_MSG
call print_string

call load_kernel

call switch_to_pm

jmp $                       ;Jump to current position and loop forever

%include "boot/util/print_string.asm"
%include "boot/util/disk.asm"
%include "boot/gdt/gdt.asm"
%include "boot/util/print_string_pm.asm"
%include "boot/switch_to_pm.asm"

[bits 16]
load_kernel:
    mov bx, LOAD_KERNEL_MSG ;Print a message saying we are loading the kernel
    call print_string
    mov bx, KERNEL_OFFSET       ;Set up disk_load routine parameters
    mov dh, 15
    mov dl, [BOOT_DRIVE]
    call disk_load              ;Call disk_load
    ret

[bits 32]
BEGIN_PM:
    mov ebx, PROT_MODE_MSG
    call print_string_pm
    call KERNEL_OFFSET

    jmp $

; Data
BOOT_DRIVE: db 0
REAL_MODE_MSG: db "Started in real mode.", 0
PROT_MODE_MSG: db "Successfully entered 32-bit protected mode.", 0
LOAD_KERNEL_MSG: db "Loading Kernel into memory", 0

; Bootsector padding
times 510-($-$$) db 0
dw 0xaa55
以下是GDT:

;Global Descriptor Table
gdt_start:

gdt_null:   ; We need a null descriptor at the start (8 bytes)
    dd 0x0
    dd 0x0

gdt_code:   ; Code segment descriptor
    ; Base=0x0, Limit=0xfffff
    ; 1st flags : (present)1 (privilege)00 (descriptor type)1 -> 1001b
    ; type flags : (code)1 (conforming)0 (readable)1 (accessed)0 -> 1010b
    ; 2nd flags : (granularity)1 (32 - bit default)1 (64 - bit seg)0 (AVL)0 -> 1100b
    dw 0xffff       ; Limit (bits 0-15)
    dw 0x0      ; Base (0-15)
    dw 0x0          ; Base (16-23)
    db 10011010b    ; 1st flags and type flags
    db 11001111b    ; 2nd flags and Limit (16-19)
    db 0x0          ; Base (24-31)

gdt_data:   ; Data segment descriptor
    ;Same as CSD except for type flags
    ; (code)0 (expand down)0 (writable)1 (accessed)0 -> 0010b
    dw 0xffff       ; Limit (bits 0-15)
    dw 0x0          ; Base (0-15)
    dw 0x0          ; Base (16-23)
    db 10010010b    ; 1st flags and type flags
    db 11001111b    ; 2nd flags and Limit (16-19)
    db 0x0          ; Base (24-31)

gdt_end:


;GDT Descriptor
gdt_descriptor:
    dw gdt_end - gdt_start - 1
    dd gdt_start

;Some Constants
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
以下是切换到保护模式的代码,其中会出现三重故障:

[bits 16]
switch_to_pm:
    cli
    lgdt [gdt_descriptor]   ; load the gdt
    mov eax, cr0            ; turn pm on
    or eax, 0x1
    mov cr0, eax
    jmp CODE_SEG:init_pm    ; THIS IS WHERE THE PROBLEM IS!

[bits 32]
init_pm:
    mov ax, DATA_SEG ; Point segment registers to the data
    mov ds, ax       ; selector defined in the gdt
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ebp, 0x90000 ; Update our stack
    mov esp, ebp
    call BEGIN_PM ;Move on

当我将
jmp$
指令放在某个位置空闲时,就在
jmp code_SEG:init_pm
指令之前,它在那里空闲,并且不会出现三重故障。当我将它放在该指令之后,在标签
init_pm
中,它会出现三重错误。因此,我相当肯定这是原因。我不太清楚为什么,也许这是GDT的问题。我不熟悉操作系统开发和引导加载程序。关于如何解决这个问题有什么建议吗?

问题出在您身上
jmp code\u SEG:init\u pm
。在16位模式下,4字节跳转到16位地址,如
段:偏移量
。但您需要将6字节远跳到32位地址。在fasm语法中,它将是

jmp fword CODE_SEG:init_pm

这将在指令中添加操作数大小前缀
0x66
,并将
init_pm
视为32位偏移量。不知道如何在nasm中实现同样的效果,但你知道了。迈克尔·佩奇在评论中给出了这个问题的正确答案。不幸的是,这似乎被一些人错过了,因为现在已经有三个错误的答案张贴,其中两个犯同样的错误。下面是他作为答案发表的评论,希望能让大家更清楚地看到:


你确定你的GDT是正确的吗?我认为粗略地看,最突出的是每个条目都是9字节(72位)。GDT条目是8字节(64位)。似乎您的意思是
db0x0;基本(16-23)
而不是
dw0x0;底座(16-23)
?请注意,区别在于
dw
更改为
db
。错误的GDT条目将产生三重错误

Michael Petch还做了一个很好的后续评论,指出了引导加载程序的其他问题:


我还建议你看看我的眼睛。您假设DS(数据段)寄存器在输入时为零(因为您使用的是org 0x7c00)。您应该显式地将其设置为零。您还以一种奇怪的方式设置堆栈。您将SP设置为9000,但未设置SS,这意味着您不知道将堆栈放入内存的位置。您应该先设置SS寄存器,然后再设置SP寄存器。我的引导加载程序提示提供了一个示例


你确定你的GDT是正确的吗?我认为粗略地看,最突出的是每个条目都是9字节(72位)。GDT条目是8字节(64位)。似乎您的意思是
db0x0;基本(16-23)
而不是
dw0x0;底座(16-23)
?请注意,区别在于
dw
更改为
db
。错误的GDT条目将产生三重错误。我还建议查看我的。您假设DS(数据段)寄存器在输入时为零(因为您使用的是org 0x7c00)。您应该显式地将其设置为零。您还以一种奇怪的方式设置堆栈。您将SP设置为9000,但未设置SS,这意味着您不知道将堆栈放入内存的位置。您应该先设置SS寄存器,然后再设置SP寄存器。我的引导程序提示提供了一个例子。我和罗斯里奇在一起。我知道几十年前的硬件并没有将SS设置为零。有些人将堆栈段放在最高可用内存区域的正下方,有些人将其放在前64k。很多人仍然认为,当BIOS跳转到CS:IP时,CS为零是必需的。在80年代,我们的引导磁盘无法工作,除非您对事情进行编码以不做任何假设。现在开发引导加载程序的一般经验法则(几十年来一直是不成文的规则)我可以向你保证@joshua,SS并不总是零。如果你想看看那些不考虑标准就做奇怪事情的硬件,试着在华硕硬件上编写一个引导加载程序。MBR不会做这些事情,除非你编写代码来实际设置它。他正在编写MBR,正如您所见,在他的代码中没有调整堆栈段寄存器的工作。也许你正在考虑在引导加载程序中通常做什么。。。例如,此处:OPs JMP指令将起作用,因为偏移量在较低的64kb内(可以表示为16位偏移量)。它使用选择器代码_SEG和zero将init_pm扩展到32位地址。在这种情况下,32位jmp是不必要的。哦,但是如果它加载的是一个零基址的cs,它在哪里添加(
realmode_cs遗憾的是,没有一个是自己工作的。Michael给了我一些我使用的代码,它工作了。除了他指出的问题之外,我仍然不知道到底是什么问题。