用户程序 - 常见错误

错误 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个条目:

  1. Null描述符
  2. 内核代码段 (DPL=0)
  3. 内核数据段 (DPL=0)
  4. 用户代码段 (DPL=3)
  5. 用户数据段 (DPL=3)
  6. 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));