我该如何初始化GDT?

时间:2017-09-17 14:37:45

标签: x86 kernel ld osdev gdt

我制作了一个操作系统而且我被困在GDT。我尝试了不同的教程,例如http://www.osdever.net/bkerndev/Docs/gdt.htmhttp://www.jamesmolloy.co.uk/tutorial_html/4.-The%20GDT%20and%20IDT.html,但我的操作系统总是崩溃。我怎样才能解决这个问题?我使用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

kernel makefile:

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

1 个答案:

答案 0 :(得分:2)

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

我怀疑这个问题可能不是上面的问题。一般来说,如果要创建打算由Multiboot(2)兼容的引导加载程序加载的 ELF 对象,则应在链接描述文件中明确设置入口点。设置它会明确告诉链接器您希望引导加载程序开始执行代码的位置。在您的情况下,您的代码中有一个start标签,因此我认为您打算将其作为切入点。

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

ENTRY(start) 

链接器期望符号start将是全局标签。在包含multiboot2标头的汇编程序文件中,请确保start在此行中是全局的:

global start

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

如果ENTRY指令不存在,您将不会收到任何警告。在这种情况下,链接器通常会静静地搜索名为start全局标签,如果找不到,则将入口点设置为 ELF的起始VMA 对象。在链接描述文件中指定带有ENTRY指令的起始地址将告诉您是否存在问题以及如果缺少VMA,它将用作入口点。

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