gcc 为什么内联汇编代码会导致三重错误?

mwg9r5ms  于 2022-11-13  发布在  其他
关注(0)|答案(1)|浏览(175)

我使用GCC编译代码,并带有-masm=intel选项。我的内核是由GRUB之类的多引导加载程序加载的。
我想加载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();

我该怎么做才能让它工作呢?

ndasle7k

ndasle7k1#

问题不在内联汇编代码中,但是我在您添加到问题中的代码片段中发现了一些错误:

  • GDT_FLAGS条目:
GDT_SIZE = 0x3

应道:

GDT_SIZE = 0x4
  • 您正在使用多重引导加载程序,并且您将访问0x 100000以上的内存。您的GDT条目没有设置GDT_GRANULARITY位,因此您被限制在较低的1 MiB内存中。同时,您也没有用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", "memory"
      );
  }

建议

  • 在操作系统开发早期调试GDT代码和中断时,我发现使用BOCHS仿真器很有用。当出现问题(即三重故障)时,它将转储处理器状态信息,并有info gdtinfo idt命令将这些表转储到控制台。要使用BOCHS进行操作系统开发,您可以生成一个ISO映像并作为CD-ROM Boot 。
  • 您正确地编写了set_gdt_entry,只使用了较低的20位,而丢弃了较高的12位。我建议使用介于0x 00000和0xFFFFF之间的值指定限制当使用GDT_GRANULARITY时,CPU将极限值左移12位,并且将低12位设置为0xFFF。当设置GDT_GRANULARITY时,限制被视为4 KiB页面而不是字节。

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

相关问题