Triple fault when jumping into protected mode

2019-05-08 07:37发布

问题:

I'm developing a boot loader, which will boot into a simple kernel after switching into protected mode. I used this paper as a tutorial, somewhere in chapter four or five. In theory it is supposed to start in 16-bit real mode, load the kernel into memory, switch to 32-bit protected mode and start executing the kernel code.

However, when I switch into protected mode and far jump or jump to another segment, it triple faults. Here is the main boot sector code:

[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

Here is the 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

Here is the code for switching into protected mode, where it triple faults:

[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

When I place a jmp $ instruction to idle at a certain spot, right before the jmp CODE_SEG:init_pm instruction, it idles there and does not triple fault. When I place it after that instruction, within the label init_pm, it does triple fault. So I am fairly sure that it is the cause. I'm not too sure why, maybe it's an issue with the GDT. I am new to operating system development and boot loaders. Any suggestions on how to solve this problem?

回答1:

Michael Petch gave the correct answer to this question in the comments. Unfortunately this has seem to been missed by several people as there have now been three incorrect answers posted, two of them making the same mistake. Here then is his comment posted as answer in the hopes that it makes it more visible:

Are you sure your GDT is correct? I think the thing that stands out upon cursory look is that each of your entries is 9 byte (72 bits). A GDT entry is 8 bytes (64-bits). it appears that maybe you meant db 0x0 ; Base (16-23) instead of dw 0x0 ; Base (16-23)? Note the difference is that dw is changed to db. Wrong GDT entries would generate a triple fault.

Michael Petch also made a good followup comment that pointed out other problems with the bootloader:

I'd also recommend looking at my general bootloader tips. You make the assumption that the DS (data segment) register is zero upon entry (since you use org 0x7c00). You should set it to zero explicitly. You also set the stack in an odd way. You set SP to 9000 but you don't set SS which means you don't really know where you are putting the stack in memory. You should set the SS register followed by setting the SP register. My bootloader tips offer an example.



回答2:

The problem is with you jmp CODE_SEG:init_pm. In 16-bit mode it's a 4-bytes jump to 16-bit address as segment:offset. But you need to do 6-byte far jump to a 32-bit address. In fasm syntax it will be

jmp fword CODE_SEG:init_pm

This will add an operand size prefix 0x66 to the instruction and treat init_pm as 32-bit offset. Not sure how to achieve the same in nasm, but you get the idea.