簡體   English   中英

用基本的 kernel 實現 GDT

[英]Implementing GDT with basic kernel

我最近對 kernel 開發着迷,並從 OSDev Wiki 上的基本教程開始。 實施 Hello World 示例后,我繼續並開始嘗試創建全局描述符表。 我從網上的各種來源拼湊了一些 GDT 的代碼,但最終失敗了。 我的實現有什么問題嗎?如果不是很清楚,是否有任何來源可以提供更多信息?

簡而言之,以下帶有 GDT 的 kernel 的實現無法使用 GRUB 加載。 我正在使用gcc as編譯,並且可以提供所需的任何其他信息。

靴子

.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 function 更改為僅通過 20 位限制並將其記錄在注釋中以備將來使用。 另一種解決方案當然是將參數右移 12 位,但它的意義不大,下次您返回 GDT 管理時可能會有不同的解釋。

第二件似乎不正確的事情是您獲取gdt_flush參數的方式。 堆棧向下增長,因此最后推送的項目位於(%esp) (即call指令推送的返回地址),而您想要的參數位於4(%esp)

我假設你已經處於保護模式並且實際的 GDT 已經由引導加載程序設置,所以我看不出有任何明顯的理由再跳遠(至少消耗三個時鍾),盡管代碼並不總是正確的段直接放在 null 段之后。 我不喜歡那個跳轉的是用作跳轉目的地的 label。 我建議檢查它,因為它是一個需要絕對值的遠距離跳躍。

我知道這個回復已經很晚了,但如果有人仍然想知道,這是我的回答。

首先,OSDEV GDT 教程指出 0x10 和 0x08 是占位符值——它們應該替換為您的段的實際地址。 如果您編寫了自己的引導加載程序並使用 QEMU,這兩個可能會起作用,但如果您使用 GRUB,則 $0x10 用於代碼,$0x18 用於數據。 請參閱此處(忽略有關中斷的討論)。 您可以使用這些試試運氣,但不能保證它會在 100% 的時間內正常工作。

$0x08/$0x10 值實際上指的是 GDT 中定義的段的線性地址。 為了計算這些並解決您的問題(假設 C 代碼的 rest 是正確的),您需要根據 GDT 的起始地址計算段的地址,因此(偽代碼):

code segment addr => &gdt[1] - &gdt
data segment addr => &gdt[2] - &gdt

如果你想在 C 中實現它,你必須將這些作為參數傳遞給你的gdt_flush() ,然后通過堆棧或寄存器在程序集中檢索它們(取決於你的編譯器如何傳遞參數)。 然后,修改gdt_flush程序集 function 以將“數據段地址”的地址分配給所需的段寄存器,並將“代碼段地址”的地址分配給遠跳轉的段,如下所示:

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 字節長,這應該不會太難。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM