我正在用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, base=0x00000000, limit=0xffffffff, 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, base=0x00000000, limit=0xffffffff, Execute/Read, Non-Conforming, 32-bit
GDT[0x0010]=Data segment, base=0x00000000, limit=0xffffffff, Read/Write
GDT[0x0018]=Code segment, base=0x00000000, limit=0x00000fff, Execute-Only, Non-Conforming, 32-bit
GDT[0x0020]=Data segment, base=0x00000000, limit=0x00000fff, 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, Read/Write, 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, 0x0, 0xFFFFFFFF, SEG_TYPE_DRW, SEG_AC_KERNEL);
// User code segment
gdt_entry_init(gdt + 3, 0x0, 0x0, SEG_TYPE_CEO, SEG_AC_USER);
// User data segment
gdt_entry_init(gdt + 4, 0x0, 0x0, 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 %0n"
: /* no output */
: "m" (gdtr)
: "memory");
// 0x10 is the address of the the kernel data segment
asm volatile("movw 0x10, %%axn":);
asm volatile("movw %%ax, %%dsn":);
asm volatile("movw %%ax, %%fsn":);
asm volatile("movw %%ax, %%gsn":);
asm volatile("movw %%ax, %%ssn":);
// 0x8 is the address of the kernel code segment
asm volatile("pushl 0x8n"
"pushl $1fn"
"lretn"
"1:n"
: /* no output */);
}
如果你们知道这是怎么回事的话。
事实证明,一个非常聪明的人发现了这些问题:
- 在编写内联asm时,我错过了直接值前面的
$
:
// 0x10 is the address of the the kernel data segment
asm volatile("movw $0x10, %%axn":);
asm volatile("movw %%ax, %%dsn":);
asm volatile("movw %%ax, %%fsn":);
asm volatile("movw %%ax, %%gsn":);
asm volatile("movw %%ax, %%ssn":);
// 0x8 is the address of the kernel code segment
asm volatile("pushl $0x8n"
"pushl $1fn"
"lretn"
"1:n"
: /* no output */);
- 我交换了KERNEL和USER的权限,正确的应该是
enum SEG_AC {
SEG_AC_KERNEL = 0b00,
SEG_AC_USER = 0b11,
};