最近,我对内核开发产生了兴趣,并开始学习关于OSDev Wiki的基本教程。在实现Hello示例之后,我继续并开始尝试创建。从网上的各种来源,我拼凑了一些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);
}发布于 2013-08-15 09:03:41
似乎有几个问题。我没有检查你的GDT条目的特定部分(这是一个人必须自己做的工作,英特尔手册在手中)。
第一件事(现在不会引起问题,但将来可能会这样做)是,您指定并使用4字节宽的限制,尽管GDT只工作20位。您应该将您的gdt_install函数更改为只通过20位限制,并将其记录在注释中供以后使用。另一种解决方案当然是将参数12位右移,但这将没有什么意义,下次回到GDT管理时可能会以不同的方式解释。
第二件似乎不正确的事情是获取gdt_flush参数的方式。堆栈向下增长,因此最后一个推项位于(%esp)上(即由call指令推送的返回地址),而您想要的参数位于4(%esp)上。
我假设您已经处于保护模式,而实际的GDT已经由引导加载程序设置,因此我看不到任何明显的跳远(至少消耗三个时钟)的原因,尽管代码段并不总是直接放在null段之后。我不喜欢跳的是作为跳跃目的地的标签。我建议您检查一下,因为这是一个需要绝对值的跳远。
发布于 2022-05-26 14:39:33
我知道这种反应已经很晚了,但我的答案是,万一有人还在想。
首先,OSDEV教程指出,0x10和0x08是占位符值--它们意味着要被段的实际地址替换。如果您已经编写了自己的引导加载程序,并且正在使用QEMU,这两种方法可能都能工作,但是如果您使用GRUB,那么$0x10将用于代码,$0x18用于数据。请参阅这里 (忽略关于中断的讨论)。你可以尝试你的运气使用这些,但没有保证它将工作100%的时间。
值$0x08/$0x10实际上所指的是GDT中定义的段的线性地址。为了计算这些数据并修复您的问题(假设C代码的其余部分是正确的),您需要根据GDT开始的地址计算段的地址,所以(伪代码):
code segment addr => &gdt[1] - &gdt
data segment addr => &gdt[2] - &gdt如果您想在C中实现这一点,您必须将这些参数作为参数传递给您的gdt_flush(),然后通过堆栈或寄存器(取决于编译器传递参数的方式)在程序集中检索它们。然后,修改gdt_flush程序集函数,将'data段addr‘的地址分配给所需的段寄存器,并将’代码段addr‘的地址指定为跳远的段,如下所示:
gdt_flush:
cli
movl -4(%esp), %eax
lgdt (%eax)
movw 'data segment addr', %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss
jmp 'code segment addr', $.flush
.flush:
ret要使其工作,还必须确保编译器/链接器将C代码和程序集代码加载在同一段中。老实说,实现这一点的最好方法是在程序集中,例如这里和这里。如果您仍然坚持使用C,那么您将不得不想出如何以其他方式计算这些地址--考虑到条目是连续的,并且每个地址都有8个字节长,这不会太困难。
https://stackoverflow.com/questions/18247106
复制相似问题