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



我使用带有-masm=intel选项的GCC编译我的代码。我的内核是由像 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 %0n"
"mov eax, 0x10n"
"mov ss, eaxn"
"mov es, eaxn"
"mov ds, eaxn"
"mov gs, eaxn"
"mov fs, eaxn"
"jmp 0x08:1fn"
"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_FLAGS条目:

    GDT_SIZE = 0x3
    

    应该是:

    GDT_SIZE = 0x4
    
  • 您正在使用多重引导加载程序,您将访问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 %0n"
    "mov eax, 0x10n"
    "mov ss, eaxn"
    "mov es, eaxn"
    "mov ds, eaxn"
    "mov gs, eaxn"
    "mov fs, eaxn"
    "jmp 0x08:1fn"
    "1:n"
    : : "m" (gdtp) : "eax", "memory"
    );
    }
    
<小时 />

建议

  • 在调试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 页。

相关内容

  • 没有找到相关文章

最新更新