Тройная ошибка при переходе в длинный режим x86_64

У меня хобби-ОС, и я хочу, чтобы она перешла в 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-й мегабайт, код не будет получен до этого, моя запись в ОС составляет 2 МБ.

Из-за отсутствия дополнительной информации я считаю, что jmp (0x8):(kernel_jumper - KERNEL_VIRTUAL_ADDR) должно было быть jmp (0x8):(kernel_jumper)

Michael Petch 05.05.2024 08:02

@MichaelPetch он не может перейти к 64-битному верхнему адресу из 32-битного кода (я думаю, даже с префиксами), поэтому имеет смысл сначала выполнить переход к 64-битному младшему коду, а затем к конечному пункту назначения. Еще две (с половиной) вещи здесь кажутся неправильными. Во-первых, код подкачки просто выполняет сопоставление идентификаторов первых 4 МБ адресного пространства. Не существует верхнего адреса, на который можно было бы перейти, даже если бы он туда приземлился. Во-вторых, не туда попало — RIP действительно странный, ни низкий, ни высокий (как будто 32-битный урезанный). В-третьих, этот «jmp.h» должен быть абсолютным, но так ли это? Как ассемблер узнает, что это так?

Andrey Turkin 05.05.2024 09:16

Пожалуйста, обрежьте свой код, чтобы облегчить поиск проблемы. Следуйте этим рекомендациям, чтобы создать минимально воспроизводимый пример.

Community 05.05.2024 17:35
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
3
97
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Увидев ваш скрипт компоновщика, я могу подтвердить, что проблема в этом FAR JMP для метки kernel_jumper:

    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)

В информации об отладке есть подсказка, где она отображается RIP=000000008020015b. Это не ни первые 4 МБ, ни верхняя половина адресного пространства KERNEL_VIRTUAL_ADDR. Похоже, что этот адрес был вычислен как 0x20015b — 0xFFFFFFFF80000000 = 0x8020015B.


Я считаю, что это выходит за рамки того, о чем спрашивают, но код является неполным, поскольку в нем не сопоставлены адреса старшей половины и он не переходит от нижней половины к старшей половине. Следующий код добавляет дополнительную поддержку для обработки сопоставления старших половинных адресов; переход от нижней половины к высшей половине; удаление отображения нижней половины; перезагрузка ГДТ из верхней половины; и установка регистров сегмента:

%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:

Я добавил соответствующие комментарии в код, где внес изменения.

Проницательный наблюдатель, возможно, заметил, что сопоставления страниц в приведенном выше коде Identity отображают нижнюю половину; отображает верхнюю половину; и случается, что он отображает еще 2 региона. Это нормально, поскольку лишние сопоставления исчезнут, когда нижняя половина будет отключена. Первоначальные сопоставления на самом деле выглядят так:

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

После того, как нижняя половина будет отключена, она должна выглядеть так:

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

Это нормально, поскольку дополнительные сопоставления немного упрощают код и в конечном итоге не приносят вреда.

Ах, это правда; одна из этих вещей не похожа на другие. kernel_jumper все еще находится в разделе мультизагрузки и привязан к своему фактическому физическому местоположению, в отличие от всех данных. Я как бы предположил, что все было связано с его верхним расположением, включая весь код мультизагрузки, и код просто работал на низком уровне, потому что все ветки были связаны с компьютером. Особая благодарность за то, что вы приложили дополнительные усилия и заполнили код!

Andrey Turkin 06.05.2024 06:49

Другие вопросы по теме