Mar*_*Dem 5 paging x86 assembly x86-64 osdev
以下从 32 位保护模式(启用 A20)转换到 64 位长模式的代码似乎给我带来了问题。我将 1GiB 页面从 0x00000000 恒等映射到 0x3fffffff;启用 PAE;启用 EFER MSR 中的长模式位;安装 GDT;启用分页;然后对我的 64 位入口点进行模拟 FAR JMP:
lea eax, [PML4]
mov cr3, eax
mov eax, cr4
or eax, 100000b
mov cr4, eax
mov ecx, 0xc0000080
rdmsr
or eax, 100000000b
wrmsr
mov eax, cr0
mov ebx, 0x1
shl ebx, 31
or eax, ebx
mov cr0, eax
call gdt64_install
push 8
push longmode
retf ;<===================== faults here
Run Code Online (Sandbox Code Playgroud)
当执行指令时,程序在BOCHSRETF中出现三次错误,但似乎没有返回任何错误。如果我info tab在这次跳转之前输入,我会得到:
0x00000000-0x3fffffff -> 0x000000000000-0x00003fffffff
Run Code Online (Sandbox Code Playgroud)
在我看来,分页正在工作。这是sreg输出:
es:0x0010, dh=0x00cf9300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed
cs:0x0008, dh=0x00cf9b00, dl=0x0000ffff, valid=1
Code segment, base=0x00000000, limit=0xffffffff, Execute/Read, Non-Conforming, Accessed, 32-bit
ss:0x0010, dh=0x00cf9300, dl=0x0000ffff, valid=31
Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed
ds:0x0010, dh=0x00cf9300, dl=0x0000ffff, valid=31
Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed
fs:0x0000, dh=0x00009300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
gs:0x0000, dh=0x00009300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
ldtr:0x0000, dh=0x00008200, dl=0x0000ffff, valid=1
tr:0x0000, dh=0x00008b00, dl=0x0000ffff, valid=1
gdtr:base=0x0000000000008252, limit=0x1f
idtr:base=0x0000000000000000, limit=0x3ff
Run Code Online (Sandbox Code Playgroud)
我的GDT条目是:
gdt64_install:
lgdt[GDT_addr]
ret
GDT_addr:
dw (GDT64_end - GDT64) - 1
dd GDT64
GDT64:
dd 0, 0
dd 0xffff ; segment limit
dd 0xef9a00
dd 0xffff ; segment limit
dd 0xef9200
dd 0, 0
GDT64_end:
Run Code Online (Sandbox Code Playgroud)
我使用PML4和PDP 的页表结构定义为:
align 4096 ;;align to 4 KB
PML4:
dq 0 or 1b or 10b or PDP;;preset bit, r/w bit
dq 511 dup(PDP or 10b)
PDP:
dq 0 or 1b or 10000000b ;;dq zero, because we map memory from start so 0x0000, present bit
;;PDPE.PS to indicate 1gb pages
dq 511 dup(10000000b)
Run Code Online (Sandbox Code Playgroud)
你知道为什么它可能是三重故障吗?
主要问题是您的GDT似乎是按照 32 位设计的。对于 64 位描述符,您需要设置 64 位描述符位。从OSDev wiki我们可以看到 GDT 的布局以及标志和访问位:
正如 wiki 中所述,这些更改适用于 64 位描述符:
x86-64 的变化
- “L”位(位 21,“Sz”旁边)用于指示 x86-64 描述符
- 当设置“L”位时,“Sz”位(位 22)必须为 0,因为组合 Sz = 1、L = 1 保留供将来使用(如果尝试使用它,将引发异常)
出于性能原因,英特尔还建议将GDT在 8 字节边界上对齐。在 64 位描述符中,基数和限制应设置为 0。如果您打算稍后使用64 位模式的GDTdd GDT64表,您将需要更改为四字。考虑到这些事情,我修改了你的 GDT 以使其更具可读性:
GDT_addr:
dw (GDT64_end - GDT64) - 1
dq GDT64 ; Use quadword so we can use this GDT table
; from 64-bit mode if necessary
align 8 ; Intel suggests GDT should be 8 byte aligned
GDT64: ; Global Descriptor Table (64-bit).
; 64-bit descriptors should set all limit and base to 0
; NULL Descriptor
dw 0 ; Limit (low).
dw 0 ; Base (low).
db 0 ; Base (middle)
db 0 ; Access.
db 0 ; Flags.
db 0 ; Base (high).
; 64-bit Code descriptor
dw 0 ; Limit (low).
dw 0 ; Base (low).
db 0 ; Base (middle)
db 10011010b ; Access (present/exec/read).
db 00100000b ; Flags 64-bit descriptor
db 0 ; Base (high).
; 64-bit Data descriptor
dw 0 ; Limit (low).
dw 0 ; Base (low).
db 0 ; Base (middle)
db 10010010b ; Access (present/read&write).
db 00100000b ; Flags 64-bit descriptor.
db 0 ; Base (high).
GDT64_end:
Run Code Online (Sandbox Code Playgroud)
您可以使用它转换到 64 位长模式:
push 8
push longmode
retf
Run Code Online (Sandbox Code Playgroud)
虽然这有效,但如果您使用 FASM 或 NASM,并且仍处于 32 位模式,则使用FAR JMP会容易得多:
jmp 0x08:longmode
Run Code Online (Sandbox Code Playgroud)
在 64 位代码中执行一次FAR JMP存在问题,因为某些早期的 AMD64 处理器类型不支持JMP mem16:64。使用PUSH / RETF方法使得代码更加通用。仅在极少数情况下才会在 64 位长模式下执行一次此类FAR JMP 。
您的代码中还有一个关于读取扇区的问题。我发现并非所有代码和数据都被读入内存。在你的exread.inc定义中:
SECTOREAD equ 20
Run Code Online (Sandbox Code Playgroud)
我发现当我构建软盘映像时,文件大小为 13976。即 28 个扇区(512*28=14336)。你的阅读价值20还不够。确保这对您来说不是问题,如果需要,请阅读更多扇区。
与当前的问题无关,我注意到您Makefile有:
qrun: deploy_all
qemu-system-i386 kernel.bin
Run Code Online (Sandbox Code Playgroud)
如果您想在QEMU中运行 64 位代码,您需要使用qemu-system-x86_64not qemu-system-i386。我发现这个更有用:
qrun: deploy_all
qemu-system-x86_64 -fda floppy.bin -no-shutdown -no-reboot -d int
Run Code Online (Sandbox Code Playgroud)
这些-no-shutdown -no-reboot -d int选项对于调试很有用。它将导致QEMU在三重故障时无法重新启动和关闭。-d int提供有关引发的中断和异常的有用信息。