跳转到长模式 x86_64 时出现三重故障

问题描述 投票:0回答:1

我有业余爱好操作系统,我希望它跳转到 64 位长模式,在远跳转到 64 位长模式条目之前一切正常,分页工作正常,但 QEMU 日志文件显示 EFER 值是 LMA

Triple fault
CPU Reset (CPU 0)
RAX=0000000000000100 RBX=0000000080000011 RCX=00000000c0000080 RDX=0000000000000000
RSI=0000000000000015 RDI=000000000020102d RBP=0000000000000000 RSP=000000000020b000
R8 =0000000000000000 R9 =0000000000000000 R10=0000000000000000 R11=0000000000000000
R12=0000000000000000 R13=0000000000000000 R14=0000000000000000 R15=0000000000000000
RIP=000000008020015b RFL=00000086 [--S--P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0018 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 0000000000000000 00000000 00209a00 DPL=0 CS64 [-R-]
SS =0018 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0018 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0018 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0018 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 0000000000000000 0000ffff 00008200 DPL=0 LDT
TR =0000 0000000000000000 0000ffff 00008b00 DPL=0 TSS64-busy
GDT=     000000000020103b 00000017
IDT=     0000000000000000 00000000
CR0=80000011 CR2=000000008020015b CR3=0000000000202000 CR4=00000020
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=0000000000000400 CCD=ffffffff80000011 CCO=LOGICL
EFER=0000000000000500
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=0000000000000000 0000000000000000 XMM01=0000000000000000 0000000000000000
XMM02=0000000000000000 0000000000000000 XMM03=0000000000000000 0000000000000000
XMM04=0000000000000000 0000000000000000 XMM05=0000000000000000 0000000000000000
XMM06=0000000000000000 0000000000000000 XMM07=0000000000000000 0000000000000000
XMM08=0000000000000000 0000000000000000 XMM09=0000000000000000 0000000000000000
XMM10=0000000000000000 0000000000000000 XMM11=0000000000000000 0000000000000000
XMM12=0000000000000000 0000000000000000 XMM13=0000000000000000 0000000000000000
XMM14=0000000000000000 0000000000000000 XMM15=0000000000000000 0000000000000000

CR2寄存器显示页面错误发生在0x20015b虚拟地址 这是我的代码:

%define KERNEL_VIRTUAL_ADDR 0xFFFFFFFF80000000
section .multiboot_header
header_start:
    align 8
    dd 0xE85250D6 
    dd 0           
    dd header_end - header_start   
    dd 0x100000000 - (0xE85250D6 + 0 + (header_end - header_start))
    dw 0
    dw 0
    dd 8
header_end:
section .multiboot.text
global start
bits 32
;functions
check_cpuid:
        pushfd
        pop eax
        mov ecx, eax
        xor eax, 1 << 21
        push eax
        popfd
        pushfd
        pop eax
        push ecx
        popfd
        xor eax, ecx
        jz .no_cpuid
        mov edi, cpuid_av - KERNEL_VIRTUAL_ADDR
        call print
        .cont:
        ret
.no_cpuid:
        mov edi, cpuid_err - KERNEL_VIRTUAL_ADDR
        call print
        jmp check_cpuid.cont

check_long_mode:
        mov eax, 0x80000000
        cpuid
        cmp eax, 0x80000001
        jb .no_long

        mov eax, 0x80000001
        cpuid
        test edx, 1 << 29
        jz .no_long
        
        mov edi, lm - KERNEL_VIRTUAL_ADDR
        call print
        .cont:
        ret
.no_long:
        mov edi, no_lm - KERNEL_VIRTUAL_ADDR
        call print
        jmp check_long_mode.cont

print:
    mov dh, 0x0f
    xor ecx, ecx
    mov dl, [edi + ecx]
    mov word [0xb8000 + esi*2], dx
    .loopx:
        inc ecx
        inc esi
        mov dl, [edi + ecx]
        mov word [0xb8000 + esi*2], dx
        cmp byte [edi + ecx], 0
        jnz .loopx
    ret
start:
    cmp eax, 0x36d76289
    je loader
loader:
    mov esp, stack.top - KERNEL_VIRTUAL_ADDR
    ;disable paging
    cli                                  
    mov eax, cr0
    or eax, 0 << 31
    mov cr0, eax

    mov eax, cr4
    or eax, 1 << 5
    mov cr4, eax
    
    xor esi, esi
    call check_cpuid
    call check_long_mode
    mov eax, p3_table - KERNEL_VIRTUAL_ADDR
    or eax, 0b11
    mov dword [p4_table - KERNEL_VIRTUAL_ADDR], eax
    mov eax, p2_table - KERNEL_VIRTUAL_ADDR
    or eax, 0b11
    mov dword [p3_table - KERNEL_VIRTUAL_ADDR], eax
    mov eax, p1_table_1 - KERNEL_VIRTUAL_ADDR
    or eax, 0b11
    mov dword [p2_table - KERNEL_VIRTUAL_ADDR], eax

    mov eax, p1_table_1 - KERNEL_VIRTUAL_ADDR + 4096
    or eax, 0b11
    mov dword [p2_table - KERNEL_VIRTUAL_ADDR + 8], eax
    
    mov ecx, 0
    .map_p1_table:
        mov eax, 4096
        mul ecx
        or eax, 0b11
        mov [p1_table_1 - KERNEL_VIRTUAL_ADDR + ecx*8], eax
        inc ecx
        cmp ecx, 1024
        jne .map_p1_table

    mov eax, p4_table - KERNEL_VIRTUAL_ADDR
    mov cr3, eax

    mov ecx, 0xC0000080
    rdmsr
    or eax, (1 << 8)
    wrmsr

    mov ebx, cr0 
    or ebx, 1 << 31
    mov cr0, ebx 
    lgdt [gdt64.pointer_low - KERNEL_VIRTUAL_ADDR] 
    jmp (0x8):(kernel_jumper - KERNEL_VIRTUAL_ADDR) 
[bits 64]
kernel_jumper:
    .h:
        jmp .h ;this is 0x20015b that made page fault
section .data
cpuid_err: db "CPUID:0 ", 0
cpuid_av: db "CPUID:1 ", 0
os_err: db " multiboot: ", 0
no_lm: db "long-mode: 0 ", 0
lm: db "long-mode: 1 ", 0
gdt64:
    dq  0   ;first entry = 0
    .code equ $ - gdt64
        ; equ tells the compiler to set the address of the variable at given address ($ - gdt64). $ is the current position.
        ; set the following values:
        ; descriptor type: bit 44 has to be 1 for code and data segments
        ; present: bit 47 has to be  1 if the entry is valid
        ; read/write: bit 41 1 means that is readable
        ; executable: bit 43 it has to be 1 for code segments
        ; 64bit: bit 53 1 if this is a 64bit gdt
        dq (1 <<44) | (1 << 47) | (1 << 41) | (1 << 43) | (1 << 53)  ;second entry=code=0x8
    .data equ $ - gdt64
        dq (1 << 44) | (1 << 47) | (1 << 41)    ;third entry = data = 0x10
.pointer:
    dw .pointer - gdt64 - 1
    dq gdt64
.pointer_low:
    dw .pointer - gdt64 - 1
    dq gdt64 - KERNEL_VIRTUAL_ADDR
section .bss
align 4096
p4_table:
        resb 4096
p3_table:
        resb 4096
p2_table:
        resb 4096
p1_table_1:
        resb 8192
align 16
stack:
    resb 16384
    .top:

这是我的 linker.ld 文件:

OUTPUT(X86-64)
ENTRY(start)

SECTIONS {
    . = 2M;

    _kernel_start = .;
    _kern_virtual_offset = 0xffffffff80000000;
    .multiboot_header :
    {
        /* Be sure that multiboot header is at the beginning */
        *(.multiboot_header)
    }

    .multiboot.text :
    {
        *(.multiboot.text)
    }

    . += _kern_virtual_offset;
    /* Add a symbol that indicates the start address of the kernel. */
    .text ALIGN (4K) : AT (ADDR (.text) - _kern_virtual_offset)
    {
        *(.text)
        *(.text.*)
    }
    .rodata ALIGN (4K) : AT (ADDR (.rodata) - _kern_virtual_offset)
    {
        *(.rodata)
        *(.rodata.*)
    }
    .data ALIGN (4K) : AT (ADDR (.data) - _kern_virtual_offset)
    {
        *(.data)
        *(.data.*)
    }
    .bss ALIGN (4K) : AT (ADDR (.bss) - _kern_virtual_offset)
    {
        *(.bss)
    }

    _kernel_end = .;
    _kernel_physical_end = . - _kern_virtual_offset;
}

从给定的信息来看,似乎发生了页面错误,但如果操作系统没有映射第 2 MB,则在此之前将不会获取代码,我的操作系统条目为 2 MB。

**编辑:**感谢迈克尔·佩奇,

jmp (0x8):(kernel_jumper - KERNEL_VIRTUAL_ADDR)
应该是
jmp (0x8):(kernel_jumper)
。 我编辑了代码,操作系统最终进入了 x64 长模式,我不知道为什么我要子化 kernel_jumper 并将整个函数放在下半部分,如果我从代码开始就这样做,操作系统将从一开始就会出现三重错误。 我认为 RIP 值很混乱,因为汇编器计算将 0x20015b 减去 0xFFFFFFFF80000000。

assembly segmentation-fault x86-64 osdev gdt
1个回答
0
投票

看到您的链接器脚本,我可以确认问题是到

kernel_jumper
的 FAR JMP:

    jmp (0x8):(kernel_jumper - KERNEL_VIRTUAL_ADDR)

[bits 64]
kernel_jumper:
    .h:
        jmp .h ;this is 0x20015b that made page fault

问题在于

kernel_jumper
仍然是下半部地址并被视为下半部地址。应该是:

jmp (0x8):(kernel_jumper)

我相信这超出了所要求的范围,但代码不完整,因为它没有映射上半部分地址,并且没有从下半部分转换到上半部分。以下代码添加了额外的代码来处理映射高半地址;从下半部过渡到上半部;删除下半部分映射;从上半部分重新加载 GDT;并设置段寄存器:

%define KERNEL_VIRTUAL_ADDR 0xFFFFFFFF80000000
section .multiboot_header
header_start:
    align 8
    dd 0xE85250D6
    dd 0
    dd header_end - header_start
    dd 0x100000000 - (0xE85250D6 + 0 + (header_end - header_start))
    dw 0
    dw 0
    dd 8
header_end:
section .multiboot.text
global start
bits 32
;functions
check_cpuid:
        pushfd
        pop eax
        mov ecx, eax
        xor eax, 1 << 21
        push eax
        popfd
        pushfd
        pop eax
        push ecx
        popfd
        xor eax, ecx
        jz .no_cpuid
        mov edi, cpuid_av - KERNEL_VIRTUAL_ADDR
        call print
        .cont:
        ret
.no_cpuid:
        mov edi, cpuid_err - KERNEL_VIRTUAL_ADDR
        call print
        jmp check_cpuid.cont

check_long_mode:
        mov eax, 0x80000000
        cpuid
        cmp eax, 0x80000001
        jb .no_long

        mov eax, 0x80000001
        cpuid
        test edx, 1 << 29
        jz .no_long

        mov edi, lm - KERNEL_VIRTUAL_ADDR
        call print
        .cont:
        ret
.no_long:
        mov edi, no_lm - KERNEL_VIRTUAL_ADDR
        call print
        jmp check_long_mode.cont

print:
    mov dh, 0x0f
    xor ecx, ecx
    mov dl, [edi + ecx]
    mov word [0xb8000 + esi*2], dx
    .loopx:
        inc ecx
        inc esi
        mov dl, [edi + ecx]
        mov word [0xb8000 + esi*2], dx
        cmp byte [edi + ecx], 0
        jnz .loopx
    ret
start:
    cmp eax, 0x36d76289
    je loader
loader:
    mov esp, stack.top - KERNEL_VIRTUAL_ADDR
    ;disable paging
    cli
    mov eax, cr0
    or eax, 0 << 31
    mov cr0, eax

    mov eax, cr4
    or eax, 1 << 5
    mov cr4, eax

    xor esi, esi
    call check_cpuid
    call check_long_mode
    mov eax, p3_table - KERNEL_VIRTUAL_ADDR
    or eax, 0b11
    ; Map the lower half addresses
    mov dword [p4_table - KERNEL_VIRTUAL_ADDR], eax
    ; Map the higher half addresses
    mov dword [p4_table+511*8 - KERNEL_VIRTUAL_ADDR], eax

    mov eax, p2_table - KERNEL_VIRTUAL_ADDR
    or eax, 0b11
    ; Map the lower half addresses
    mov dword [p3_table - KERNEL_VIRTUAL_ADDR], eax
    ; Map the higher half addresses
    mov dword [p3_table+510*8 - KERNEL_VIRTUAL_ADDR], eax
    mov eax, p1_table_1 - KERNEL_VIRTUAL_ADDR
    or eax, 0b11
    mov dword [p2_table - KERNEL_VIRTUAL_ADDR], eax

    mov eax, p1_table_1 - KERNEL_VIRTUAL_ADDR + 4096
    or eax, 0b11
    mov dword [p2_table - KERNEL_VIRTUAL_ADDR + 8], eax

    mov ecx, 0
    .map_p1_table:
        mov eax, 4096
        mul ecx
        or eax, 0b11
        mov [p1_table_1 - KERNEL_VIRTUAL_ADDR + ecx*8], eax
        inc ecx
        cmp ecx, 1024
        jne .map_p1_table

    mov eax, p4_table - KERNEL_VIRTUAL_ADDR
    mov cr3, eax

    mov ecx, 0xC0000080
    rdmsr
    or eax, (1 << 8)
    wrmsr

    mov ebx, cr0
    or ebx, 1 << 31
    mov cr0, ebx
    lgdt [gdt64.pointer_low - KERNEL_VIRTUAL_ADDR]

    ; We need to reload CS by a FAR JMP to the lower half label kernel_jumper
    jmp (0x8):(kernel_jumper)

[bits 64]
kernel_jumper:
    ; Jump to the higher half entry point kernel_jumper_high
    mov rax, kernel_jumper_high
    jmp rax

; Section .text has higher half addresses
section .text
kernel_jumper_high:
    ; Load the GDT from the higher half
    lgdt [gdt64.pointer]

    ; Set a higher half stack
    lea rsp, [stack.top]

    ; Initialize the segment registers to NULL segment
    xor eax, eax
    mov ds, eax
    mov es, eax
    mov ss, eax
    mov fs, eax
    mov gs, eax

    ; Remove the lower half page mappings
    mov rax, p4_table
    mov dword [rax], 0
    mov rax, p3_table
    mov dword [rax], 0

    ; Flush the TLB by reloading CR3
    mov rax, cr3
    mov cr3, rax

    ; Add higher half long mode code here

    ; Print HHLM to upper right of screen (white on magenta)
    lea rax, [0xb8000 + KERNEL_VIRTUAL_ADDR]
    mov word [rax+76*2], 0x57 << 8 | 'H'
    mov word [rax+77*2], 0x57 << 8 | 'H'
    mov word [rax+78*2], 0x57 << 8 | 'L'
    mov word [rax+79*2], 0x57 << 8 | 'M'

    ;
    ; Infinite loop
    .h:
        jmp .h


section .data
cpuid_err: db "CPUID:0 ", 0
cpuid_av: db "CPUID:1 ", 0
os_err: db " multiboot: ", 0
no_lm: db "long-mode: 0 ", 0
lm: db "long-mode: 1 ", 0
gdt64:
    dq  0   ;first entry = 0
    .code equ $ - gdt64
        ; equ tells the compiler to set the address of the variable at given address ($ - gdt64). $ is the current position.
        ; set the following values:
        ; descriptor type: bit 44 has to be 1 for code and data segments
        ; present: bit 47 has to be  1 if the entry is valid
        ; read/write: bit 41 1 means that is readable
        ; executable: bit 43 it has to be 1 for code segments
        ; 64bit: bit 53 1 if this is a 64bit gdt
        dq (1 <<44) | (1 << 47) | (1 << 41) | (1 << 43) | (1 << 53)  ;second entry=code=0x8
    .data equ $ - gdt64
        dq (1 << 44) | (1 << 47) | (1 << 41)    ;third entry = data = 0x10
.pointer:
    dw .pointer - gdt64 - 1
    dq gdt64
.pointer_low:
    dw .pointer - gdt64 - 1
    dq gdt64 - KERNEL_VIRTUAL_ADDR
section .bss
align 4096
p4_table:
        resb 4096
p3_table:
        resb 4096
p2_table:
        resb 4096
p1_table_1:
        resb 8192
align 16
stack:
    resb 16384
    .top:

精明的观察者可能已经注意到,上面代码中的页面映射标识映射了下半部分并创建了上半部分映射碰巧映射了2个附加区域。这没关系,因为当下半部分未映射时,额外的映射将会消失。初始映射实际上如下所示:

0x0000000000000000-0x00000000003fffff -> 0x000000000000-0x0000003fffff
0x0000007f80000000-0x0000007f803fffff -> 0x000000000000-0x0000003fffff
0xffffff8000000000-0xffffff80003fffff -> 0x000000000000-0x0000003fffff
0xffffffff80000000-0xffffffff803fffff -> 0x000000000000-0x0000003fffff

下半部分取消映射后,它应该如下所示:

0xffffffff80000000-0xffffffff803fffff -> 0x000000000000-0x0000003fffff

这没关系,因为额外的映射稍微简化了代码并且最终不会造成任何损害。

© www.soinside.com 2019 - 2024. All rights reserved.