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

以下从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中的程序三重故障但似乎没有返回任何错误.如果我在此跳转之前键入信息选项卡,我会得到:

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找到

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

《assembly – 跳转到64位长模时的三重错误》

《assembly – 跳转到64位长模时的三重错误》

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

x86-64 Changes

  • ‘L’ bit (bit 21, next to ‘Sz’) is used to indicate x86-64 descriptor
  • ‘Sz’ bit (bit 22) has to be 0 when the ‘L’ bit is set, as the combination Sz = 1, L = 1 is reserved for future use (and will throw an exception if you try to use it)

出于性能原因,英特尔还建议将GDT在8字节边界上对齐.在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提供有关抛出的中断和异常的有用信息.

点赞