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

标签 assembly x86 bootloader osdev protected-mode

我正在开发一个引导加载程序,它将在切换到保护模式后引导到一个简单的内核。我用过 this paper作为教程,在第四或第五章的某个地方。理论上它应该以 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_SEG:init_pm .在 16 位模式下,它是一个 4 字节跳转到 16 位地址,如 segment:offset .但是你需要做 6 字节远跳转到 32 位地址。在 fasm 语法中,它将是

jmp fword CODE_SEG:init_pm

这将添加一个操作数大小前缀 0x66指导和治疗 init_pm作为 32 位偏移量。不确定如何在 nasm 中实现相同的目标,但您明白了。

关于assembly - 跳入保护模式时出现三重故障,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36562268/

相关文章:

linux - 在linux中,如何通过GNU ARM汇编进行系统调用

assembly - 自定义引导加载程序问题

linux - 为什么我们需要嵌入式设备中的引导加载程序?

assembly - 在引导加载程序之后运行 C 程序

java - Java 中的异常处理是如何实现的?

assembly - MOV r/m8,r8 和 MOV r8,r/m8 的区别

c - gcc 输出反汇编中的 data32 data32 nopw %cs :0x0(%rax, %rax,1) 指令的含义是什么?

linux - 这个 x86 Hello World using 32-bit int 0x80 Linux system calls from _start 的解释是什么?

assembly - 需要对我的 SSE/Assembly 尝试进行一些建设性的批评

带有线性插值的 x86 程序集衰落 bmp