如何初始化 GDT?

How can I initialise the GDT?

我正在制作一个操作系统,但我被困在 GDT 上。我试过不同的教程,例如 http://www.osdever.net/bkerndev/Docs/gdt.htm and http://www.jamesmolloy.co.uk/tutorial_html/4.-The%20GDT%20and%20IDT.html,但我的 os 总是崩溃。我怎样才能解决这个问题?我使用grub所以内核已经处于保护模式。

boot.asm:

section .multiboot
multiboot_start:
dd 0xe85250d6
dd 0
dd multiboot_end - multiboot_start
dd 0x100000000 - (0xe85250d6 + 0 + (multiboot_end - multiboot_start))
dw 0
multiboot_end:
section .text
global gdt_flush
extern gp
gdt_flush:
lgdt [gp]
mov ax, 0x10
mov ds, ax; This line restarts the computer
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
jmp 0x08:flush2
flush2:
ret               
extern kernel_main
start:
mov esp, stack_space
call kernel_main
hlt
section .bss
resb 10240
stack_space:

kernel.c:

#include <tty.h>
#include <log.h>
struct gdt_entry {
unsigned short limit_low;
unsigned short base_low;
unsigned char base_middle;
unsigned char access;
unsigned char granularity;
unsigned char base_high;
} __attribute__((packed));
struct gdt_ptr {
unsigned short limit;
unsigned int base;
} __attribute__((packed));
struct gdt_entry gdt[3];
struct gdt_ptr gp;
extern void gdt_flush();
void gdt_set_gate(int num, unsigned long base, unsigned long limit, unsigned char access, unsigned char gran) {
gdt[num].base_low = (base & 0xFFFF);
gdt[num].base_middle = (base >> 16) & 0xFF;
gdt[num].base_high = (base >> 24) & 0xFF;
gdt[num].limit_low = (limit & 0xFFFF);
gdt[num].granularity = ((limit >> 16) & 0x0F);
gdt[num].granularity |= (gran & 0xF0);
gdt[num].access = access;
}
void gdt_install() {
gp.limit = (sizeof(struct gdt_entry) * 3) - 1;
gp.base = &gdt;
gdt_set_gate(0, 0, 0, 0, 0);
gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF);
gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF);
gdt_flush();
}
void kernel_main(void){
initterm();
put("Initializing system...\n");
gdt_install();
}

linker.ld:

SECTIONS
{
. = 1M;
.text BLOCK(4K) : ALIGN(4K)
{
    *(.multiboot)
    *(.text)
}
.data BLOCK(4K) : ALIGN(4K)
{
    *(.data)
}
.bss BLOCK(4K) : ALIGN(4K)
{
    *(COMMON)
    *(.bss)
}
}

生成文件:

LINKFILES=kernel/boot.o kernel/kernel.o kernel/libk/string/strlen.o kernel/libk/tty/tty.o kernel/libk/ioport/inb.o kernel/libk/ioport/outb.o kernel/libk/serial/serwritechar.o kernel/libk/serial/writetoserial.o kernel/libk    /tty/print.o kernel/libk/log/put.o
compile:
cd kernel && make compile
build:
ld -o devos.bin -Tkernel/linker.ld $(LINKFILES) -melf_i386
mkdir -p iso/boot/grub
mv devos.bin iso/boot/devos.bin
cp grub.cfg iso/boot/grub/grub.cfg
grub-mkrescue -o devos.iso iso
.SILENT:
all: compile build

内核 makefile:

compile:
cd libk && make compile
nasm -felf32 boot.asm
gcc -c kernel.c -I libk -std=gnu99 -m32 -ffreestanding

假设您没有向我们展示的 initterm 没有错误或使用 STI 指令打开中断,您的代码似乎没问题。未处理的中断或没有正确的中断描述符 Table (IDT) 将导致三重故障。

我怀疑问题可能根本不是上述问题。一般来说,如果您创建的 ELF objects 打算由 Multiboot(2) 兼容的引导加载程序加载,您应该在链接描述文件中明确设置入口点。设置它明确地告诉链接器您希望引导加载程序从哪里开始执行您的代码。在您的情况下,您的代码中有一个 start 标签,所以我认为您打算将其作为入口点。

在链接描述文件的顶部添加:

ENTRY(start) 

链接器期望符号 start 将是一个全局标签。在带有 multiboot2 header 的汇编文件中,确保 start 是全局的,这一行:

global start

这应该足以正确设置入口点。如果您在链接描述文件中明确放置 ENTRY 指令,如果链接器找不到您定义为入口点的全局标签,它会警告您,并会告诉您默认入口点地址。默认值通常是 ELF object 中的起始虚拟内存地址 (VMA)。在你的情况下是 0x100000.

如果 ENTRY 指令不存在,您将不会收到任何警告。在这种情况下,链接器通常会安静地搜索一个名为 startglobal 标签,如果找不到,则将入口点设置为 [=35= 的起始 VMA ]ELFobject。在链接描述文件中使用 ENTRY 指令指定起始地址将告诉您是否存在问题,如果缺少它,它会使用哪个 VMA 作为入口点。

一般经验法则:始终使用 ENTRY 指令在链接描述文件中指定入口点,并在代码中全局导出该标签。