У меня хобби-ОС, и я хочу, чтобы она перешла в 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 МБ.
@MichaelPetch он не может перейти к 64-битному верхнему адресу из 32-битного кода (я думаю, даже с префиксами), поэтому имеет смысл сначала выполнить переход к 64-битному младшему коду, а затем к конечному пункту назначения. Еще две (с половиной) вещи здесь кажутся неправильными. Во-первых, код подкачки просто выполняет сопоставление идентификаторов первых 4 МБ адресного пространства. Не существует верхнего адреса, на который можно было бы перейти, даже если бы он туда приземлился. Во-вторых, не туда попало — RIP действительно странный, ни низкий, ни высокий (как будто 32-битный урезанный). В-третьих, этот «jmp.h» должен быть абсолютным, но так ли это? Как ассемблер узнает, что это так?
Пожалуйста, обрежьте свой код, чтобы облегчить поиск проблемы. Следуйте этим рекомендациям, чтобы создать минимально воспроизводимый пример.
Увидев ваш скрипт компоновщика, я могу подтвердить, что проблема в этом 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 все еще находится в разделе мультизагрузки и привязан к своему фактическому физическому местоположению, в отличие от всех данных. Я как бы предположил, что все было связано с его верхним расположением, включая весь код мультизагрузки, и код просто работал на низком уровне, потому что все ветки были связаны с компьютером. Особая благодарность за то, что вы приложили дополнительные усилия и заполнили код!
Из-за отсутствия дополнительной информации я считаю, что
jmp (0x8):(kernel_jumper - KERNEL_VIRTUAL_ADDR)
должно было бытьjmp (0x8):(kernel_jumper)