首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用基本内核实现GDT

用基本内核实现GDT
EN

Stack Overflow用户
提问于 2013-08-15 05:57:03
回答 2查看 1.3K关注 0票数 3

最近,我对内核开发产生了兴趣,并开始学习关于OSDev Wiki的基本教程。在实现Hello示例之后,我继续并开始尝试创建。从网上的各种来源,我拼凑了一些GDT代码,这最终失败。在我的实现中是否有什么问题,如果这还不清楚,是否有任何来源可以提供更多的信息?

简而言之,具有GDT的内核的以下实现无法使用GRUB加载。我正在用gccas编译,可以提供任何其他所需的信息。

boot.s

代码语言:javascript
复制
.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

代码语言:javascript
复制
void kernel_main() {
    gdt_install();
    ...
}

gdt.c

代码语言:javascript
复制
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);
}
EN

回答 2

Stack Overflow用户

发布于 2013-08-15 09:03:41

似乎有几个问题。我没有检查你的GDT条目的特定部分(这是一个人必须自己做的工作,英特尔手册在手中)。

第一件事(现在不会引起问题,但将来可能会这样做)是,您指定并使用4字节宽的限制,尽管GDT只工作20位。您应该将您的gdt_install函数更改为只通过20位限制,并将其记录在注释中供以后使用。另一种解决方案当然是将参数12位右移,但这将没有什么意义,下次回到GDT管理时可能会以不同的方式解释。

第二件似乎不正确的事情是获取gdt_flush参数的方式。堆栈向下增长,因此最后一个推项位于(%esp)上(即由call指令推送的返回地址),而您想要的参数位于4(%esp)上。

我假设您已经处于保护模式,而实际的GDT已经由引导加载程序设置,因此我看不到任何明显的跳远(至少消耗三个时钟)的原因,尽管代码段并不总是直接放在null段之后。我不喜欢跳的是作为跳跃目的地的标签。我建议您检查一下,因为这是一个需要绝对值的跳远。

票数 2
EN

Stack Overflow用户

发布于 2022-05-26 14:39:33

我知道这种反应已经很晚了,但我的答案是,万一有人还在想。

首先,OSDEV教程指出,0x10和0x08是占位符值--它们意味着要被段的实际地址替换。如果您已经编写了自己的引导加载程序,并且正在使用QEMU,这两种方法可能都能工作,但是如果您使用GRUB,那么$0x10将用于代码,$0x18用于数据。请参阅这里 (忽略关于中断的讨论)。你可以尝试你的运气使用这些,但没有保证它将工作100%的时间。

值$0x08/$0x10实际上所指的是GDT中定义的段的线性地址。为了计算这些数据并修复您的问题(假设C代码的其余部分是正确的),您需要根据GDT开始的地址计算段的地址,所以(伪代码):

代码语言:javascript
复制
code segment addr => &gdt[1] - &gdt
data segment addr => &gdt[2] - &gdt

如果您想在C中实现这一点,您必须将这些参数作为参数传递给您的gdt_flush(),然后通过堆栈或寄存器(取决于编译器传递参数的方式)在程序集中检索它们。然后,修改gdt_flush程序集函数,将'data段addr‘的地址分配给所需的段寄存器,并将’代码段addr‘的地址指定为跳远的段,如下所示:

代码语言:javascript
复制
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个字节长,这不会太困难。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/18247106

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档