【问题标题】:Triple fault when jumping to 64-bit longmode跳转到 64 位长模式时出现三重故障
【发布时间】:2017-09-12 07:07:39
【问题描述】:

以下从 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

当执行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找到

【问题讨论】:

  • 哇哦,有人真的在写cr 寄存器。尊重。
  • @harold 在 32 位保护模式下带有 FAR RET 一个 32 位 DWORD 将从堆栈中弹出到 CS 并丢弃前 16 位。然后将下一个 DWORD 弹出到 EIP 中。虽然我只是像你说的那样使用 JMP,但我不认为错误是用他的 FAR RET 构造返回地址。
  • 您有一个项目可以使用您的所有代码吗?由于您没有向我们展示一个最小的完整可验证示例,因此很难解决问题。
  • @MichaelPetch 一切都位于我朋友的 github 上,我们今天提交了最新的代码:github.com/cuaox/RIOS 他问我是否想帮忙,我正在尝试做一些分页,但是,有些事情是不工作

标签: assembly x86 x86-64 paging osdev


【解决方案1】:

主要问题是您的 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 位模式使用 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 提供有关引发的中断和异常的有用信息。

【讨论】:

    猜你喜欢
    • 2021-06-13
    • 2018-02-18
    • 1970-01-01
    • 1970-01-01
    • 2016-11-19
    • 2014-07-29
    • 2022-08-06
    • 1970-01-01
    • 2021-02-28
    相关资源
    最近更新 更多