跳转到64位长模时的三重错误

时间:2017-04-16 14:38:20

标签: assembly x86 x86-64 paging osdev

以下从32位保护模式(启用A20)转换为64位长模式的代码似乎给了我一些问题。我将1GiB页面的身份映射为0x00000000到0x3fffffff;启用PAE;启用EFER MSR中的longmode位;安装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

执行RETF指令但 BOCHS 中的程序三重故障但似乎没有返回任何错误。如果我在跳转之前输入info tab,我会得到:

0x00000000-0x3fffffff -> 0x000000000000-0x00003fffffff

在我看来,分页工作正常。这是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:

使用PML4 and 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)

为什么它可能是三重断层的任何想法?

我的项目副本可以在Github

找到

1 个答案:

答案 0 :(得分:2)

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

enter image description here

enter image description here

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

  

x86-64更改

     
      
  • 'L'位(第21位,“Sz”旁边)用于表示x86-64描述符
  •   
  • 当'L'位置1时,'sz'位(位22)必须为0,因为组合Sz = 1,L = 1保留供将来使用(如果你尝试使用将抛出异常)它)
  •   

出于性能原因,英特尔还建议在8字节边界上对齐 GDT 。在64位描述符中,base和limit应设置为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位模式,则使用FAR JMP要容易得多:

jmp 0x08:longmode

在64位代码中执行 FAR JMP 时出现问题,因为有些early AMD64 processor types不支持JMP mem16:64。使用 PUSH / RETF 方法可以使代码更加通用。在64位长模式下执行一次 FAR 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提供有关抛出的中断和异常的有用信息。