用户程序 - 常见错误
错误 1:INT 0x80 触发GPF(通用保护错误)
症状
用户程序执行 int 0x80 时产生GPF而非进入系统调用处理器。
原因
IDT中 0x80 条目的DPL=0,Ring 3代码不允许触发。
解决方案
系统调用中断门必须设置DPL=3:
// 0x8E = DPL=0 (仅内核可触发)
// 0xEE = DPL=3 (用户态也可触发) ← 使用这个
idt_set_gate(0x80, (uint32_t)isr128, 0x08, 0xEE);错误 2:从Ring 3中断后ESP错误
症状
进入内核处理器后栈指针指向非法位置。
原因
TSS中的esp0/ss0未正确设置。
解决方案
tss.ss0 = 0x10; // 内核数据段
tss.esp0 = kernel_stack; // 每个进程的内核栈顶每次进程切换时都要更新TSS.esp0!
错误 3:用户态代码访问内核内存
症状
用户程序可以读写内核数据(安全漏洞)。
原因
页表项未设置User位(US=0允许所有访问)。
解决方案
// 内核页面:不设置PAGE_USER
vmm_map_page(dir, kernel_addr, phys, PAGE_WRITABLE);
// 用户页面:设置PAGE_USER
vmm_map_page(dir, user_addr, phys, PAGE_WRITABLE | PAGE_USER);错误 4:GDT缺少用户态段
症状
IRET到Ring 3时立即GPF。
原因
GDT中没有DPL=3的用户代码段和用户数据段。
解决方案
GDT需要至少6个条目:
- Null描述符
- 内核代码段 (DPL=0)
- 内核数据段 (DPL=0)
- 用户代码段 (DPL=3)
- 用户数据段 (DPL=3)
- TSS描述符
错误 5:系统调用参数验证缺失
症状
恶意用户程序可以通过传入内核地址来读写内核内存。
原因
sys_write等函数直接使用用户传入的指针,没有验证。
解决方案
验证用户指针指向用户空间:
static bool is_user_address(uint32_t addr) {
return addr < 0xC0000000; // 低3GB是用户空间
}
static int sys_write(uint32_t buf, uint32_t len, uint32_t unused) {
if (!is_user_address(buf) || !is_user_address(buf + len)) {
return -1; // 拒绝内核地址
}
// ...正常处理
}错误 6:忘记加载TSS
症状
第一次从Ring 3中断时Triple Fault。
原因
没有使用 ltr 指令加载TSS。
解决方案
// TSS在GDT索引5的位置
uint16_t tss_sel = 5 * 8; // 偏移40
__asm__ volatile("ltr %0" : : "r"(tss_sel));