为什么我的内联汇编代码会导致三重错误?
Why does my inline assembly code cause a triple fault?
我使用带有 -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 %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_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 %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"
);
}
建议
在 OS 开发的早期调试 GDT 代码和中断时,我发现使用 BOCHS 仿真器很有用。它会在出现问题(即三重故障)时转储处理器状态信息,并具有 info gdt
和 info idt
命令将这些表转储到控制台。要使用 BOCHS 进行 OS 开发,您可以生成 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 页。
我使用带有 -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 %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_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 %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" ); }
建议
在 OS 开发的早期调试 GDT 代码和中断时,我发现使用 BOCHS 仿真器很有用。它会在出现问题(即三重故障)时转储处理器状态信息,并具有
info gdt
和info idt
命令将这些表转储到控制台。要使用 BOCHS 进行 OS 开发,您可以生成 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 页。