为什么我的代码在 x86-64 上导致三重故障?

Why is my code causing a triple fault on x86-64?

我有一个用 UEFI 启动的小型 x86-64 内核。起初,我直接在 0x400000 加载内核,但后来我想拥有更高的一半内核,因为我觉得这是做事的正确方法。我决定采用将内核分成两部分的策略。一个叫做 startup.elf(启动代码),一个叫做 kernel.elf(主内核)。启动代码应该为前 4MB 设置一个标识分页,为更高的一半内核设置分页结构。我计划将 0x8000_0000_0000 映射到 0 并从那里映射所有 RAM。

我之前有设置 GDT 的代码和远 return 设置 CS(我在这里使用的代码:)。我也有设置分页的代码。一切都运行良好,我正在努力设置 xHC。它正在按预期触发中断。

现在我的代码不起作用,并且出现三重错误。我不知道为什么,但我认为这与分页有关。我提供了一个非常简短的最小示例。它是用 UEFI 启动的,为了简洁起见,我不会在这里分享启动代码。我知道跳转到代码是有效的,因为我可以按照代码的方式停止处理器。

typedef unsigned char UINT8;
typedef unsigned short UINT16;
typedef unsigned int UINT32;
typedef unsigned long UINT64;

struct GDT{
    UINT64 nullDescriptor;
    
    UINT16 codeLimit;
    UINT16 codeBaseLow;
    UINT8 codeBaseMid;
    UINT8 codeFlags;
    UINT8 codeLimitMid;
    UINT8 codeBaseHigh;
    
    UINT16 dataLimit;
    UINT16 dataBaseLow;
    UINT8 dataBaseMid;
    UINT8 dataFlags;
    UINT8 dataLimitMid;
    UINT8 dataBaseHigh;
}__attribute__((packed));

struct GDTR{
    UINT16 size;
    GDT* address;
}__attribute__((packed));

void main(){
    //Identity mapping
    UINT64* pml4Ptr = (UINT64*)0x200000;
    *pml4Ptr = 0x20101b;
    
    UINT64* pdpPtr = (UINT64*)0x201000;
    *pdpPtr = 0x20201b;
    
    UINT64* pdPtr = (UINT64*)0x202000;
    *pdPtr = 0x20301b;
    *(pdPtr + 1) = 0x20401b;
    
    UINT64* ptPtr = (UINT64*)0x203000;
    UINT64 physAddr = 0x1b;
    for (UINT32 i = 0; i < 2 * 512; i++){
        *(ptPtr + i) = physAddr;
        physAddr += 0x1000;
    }

    asm volatile(
    "movq [=10=]x200018, %rax\n\t"
    "mov %rax, %cr3\n\t"
    );
        
    GDT gdt = {
        .nullDescriptor = 0,
        
        .codeLimit = 0x0000,
        .codeBaseLow = 0,
        .codeBaseMid = 0,
        .codeFlags = 0x9a,
        .codeLimitMid = 0xaf,
        .codeBaseHigh = 0,
        
        .dataLimit = 0x0000,
        .dataBaseLow = 0,
        .dataBaseMid = 0,
        .dataFlags = 0x92,
        .dataLimitMid = 0x00,
        .dataBaseHigh = 0
    };
    
    GDT* gdtAddr = &gdt;
    GDTR gdtr = { 23, gdtAddr };
    GDTR* gdtrAddr = &gdtr;
    
    asm volatile("lgdt (%0)" : : "r"(gdtrAddr));
    
    asm volatile(
    "movq [=10=]x400000, %rsp\n\t"
    "sub , %rsp\n\t"
    "movq , 8(%rsp)\n\t"
    "movabsq $fun, %rax\n\t"
    "mov %rax, (%rsp)\n\t"
    "lretq\n\t"
    "fun:\n\t"
    "movq [=10=]x10, %rax\n\t"
    "mov %ax, %ss\n\t"
    "mov %ax, %es\n\t"
    "mov %ax, %ds\n\t"
    "mov %ax, %gs\n\t"
    "mov %ax, %fs\n\t"
    "hlt"
    );
}

代码三重错误,我不知道为什么。它以前工作过。我尝试在加载 CR3 寄存器后放置一条 hlt 指令,它正确地停止了。每当我尝试将一个值写入内存时,它都会出现三重错误(可能是页面错误或其他)。我不明白为什么。我想我需要第二双眼睛看这个。

我使用 monitor info mem 打印虚拟内存映射并输出正确的内容(我在 QEMU 上测试代码并使用 GDB 调试)。我也试了 monitor info tlb 似乎是正确的。这是输出:

