问题描述
我最近开始着迷于内核开发,并从OSDev Wiki上的基础知识教程开始.在实现Hello World示例之后,我继续进行并开始尝试创建Global Descriptor Table.我从网上的各种来源整理了一些GDT的代码,但最终都失败了.我在执行此操作时是否有问题,如果还不能立即弄清楚,是否有任何来源可以提供更多信息?
简而言之,以下带有GDT的内核实现无法使用GRUB加载.我正在使用gcc
和as
进行编译,可以提供所需的任何其他信息.boot.s
.section .text
.global _start
.type _start, @function
_start:
movl $stack_top, %esp
call kernel_main
cli
hlt
.Lhang:
jmp .Lhang
.size _start, . - _start
.global gdt_flush
gdt_flush:
cli
movl -4(%esp), %eax
lgdt (%eax)
movw $0x10, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss //the inclusion of this line or the following
jmp $0x08, $.flush //prevents the kernel from loading
.flush:
ret
.section .bootstrap_stack
stack_bottom:
.skip 16384
stack_top:
kernel.c
void kernel_main() {
gdt_install();
...
}
gdt.c
struct gdt_entry {
uint16_t limit_low;
uint16_t base_low;
uint8_t base_middle;
uint8_t access;
uint8_t granularity;
uint8_t base_high;
}__attribute__((packed));
struct gdt_ptr {
uint16_t limit;
uint32_t base;
}__attribute__((packed));
struct gdt_entry gdt[3];
struct gdt_ptr gp;
extern void gdt_flush(struct gdt_ptr *);
void gdt_set_gate(uint32_t num, uint32_t base, uint32_t limit, uint8_t access, uint8_t 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 & 0x0F);
gdt[num].access = access;
}
void gdt_install() {
gp.limit = (sizeof(struct gdt_entry) * 3) - 1;
gp.base = (uint32_t) &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(&gp);
}
似乎有几个问题.我没有检查您的GDT条目的特定位(这是一个人必须亲自掌握英特尔手册才能完成的工作).
第一件事(现在不会引起问题,但将来可能会出现问题)是您指定并使用4字节宽的限制,尽管GDT仅使用20位.您应该将gdt_install
函数更改为仅传递20位限制,并将其记录在注释中以备将来使用.当然,另一种解决方案是将参数右移12位,但这意义不大,下次您回到GDT管理时可能会有不同的解释.
似乎不正确的第二件事是获取gdt_flush
参数的方式.堆栈向下增长,因此最后压入的项位于(%esp)
(由call
指令压入的返回地址)上,而所需的参数位于4(%esp)
.
我假设您已经处于保护模式,并且引导加载程序已经设置了实际的GDT,所以我看不出有另一个明显的原因导致再次跳远(至少消耗三个时钟),尽管并非总是如此true,代码段直接放置在空段之后.我不喜欢该跳转是用作跳转目标的标签.我建议检查一下,因为这是一个需要绝对值的遥远的距离.
I've recently become fascinated with kernel development, and started with the bare bones tutorial on the OSDev Wiki. After implementing the Hello World example, I moved on and began attempting to create the Global Descriptor Table. From various sources online I pieced together some code for the GDT, which ultimately fails. Is there something wrong in my implementation of this, and if that is not immediately clear, is there any source that could provide more info?
In short, the following implementation of a kernel with a GDT fails to load using GRUB. I am compiling with gcc
and as
, can provide any other info needed.
boot.s
.section .text
.global _start
.type _start, @function
_start:
movl $stack_top, %esp
call kernel_main
cli
hlt
.Lhang:
jmp .Lhang
.size _start, . - _start
.global gdt_flush
gdt_flush:
cli
movl -4(%esp), %eax
lgdt (%eax)
movw $0x10, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss //the inclusion of this line or the following
jmp $0x08, $.flush //prevents the kernel from loading
.flush:
ret
.section .bootstrap_stack
stack_bottom:
.skip 16384
stack_top:
kernel.c
void kernel_main() {
gdt_install();
...
}
gdt.c
struct gdt_entry {
uint16_t limit_low;
uint16_t base_low;
uint8_t base_middle;
uint8_t access;
uint8_t granularity;
uint8_t base_high;
}__attribute__((packed));
struct gdt_ptr {
uint16_t limit;
uint32_t base;
}__attribute__((packed));
struct gdt_entry gdt[3];
struct gdt_ptr gp;
extern void gdt_flush(struct gdt_ptr *);
void gdt_set_gate(uint32_t num, uint32_t base, uint32_t limit, uint8_t access, uint8_t 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 & 0x0F);
gdt[num].access = access;
}
void gdt_install() {
gp.limit = (sizeof(struct gdt_entry) * 3) - 1;
gp.base = (uint32_t) &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(&gp);
}
There seem to be several problems. I didn't check specific bits of your GDT entries (that's the work one must do on his own with Intel manuals in hands).
First thing (that doesn't cause problems now, but may do it in future) is that you specify and work with 4-byte wide limits, although GDT works only with 20 bits. You should change your gdt_install
function to pass only 20-bit limit and document it in comments for future use. Another solution is of course to shift parameter twelve bits right, but it will make less sense and may be explained differently next time you get back to GDT management.
Second thing that doesn't seem to be correct is the way you get parameter for gdt_flush
. Stack grows downwards, so the last pushed item is located on (%esp)
(that's return address pushed by call
instruction) and the parameter you want is located on 4(%esp)
.
I assume you already are in protected mode and actual GDT is already set up by boot loader, so I can't see any obvious reason for another far jump (that consumes at least three clocks), although it isn't always true that code segment is placed directly after null segment.What I don't like on that jump is the label used as jump destination. I would recommend checking it, as it is a far jump that needs absolute value.
这篇关于用基本内核实现GDT的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!