Assembly 跳至64位长模式时出现三重故障

Assembly 跳至64位长模式时出现三重故障,assembly,x86,x86-64,paging,osdev,Assembly,X86,X86 64,Paging,Osdev,下面的代码从32位保护模式(启用A20)转换为64位长模式,这似乎给我带来了一些问题。I标识将1GiB页面从0x00000000映射到0x3fffffff;启用PAE;在EFER MSR中启用长模式位;安装GDT;启用分页;然后对我的64位入口点执行模拟远JMP: lea eax, [PML4] mov cr3, eax mov eax, cr4 or eax, 100000b mov cr4, eax mov ecx, 0xc0000080 rdmsr or eax, 100000000b

下面的代码从32位保护模式(启用A20)转换为64位长模式,这似乎给我带来了一些问题。I标识将1GiB页面从0x00000000映射到0x3fffffff;启用PAE;在EFER MSR中启用长模式位;安装GDT;启用分页;然后对我的64位入口点执行模拟远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
在我看来,寻呼正在工作。这是
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
我的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:
使用的“我的页面”表结构定义为:

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)
你知道为什么会有三重断层吗


可以在

上找到我的项目的副本。主要问题是,您的GDT似乎是在设计时考虑了32位的。对于64位描述符,您需要设置64位描述符位。从中我们可以看到GDT的布局以及标志和访问位:

如wiki中所述,这些更改适用于64位描述符:

x86-64更改

  • “L”位(第21位,“Sz”旁边)用于表示x86-64描述符
  • 设置“L”位时,“Sz”位(第22位)必须为0,因为组合Sz=1,L=1保留供将来使用(如果尝试使用它,将引发异常)
出于性能原因,英特尔还建议在8字节边界上对齐GDT。在64位描述符中,基数和限制应设置为0。如果您打算以后从64位模式使用GDT表,您需要将
dd 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:

其他意见 您可以使用此选项转换为64位长模式:

push 8
push longmode
retf
虽然这是可行的,但如果您使用的是FASM或NASM,则如果您仍处于32位模式,则更容易使用:

jmp 0x08:longmode
在64位代码中执行一次FAR JMP有一个问题,因为有些代码不支持。使用PUSH/RETF方法使代码更加通用。在64位长模式下执行一次如此远的JMP只能在极少数情况下使用


关于读取扇区,代码中还有另一个问题。我发现并不是所有的代码和数据都被读入内存。在您的
exread.inc
中定义:

SECTOREAD equ 20
我发现,当我构建您的软盘映像时,文件大小是13976。这是28个扇区的价值(512*28=14336)。您的
20
值读数不足。确保这不是您的问题,如果需要,请阅读更多扇区(如果必要)


与手头的问题无关,我注意到在您的
Makefile
中您有:

qrun: deploy_all
    qemu-system-i386 kernel.bin
如果要在QEMU中运行64位代码,则需要使用
QEMU-system-x86_64
而不是
QEMU-system-i386
。我发现这更有用:

qrun: deploy_all
    qemu-system-x86_64 -fda floppy.bin -no-shutdown -no-reboot -d int

-no shutdown-no reboot-d int
选项对于调试非常有用。这将导致QEMU在出现三重故障时不重新启动和关闭
-d int
提供了有关抛出的中断和异常的有用信息。

噢,哇,有人真的在向
cr
寄存器写入数据。尊重。@harold在32位保护模式下使用远RET时,32位DWORD将从堆栈中弹出到CS中,并丢弃前16位。然后,下一个DWORD被弹出到EIP中。虽然我只会像你说的那样使用JMP,但我不认为这个bug是用ReaveReT的返回地址的构造。你有一个你可以用你的代码来完成的项目吗?因为你没有给我们提供一个最小的完整的可验证示例,所以很难排除故障。@MichaelPetch everything位于我朋友的github,我们今天提交了最新的代码:他问我是否需要帮助,我正在尝试进行一些分页,但有些东西不起作用
qrun: deploy_all
    qemu-system-x86_64 -fda floppy.bin -no-shutdown -no-reboot -d int