【问题标题】:Why does my inline assembly code cause a triple fault?为什么我的内联汇编代码会导致三重错误?
【发布时间】:2019-12-28 22:35:07
【问题描述】:

我使用带有-masm=intel 选项的GCC 编译我的代码。我的内核是由像 GRUB 这样的 Multiboot 加载程序加载的。

我想加载我的 GDT 的地址,然后我重新加载所有段寄存器,但这会导致三重错误(虚拟机重新启动)。如果我在本机程序集中(在 .asm 文件中)使用此代码,则此代码有效。

gdt.c:

#include "gdt.h"

GDT gdtp;

uint64_t gdt[GDT_ENTRIES];

void set_gdt_entry(int x, unsigned int base, unsigned int limit, int flags) {
    gdt[x] = limit & 0xffffLL;
    gdt[x] |= (base & 0xffffffLL) << 16;
    gdt[x] |= ((flags >> 4) & 0xffLL) << 40;
    gdt[x] |= ((limit >> 16) & 0xfLL) << 48;
    gdt[x] |= (flags & 0xfLL) << 52;
    gdt[x] |= ((base >> 24) & 0xffLL) << 56;
}

void gdt_init() {
    gdtp.limit = GDT_ENTRIES * 8 - 1;
    gdtp.pointer = gdt;

    set_gdt_entry(0, 0, 0, 0);
    set_gdt_entry(1, 0, 0xFFFFFFFF, GDT_SIZE | GDT_EXECUTABLE | GDT_SEGMENT | GDT_PRESENT);
    set_gdt_entry(2, 0, 0xFFFFFFFF, GDT_SIZE | GDT_SEGMENT | GDT_PRESENT);
    set_gdt_entry(3, 0, 0xFFFFFFFF, GDT_SIZE | GDT_EXECUTABLE | GDT_SEGMENT | GDT_PRESENT | GDT_RING3);
    set_gdt_entry(4, 0, 0xFFFFFFFF, GDT_SIZE | GDT_SEGMENT | GDT_PRESENT | GDT_RING3);

    asm volatile(
        "lgdt %0\n"
        "mov eax, 0x10\n"
        "mov ss, eax\n"
        "mov es, eax\n"
        "mov ds, eax\n"
        "mov gs, eax\n"
        "mov fs, eax\n"
        "jmp 0x08:1f\n"
        "1:\n"
        : : "m" (gdtp) : "eax"
    );
}

这是我的 gdt.h:

#include <stdint.h>

#define GDT_ENTRIES 7

typedef enum {
    GDT_AVAILABLE = 0x1,
    GDT_LONG_MODE = 0x2,
    GDT_SIZE = 0x3,
    GDT_GRANULARITY = 0x8,
    GDT_ACCESSED = 0x010,
    GDT_READ_WRITE = 0x020,
    GDT_CONFORMING = 0x040,
    GDT_EXECUTABLE = 0x080,
    GDT_SEGMENT = 0x100,
    GDT_RING1 = 0x200,
    GDT_RING2 = 0x400,
    GDT_RING3 = 0x600,
    GDT_PRESENT = 0x800
} GDT_FLAGS;

typedef struct {
    uint16_t limit;
    void *pointer;
}__attribute__((packed)) GDT;

void set_gdt_entry(int, unsigned int, unsigned int, int);
void gdt_init();

我该怎么做才能让它发挥作用?

【问题讨论】:

  • 它是一个包含限制和指向 GDT 数组的指针的结构。问题是,如果我将程序集放在我的 boot.asm 中它可以工作,我将编辑我的问题并显示它
  • 我编辑了我的问题
  • 我改变了它,但它仍然是三重故障。但谢谢你。我没有注意到我对此感到困惑
  • 它是由 GRUB 加载的,我认为 grub 以 1MiB 加载它
  • 我改了,但还是三重故障

标签: gcc x86 inline-assembly osdev gdt


【解决方案1】:

问题不在于内联汇编代码,但是我在您添加到问题中的代码 sn-ps 中看到了错误:

  • GDT_FLAGS 条目:

    GDT_SIZE = 0x3
    

    应该是:

    GDT_SIZE = 0x4
    
  • 您正在使用 Multiboot 加载程序,您将访问 0x100000 以上的内存。您的 GDT 条目没有设置 GDT_GRANULARITY 位,因此您受限于较低的 1MiB 内存。同样,您还没有使用GDT_READ_WRITE 位标记任何描述符。 GDT 初始化应该是:

    void gdt_init() {
        gdtp.limit = GDT_ENTRIES * 8 - 1;
        gdtp.pointer = gdt;
    
        set_gdt_entry(0, 0, 0, 0);
        set_gdt_entry(1, 0, 0xFFFFFFFF, GDT_GRANULARITY | GDT_READ_WRITE \
                      | GDT_SIZE | GDT_EXECUTABLE | GDT_SEGMENT | GDT_PRESENT);
        set_gdt_entry(2, 0, 0xFFFFFFFF, GDT_GRANULARITY | GDT_READ_WRITE \
                      | GDT_SIZE | GDT_SEGMENT | GDT_PRESENT);
        set_gdt_entry(3, 0, 0xFFFFFFFF, GDT_GRANULARITY | GDT_READ_WRITE \
                      | GDT_SIZE | GDT_EXECUTABLE | GDT_SEGMENT | GDT_PRESENT | GDT_RING3);
        set_gdt_entry(4, 0, 0xFFFFFFFF, GDT_GRANULARITY | GDT_READ_WRITE \
                      | GDT_SIZE | GDT_SEGMENT | GDT_PRESENT | GDT_RING3);
    
        asm volatile(
            "lgdt %0\n"
            "mov eax, 0x10\n"
            "mov ss, eax\n"
            "mov es, eax\n"
            "mov ds, eax\n"
            "mov gs, eax\n"
            "mov fs, eax\n"
            "jmp 0x08:1f\n"
            "1:\n"
            : : "m" (gdtp) : "eax"
        );
    }
    

建议

  • 在操作系统开发的早期调试 GDT 代码和中断时,我发现使用 BOCHS 仿真器很有用。当出现问题(即三重故障)时,它将转储处理器状态信息,并具有将这些表转储到控制台的info gdtinfo idt 命令。要使用 BOCHS 进行操作系统开发,您可以生成 ISO 映像并作为 CD-ROM 启动。

  • 您正确编码了set_gdt_entry,只使用了低 20 位而丢弃了高 12 位。为了使内容更具可读性,我建议使用介于 0x00000 和 0xFFFFF(含)之间的值指定限制。当使用GDT_GRANULARITY 时,CPU 将限制值左移 12 位,并将低 12 位设置为 0xFFF。当设置GDT_GRANULARITY 时,限制被视为 4KiB 页面而不是字节。

    GDT_GRANULARITY 未设置时,限制值只是一个介于 0x00000 和 0xFFFFF 之间的 20 位值,将限制指定为字节而不是 4KiB 页。

【讨论】:

  • 必须设置 GDT_GRANULARTIY 和 GDT_READ_WRITE。现在可以使用了,谢谢。我已经安装了 bochs 但我不知道它有诸如 info idt 和 info gdt 之类的命令
  • 是否必须将限制更改为 0xFFFFF,因为设置了粒度?
  • @Grevak,不,您不必这样做,但您可以进行更改(使代码也更具可读性)-您编码它的方式只有低 20 位重要,高 12 位被扔掉了。
  • 我还有一个问题:为什么一定要设置粒度位?如果未设置,则处理器将限制解释为字节。在 32 位保护模式下是否强制粒度?此时文档似乎令人困惑
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多