GDT 段重新加载失败

问题描述

我正在用 c 为 x86 平台编写一个小内核,但是我在加载 gdt 和重新加载段选择器时遇到了麻烦。

我正在使用 bochs 来测试我的内核。

问题是,当我加载 GDT 但不重新加载段选择器时,我可以停止我的程序,输入 info gdt 并得到一个不错的结果: 当我不加载 GDT 时:

<bochs:2> info gdt
Global Descriptor Table (base=0x00000000000010b0,limit=32):
GDT[0x0000]=??? descriptor hi=0x00000000,lo=0x00000000
GDT[0x0008]=??? descriptor hi=0x00000000,lo=0x00000000
GDT[0x0010]=Code segment,base=0x00000000,limit=0xffffffff,Execute/Read,Non-Conforming,Accessed,32-bit
GDT[0x0018]=Data segment,Read/Write,Accessed
You can list individual entries with 'info gdt [NUM]' or groups with 'info gdt [NUM] [NUM]'
<bochs:3> 

当我加载 GDT 时:

<bochs:2> info gdt
Global Descriptor Table (base=0x00000000001022a0,limit=48):
GDT[0x0000]=??? descriptor hi=0x00000000,lo=0x00000000
GDT[0x0008]=Code segment,32-bit
GDT[0x0010]=Data segment,Read/Write
GDT[0x0018]=Code segment,limit=0x00000fff,Execute-Only,32-bit
GDT[0x0020]=Data segment,Read-Only
GDT[0x0028]=??? descriptor hi=0x00000000,lo=0x00000000
You can list individual entries with 'info gdt [NUM]' or groups with 'info gdt [NUM] [NUM]'
<bochs:3> 

所以看起来我的 GDT 加载正确。

现在是棘手的部分。 当我想重新加载段选择器时,出现此错误

04641352650e[cpu0  ] fetch_raw_descriptor: GDT: index (ff57) 1fea > limit (30)
04641352650e[cpu0  ] interrupt(): vector must be within IDT table limits,IDT.limit = 0x0
04641352650e[cpu0  ] interrupt(): vector must be within IDT table limits,IDT.limit = 0x0
04641352650i[cpu0  ] cpu is in protected mode (active)
04641352650i[cpu0  ] CS.mode = 32 bit
04641352650i[cpu0  ] SS.mode = 32 bit
04641352650i[cpu0  ] EFER   = 0x00000000
04641352650i[cpu0  ] | EAX=0000ff53  EBX=00010000  ECX=001022e0  EDX=00000000
04641352650i[cpu0  ] | ESP=00102294  EBP=001022b0  ESI=00000000  EDI=00000000
04641352650i[cpu0  ] | IOPL=0 id vip vif ac vm RF nt of df if tf sf zf af PF cf
04641352650i[cpu0  ] | SEG sltr(index|ti|rpl)     base    limit G D
04641352650i[cpu0  ] |  CS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
04641352650i[cpu0  ] |  DS:0018( 0003| 0|  0) 00000000 ffffffff 1 1
04641352650i[cpu0  ] |  SS:0018( 0003| 0|  0) 00000000 ffffffff 1 1
04641352650i[cpu0  ] |  ES:0018( 0003| 0|  0) 00000000 ffffffff 1 1
04641352650i[cpu0  ] |  FS:0018( 0003| 0|  0) 00000000 ffffffff 1 1
04641352650i[cpu0  ] |  GS:0018( 0003| 0|  0) 00000000 ffffffff 1 1
04641352650i[cpu0  ] | EIP=001001d8 (001001d8)
04641352650i[cpu0  ] | CR0=0x60000011 CR2=0x00000000
04641352650i[cpu0  ] | CR3=0x00000000 CR4=0x00000000
(0).[4641352650] [0x0000001001d8] 0010:00000000001001d8 (unk. ctxt): mov ds,ax                ; 8ed8
04641352650e[cpu0  ] exception(): 3rd (13) exception with no resolution,shutdown status is 00h,resetting

这样,当我再次输入 info gdt 时,它给了我一个非常大的数组, 这甚至不适合我的终端回滚容量。 这是最后几行:

GDT[0xffd8]=??? descriptor hi=0x72670074,lo=0x64696c61
GDT[0xffe0]=16-Bit TSS (available) at 0x6c65725f,length 0xc6275
GDT[0xffe8]=Data segment,base=0x5f726700,limit=0x0002636f,Read-Only,Expand-down,Accessed
GDT[0xfff0]=Data segment,base=0x00657266,limit=0x00086572,Accessed
GDT[0xfff8]=Data segment,base=0x675f6275,limit=0x00057267,Read/Write
You can list individual entries with 'info gdt [NUM]' or groups with 'info gdt [NUM] [NUM]'