(gdb) monitor info mem
0000000000000000-0000000000400000 0000000000400000 -rw
(gdb) monitor info tlb
0000000000000000: 0000000000000000 -----CT-W
0000000000001000: 0000000000001000 -----CT-W
0000000000002000: 0000000000002000 -----CT-W
0000000000003000: 0000000000003000 -----CT-W
0000000000004000: 0000000000004000 -----CT-W
0000000000005000: 0000000000005000 -----CT-W
0000000000006000: 0000000000006000 -----CT-W
0000000000007000: 0000000000007000 -----CT-W
0000000000008000: 0000000000008000 -----CT-W
0000000000009000: 0000000000009000 -----CT-W
000000000000a000: 000000000000a000 -----CT-W
000000000000b000: 000000000000b000 -----CT-W
000000000000c000: 000000000000c000 -----CT-W
000000000000d000: 000000000000d000 -----CT-W
000000000000e000: 000000000000e000 -----CT-W
000000000000f000: 000000000000f000 -----CT-W
0000000000010000: 0000000000010000 -----CT-W
0000000000011000: 0000000000011000 -----CT-W
0000000000012000: 0000000000012000 -----CT-W
0000000000013000: 0000000000013000 -----CT-W
0000000000014000: 0000000000014000 -----CT-W
0000000000015000: 0000000000015000 -----CT-W
0000000000016000: 0000000000016000 -----CT-W
0000000000017000: 0000000000017000 -----CT-W
0000000000018000: 0000000000018000 -----CT-W
0000000000019000: 0000000000019000 -----CT-W
000000000001a000: 000000000001a000 -----CT-W
000000000001b000: 000000000001b000 -----CT-W
000000000001c000: 000000000001c000 -----CT-W
000000000001d000: 000000000001d000 -----CT-W
000000000001e000: 000000000001e000 -----CT-W
000000000001f000: 000000000001f000 -----CT-W
0000000000020000: 0000000000020000 -----CT-W
0000000000021000: 0000000000021000 -----CT-W
0000000000022000: 0000000000022000 -----CT-W
0000000000023000: 0000000000023000 -----CT-W
0000000000024000: 0000000000024000 -----CT-W
0000000000025000: 0000000000025000 -----CT-W
0000000000026000: 0000000000026000 -----CT-W
0000000000027000: 0000000000027000 -----CT-W
0000000000028000: 0000000000028000 -----CT-W
0000000000029000: 0000000000029000 -----CT-W
000000000002a000: 000000000002a000 -----CT-W
000000000002b000: 000000000002b000 -----CT-W
000000000002c000: 000000000002c000 -----CT-W
000000000002d000: 000000000002d000 -----CT-W
000000000002e000: 000000000002e000 -----CT-W
000000000002f000: 000000000002f000 -----CT-W
0000000000030000: 0000000000030000 -----CT-W
0000000000031000: 0000000000031000 -----CT-W
0000000000032000: 0000000000032000 -----CT-W
0000000000033000: 0000000000033000 -----CT-W
0000000000034000: 0000000000034000 -----CT-W
0000000000035000: 0000000000035000 -----CT-W
0000000000036000: 0000000000036000 -----CT-W
0000000000037000: 0000000000037000 -----CT-W
0000000000038000: 0000000000038000 -----CT-W
0000000000039000: 0000000000039000 -----CT-W
000000000003a000: 000000000003a000 -----CT-W
000000000003b000: 000000000003b000 -----CT-W
000000000003c000: 000000000003c000 -----CT-W
000000000003d000: 000000000003d000 -----CT-W
000000000003e000: 000000000003e000 -----CT-W
000000000003f000: 000000000003f000 -----CT-W
0000000000040000: 0000000000040000 -----CT-W
0000000000041000: 0000000000041000 -----CT-W
0000000000042000: 0000000000042000 -----CT-W
0000000000043000: 0000000000043000 -----CT-W
0000000000044000: 0000000000044000 -----CT-W
0000000000045000: 0000000000045000 -----CT-W
0000000000046000: 0000000000046000 -----CT-W
0000000000047000: 0000000000047000 -----CT-W
0000000000048000: 0000000000048000 -----CT-W
0000000000049000: 0000000000049000 -----CT-W
000000000004a000: 000000000004a000 -----CT-W
000000000004b000: 000000000004b000 -----CT-W
000000000004c000: 000000000004c000 -----CT-W
000000000004d000: 000000000004d000 -----CT-W
000000000004e000: 000000000004e000 -----CT-W
000000000004f000: 000000000004f000 -----CT-W
0000000000050000: 0000000000050000 -----CT-W
0000000000051000: 0000000000051000 -----CT-W
0000000000052000: 0000000000052000 -----CT-W
0000000000053000: 0000000000053000 -----CT-W
0000000000054000: 0000000000054000 -----CT-W
0000000000055000: 0000000000055000 -----CT-W
0000000000056000: 0000000000056000 -----CT-W
0000000000057000: 0000000000057000 -----CT-W
0000000000058000: 0000000000058000 -----CT-W
0000000000059000: 0000000000059000 -----CT-W
000000000005a000: 000000000005a000 -----CT-W
000000000005b000: 000000000005b000 -----CT-W
000000000005c000: 000000000005c000 -----CT-W
000000000005d000: 000000000005d000 -----CT-W
000000000005e000: 000000000005e000 -----CT-W
000000000005f000: 000000000005f000 -----CT-W
0000000000060000: 0000000000060000 -----CT-W
0000000000061000: 0000000000061000 -----CT-W
0000000000062000: 0000000000062000 -----CT-W
0000000000063000: 0000000000063000 -----CT-W
0000000000064000: 0000000000064000 -----CT-W
0000000000065000: 0000000000065000 -----CT-W
0000000000066000: 0000000000066000 -----CT-W
0000000000067000: 0000000000067000 -----CT-W
0000000000068000: 0000000000068000 -----CT-W
0000000000069000: 0000000000069000 -----CT-W
000000000006a000: 000000000006a000 -----CT-W
000000000006b000: 000000000006b000 -----CT-W
000000000006c000: 000000000006c000 -----CT-W
000000000006d000: 000000000006d000 -----CT-W
000000000006e000: 000000000006e000 -----CT-W
000000000006f000: 000000000006f000 -----CT-W
0000000000070000: 0000000000070000 -----CT-W
0000000000071000: 0000000000071000 -----CT-W
0000000000072000: 0000000000072000 -----CT-W
0000000000073000: 0000000000073000 -----CT-W
0000000000074000: 0000000000074000 -----CT-W
0000000000075000: 0000000000075000 -----CT-W
0000000000076000: 0000000000076000 -----CT-W
0000000000077000: 0000000000077000 -----CT-W
0000000000078000: 0000000000078000 -----CT-W
0000000000079000: 0000000000079000 -----CT-W
000000000007a000: 000000000007a000 -----CT-W
000000000007b000: 000000000007b000 -----CT-W
000000000007c000: 000000000007c000 -----CT-W
000000000007d000: 000000000007d000 -----CT-W
000000000007e000: 000000000007e000 -----CT-W
000000000007f000: 000000000007f000 -----CT-W
0000000000080000: 0000000000080000 -----CT-W
0000000000081000: 0000000000081000 -----CT-W
0000000000082000: 0000000000082000 -----CT-W
0000000000083000: 0000000000083000 -----CT-W
0000000000084000: 0000000000084000 -----CT-W
0000000000085000: 0000000000085000 -----CT-W
0000000000086000: 0000000000086000 -----CT-W
0000000000087000: 0000000000087000 -----CT-W
0000000000088000: 0000000000088000 -----CT-W
0000000000089000: 0000000000089000 -----CT-W
000000000008a000: 000000000008a000 -----CT-W
000000000008b000: 000000000008b000 -----CT-W
000000000008c000: 000000000008c000 -----CT-W
000000000008d000: 000000000008d000 -----CT-W
000000000008e000: 000000000008e000 -----CT-W
000000000008f000: 000000000008f000 -----CT-W
0000000000090000: 0000000000090000 -----CT-W
0000000000091000: 0000000000091000 -----CT-W
0000000000092000: 0000000000092000 -----CT-W
0000000000093000: 0000000000093000 -----CT-W
0000000000094000: 0000000000094000 -----CT-W
0000000000095000: 0000000000095000 -----CT-W
0000000000096000: 0000000000096000 -----CT-W
0000000000097000: 0000000000097000 -----CT-W
0000000000098000: 0000000000098000 -----CT-W
0000000000099000: 0000000000099000 -----CT-W
000000000009a000: 000000000009a000 -----CT-W
000000000009b000: 000000000009b000 -----CT-W
000000000009c000: 000000000009c000 -----CT-W
000000000009d000: 000000000009d000 -----CT-W
000000000009e000: 000000000009e000 -----CT-W
000000000009f000: 000000000009f000 -----CT-W
00000000000a0000: 00000000000a0000 -----CT-W
00000000000a1000: 00000000000a1000 -----CT-W
00000000000a2000: 00000000000a2000 -----CT-W
00000000000a3000: 00000000000a3000 -----CT-W
00000000000a4000: 00000000000a4000 -----CT-W
00000000000a5000: 00000000000a5000 -----CT-W
00000000000a6000: 00000000000a6000 -----CT-W
00000000000a7000: 00000000000a7000 -----CT-W
00000000000a8000: 00000000000a8000 -----CT-W
00000000000a9000: 00000000000a9000 -----CT-W
00000000000aa000: 00000000000aa000 -----CT-W
00000000000ab000: 00000000000ab000 -----CT-W
00000000000ac000: 00000000000ac000 -----CT-W
00000000000ad000: 00000000000ad000 -----CT-W
00000000000ae000: 00000000000ae000 -----CT-W
00000000000af000: 00000000000af000 -----CT-W
00000000000b0000: 00000000000b0000 -----CT-W
00000000000b1000: 00000000000b1000 -----CT-W
00000000000b2000: 00000000000b2000 -----CT-W
00000000000b3000: 00000000000b3000 -----CT-W
00000000000b4000: 00000000000b4000 -----CT-W
00000000000b5000: 00000000000b5000 -----CT-W
00000000000b6000: 00000000000b6000 -----CT-W
00000000000b7000: 00000000000b7000 -----CT-W
00000000000b8000: 00000000000b8000 -----CT-W
00000000000b9000: 00000000000b9000 -----CT-W
00000000000ba000: 00000000000ba000 -----CT-W
00000000000bb000: 00000000000bb000 -----CT-W
00000000000bc000: 00000000000bc000 -----CT-W
00000000000bd000: 00000000000bd000 -----CT-W
00000000000be000: 00000000000be000 -----CT-W
00000000000bf000: 00000000000bf000 -----CT-W
00000000000c0000: 00000000000c0000 -----CT-W
00000000000c1000: 00000000000c1000 -----CT-W
00000000000c2000: 00000000000c2000 -----CT-W
00000000000c3000: 00000000000c3000 -----CT-W
00000000000c4000: 00000000000c4000 -----CT-W
00000000000c5000: 00000000000c5000 -----CT-W
00000000000c6000: 00000000000c6000 -----CT-W
00000000000c7000: 00000000000c7000 -----CT-W
00000000000c8000: 00000000000c8000 -----CT-W
00000000000c9000: 00000000000c9000 -----CT-W
00000000000ca000: 00000000000ca000 -----CT-W
00000000000cb000: 00000000000cb000 -----CT-W
...
etc

知道哪里出了问题吗?

此外,我用这个脚本编译代码:

g++ -static -ffreestanding -nostdlib -mgeneral-regs-only -mno-red-zone -c -m64 Startup/Source/Main.cpp -oStartup/Object/Main.o
ld -entry main --oformat elf64-x86-64 --no-dynamic-linker -static -nostdlib -Ttext-segment=300000 Startup/Object/Main.o -ostartup.elf

编辑

我认为罪魁祸首是 g++ 在堆栈上分配变量。当我退出 UEFI 时,堆栈指针指向 4MB 以上。然后我设置 CR3,当使用堆栈时,它在未处理页面错误后出现三重错误。

如何解决这个问题?

UEFI 不一定会在链接到的位置准确加载您的执行,因此您的内核可能完全在内存中的其他地方,因此在您加载 cr3 时未映射。在可执行文件中使用符号代替常量来映射内存。

最后,代码有几处错误。

首先,我不得不在g++编译命令中使用-fomit-frame-pointer来避免代码使用帧指针(rbp)。我在这里找到了一篇关于帧指针的好文章:https://people.cs.rutgers.edu/~pxk/419/notes/frames.html。本文陈述如下:

Here’s what happens during function (there might be slight differences among languages/architectures)

  1. Push the current value of the frame pointer (ebp/rbp). This saves it so we can restore it later.

  2. Move the current stack pointer to the frame pointer. This defines the start of the frame.

  3. Subtract the space needed for the function’s data from the stack pointer. Remember that stacks grow from high memory to low memory. This puts the stack pointer past the space that will be used by the function so that anything pushed onto the stack now will not overwrite useful values.

  4. Now execute the code for the function. References to local variables will be negative offsets to the frame pointer (e.g., "movl 3, –8(%rbp)”).

  5. On exit from the function, copy the value from the frame pointer to the stack pointer (this clears up the space allocated to the stack frame for the function) and pop the old frame pointer. This is accomplished by the “leave” instruction.

  6. Return from the procedure via a “ret” instruction. This pops the return value from the stack and transfers execution to that address.

这里的关键点是第 4 点。如果没有 -fomit-frame-pointer,该函数将引用与帧指针有负偏移的局部变量。当我设置一个新堆栈时,该函数仍将使用接近旧堆栈的旧 RBP 值。该函数将在编译期间分配一定数量的堆栈。然后 RSP 将被复制到 RBP 并且函数中的所有内容都只是相对于 RBP 的偏移量。然后 RSP 将递减到刚好低于分配的值 space。当我修改堆栈时,RBP 值没有被修改。一种解决方案是修改它,但我认为更好的方法是不使用 RBP 并完全省略帧指针。

此外,我首先将堆栈放在 0x400000 处。这也是错误的。好像不是因为栈向下增长。这是错误的,因为当您省略帧指针时,该函数仍会为该函数递减已分配堆栈的 RSP space。然后不使用 RBP 的负偏移量,而是使用 RSP 的正偏移量。当我第一次进入该函数时,RSP 递减,然后如果我将堆栈指针设置为 0x400000,则会发生页面错误,因为函数数据的偏移量将从 0x400000 开始为正偏移量。因为我只映射了 4MB,所以我最终位于映射区域之上,它触发了一个页面错误,归结为三重错误,没有适当的处理。相反,我应该将堆栈指针放低,这应该可以正常工作。

有效的代码如下:

typedef unsigned char UINT8;
typedef unsigned short UINT16;
typedef unsigned int UINT32;
typedef unsigned long UINT64;

struct GDT{
    UINT64 nullDescriptor;
    
    UINT16 codeLimit;
    UINT16 codeBaseLow;
    UINT8 codeBaseMid;
    UINT8 codeFlags;
    UINT8 codeLimitMid;
    UINT8 codeBaseHigh;
    
    UINT16 dataLimit;
    UINT16 dataBaseLow;
    UINT8 dataBaseMid;
    UINT8 dataFlags;
    UINT8 dataLimitMid;
    UINT8 dataBaseHigh;
}__attribute__((packed));

struct GDTR{
    UINT16 size;
    GDT* address;
}__attribute__((packed));

void main(){
    //Identity mapping
    UINT64* pml4Ptr = (UINT64*)0x200000;
    *pml4Ptr = 0x20101b;
    
    UINT64* pdpPtr = (UINT64*)0x201000;
    *pdpPtr = 0x20201b;
    
    UINT64* pdPtr = (UINT64*)0x202000;
    *pdPtr = 0x20301b;
    *(pdPtr + 1) = 0x20401b;
    
    UINT64* ptPtr = (UINT64*)0x203000;
    UINT64 physAddr = 0x1b;
    for (UINT32 i = 0; i < 2 * 512; i++){
        *(ptPtr + i) = physAddr;
        physAddr += 0x1000;
    }

    asm volatile(
    "movq [=10=]x200018, %rax\n\t"
    "mov %rax, %cr3\n\t"
    "movq [=10=]x350000, %rsp"
    );
        
    GDT gdt = {
        .nullDescriptor = 0,
        
        .codeLimit = 0x0000,
        .codeBaseLow = 0,
        .codeBaseMid = 0,
        .codeFlags = 0x9a,
        .codeLimitMid = 0xaf,
        .codeBaseHigh = 0,
        
        .dataLimit = 0x0000,
        .dataBaseLow = 0,
        .dataBaseMid = 0,
        .dataFlags = 0x92,
        .dataLimitMid = 0x00,
        .dataBaseHigh = 0
    };
    
    GDT* gdtAddr = &gdt;
    GDTR gdtr = { 23, gdtAddr };
    GDTR* gdtrAddr = &gdtr;
    
    asm volatile("lgdt (%0)" : : "r"(gdtrAddr));
    
    asm volatile(
    "sub , %rsp\n\t"
    "movq , 8(%rsp)\n\t"
    "movabsq $fun, %rax\n\t"
    "mov %rax, (%rsp)\n\t"
    "lretq\n\t"
    "fun:\n\t"
    "movq [=10=]x10, %rax\n\t"
    "mov %ax, %ss\n\t"
    "mov %ax, %es\n\t"
    "mov %ax, %ds\n\t"
    "mov %ax, %gs\n\t"
    "mov %ax, %fs\n\t"
    "hlt"
    );
}