它说我想访问 GDT 之外的数据。

这是我目前编写的代码

enum SEG_TYPE {
    // Data
    SEG_TYPE_DRO = 0b0000,SEG_TYPE_DRW = 0b0010,SEG_TYPE_DROE = 0b0100,SEG_TYPE_DRWE = 0b0110,// Code
    SEG_TYPE_CEO = 0b1000,SEG_TYPE_CER = 0b1010,SEG_TYPE_CEOC = 0b1100,SEG_TYPE_CERC = 0b1110,};

enum SEG_AC {
    SEG_AC_KERNEL = 0b11,SEG_AC_USER = 0b00,};

void gdt_entry_init(struct gdt_entry* entry,u32 base,u32 limit,enum SEG_TYPE type,enum SEG_AC access_rights) {
    // Base address
    entry->base_0_15 = base;
    entry->base_16_23 = base >> 16;
    entry->base_24_31 = base >> 24;
    // Limit
    entry->limit_0_15 = limit;
    entry->limit_16_19 = limit >> 16;
    // Segment type
    entry->type = type;
    // Access rights
    entry->dpl = access_rights;
    // AVL
    entry->avl = 0;
    // Default operation set to 32 bits
    entry->db = 1;
    // Code segment
    entry->l = 0;
    // Present (always present)
    entry->p = 1;
    // Descriptor type (code or data)
    entry->s = 1;
    // Granularity (enabled with 4KBytes increment)
    entry->g = 1;
}
struct gdt_entry {
    u32 limit_0_15  : 16;
    u32 base_0_15   : 16;
    u32 base_16_23  : 8;
    u32 type                : 4;
    u32 s                   : 1;
    u32 dpl                 : 2;
    u32 p                   : 1;
    u32 limit_16_19 : 4;
    u32 avl                 : 1;
    u32 l                   : 1;
    u32 db                  : 1;
    u32 g                   : 1;
    u32 base_24_31  : 8;
} __attribute__((packed));

struct gdt_r {
    u16 limit;
    u32 base;
} __attribute__((packed));

struct gdt_entry gdt[6];

void gdt_init() {
    // Null segment
    struct gdt_entry null_entry = { 0 };
    gdt[0] = null_entry;
    // Kernel code segment
    gdt_entry_init(gdt + 1,0x0,0xFFFFFFFF,SEG_TYPE_CER,SEG_AC_KERNEL);
    // Kernel data segment
    gdt_entry_init(gdt + 2,SEG_TYPE_DRW,SEG_AC_KERNEL);
    // User code segment
    gdt_entry_init(gdt + 3,SEG_TYPE_CEO,SEG_AC_USER);
    // User data segment
    gdt_entry_init(gdt + 4,SEG_TYPE_DRO,SEG_AC_USER);
    // TSS
    gdt[5] = null_entry;

    struct gdt_r gdtr;
    gdtr.base = (u32)gdt;
    gdtr.limit = sizeof(gdt);

    asm volatile("lgdt %0\n"
        : /* no output */
        : "m" (gdtr)
        : "memory");

    // 0x10 is the address of the the kernel data segment
    asm volatile("movw 0x10,%%ax\n":);
    asm volatile("movw %%ax,%%ds\n":);
    asm volatile("movw %%ax,%%fs\n":);
    asm volatile("movw %%ax,%%gs\n":);
    asm volatile("movw %%ax,%%ss\n":);
    
    // 0x8 is the address of the kernel code segment
    asm volatile("pushl 0x8\n"
                 "pushl $1f\n"
                 "lret\n"
                 "1:\n"
        : /* no output */);
}

如果你们知道这是怎么回事。

解决方法

结果是一个非常聪明的人发现了问题:

  1. 在编写内联汇编时,我错过了直接值前面的 $
    // 0x10 is the address of the the kernel data segment
    asm volatile("movw $0x10,%%ax\n":);
    asm volatile("movw %%ax,%%ds\n":);
    asm volatile("movw %%ax,%%fs\n":);
    asm volatile("movw %%ax,%%gs\n":);
    asm volatile("movw %%ax,%%ss\n":);
    
    // 0x8 is the address of the kernel code segment
    asm volatile("pushl $0x8\n"
                 "pushl $1f\n"
                 "lret\n"
                 "1:\n"
        : /* no output */);
  1. 我为 KERNEL 和 USER 交换了权限,应该是正确的
enum SEG_AC {
    SEG_AC_KERNEL = 0b00,SEG_AC_USER = 0b11,};