You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

启用PAE分页时32位内核在KVM及真实硬件触发GPF

32位PAE分页内核在KVM/真实硬件启用分页时触发GPF问题

我正在开发支持PAE分页的32位OS内核,执行mov %eax, %cr0设置PG位启用分页时,QEMU的TCG模式下运行正常,但KVM加速或真实硬件上会直接触发General Protection Fault(GPF)。

✅ 正常工作场景

  • QEMU无-enable-kvm参数的TCG模式下,分页可正常启动
  • 分页结构已完成分配与填充
  • 启用分页前VGA文本输出正常
  • 已测试4GiB范围的恒等映射

❌ 仅在KVM/真实硬件出现的故障

  • 启用分页后立即触发GPF
  • 崩溃时EIP指向有效代码(已通过inline call/pop技巧确认)
  • 故障发生在设置PG位后,尚未实际使用虚拟内存

核心问题

为何启用PAE恒等映射分页仅在KVM及真实硬件下触发GPF?是否存在KVM下EIP或CR3未映射的已知问题?还有哪些原因会导致启用分页时立即触发GPF?


配置代码

// Allocate memory for PDPT, PDs, and PTs
alignas(PAGE_SIZE) pdpt_t* pdpt = (pdpt_t*)pmm::alloc_frame(1);
alignas(PAGE_SIZE) pd_t* pds = (pd_t*)pmm::alloc_frame(4);
alignas(PAGE_SIZE) pt_t* pts = (pt_t*)pmm::alloc_frame(32);

// Checking if any allocation failed
if (!pdpt || !pds || !pts) {
    kernel_panic("Paging structures allocation failed!\n");
    return;
}

uint64_t frame_addr = 0; // Physical address starts at 0x00000000

// Set up PDPT
for (int i = 0; i < 4; i++) {
    pdpt->entries[i] = ((uint64_t)&pds[i] & ~0xFFF) | PRESENT | WRITABLE;
}

// Set up PDs and PTs
for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 512; j++) {
        int pt_index = (i * 512) + j;
        pds[i].entries[j] = ((uint64_t)&pts[pt_index] & ~0xFFF) | PRESENT | WRITABLE;

        for (int k = 0; k < 512; k++) {
            pts[pt_index].entries[k] = (frame_addr & ~0xFFF) | PRESENT | WRITABLE;
            frame_addr += PAGE_SIZE; // Move to next 4KiB frame
        }
    }
}

// Load PDPT, setting up higher-half kernel, enable PAE, enable paging and flush the TLB
enable_pae();
active_pdpt = pdpt;

// Setting up higher half kernel
// Mapping 0x100000-0x400000 to 3GiB in virtual memory
uint64_t kernel_base = uint64_t(&__kernel_phys_base);
for(uint64_t addr = kernel_base, v_addr = KERNEL_BASE; addr < kernel_base + 0x400000; addr += PAGE_SIZE, v_addr += PAGE_SIZE)
    vmm::map_page(v_addr, addr, PRESENT | WRITABLE);

// Identity mapping up to 4GiB (or to max RAM installed if 4GiB is not available)
uint64_t target = (0x100000000 > pmm::total_installed_ram) ? pmm::total_installed_ram : 0x100000000;
for (uint64_t addr = 0; addr < target; addr += PAGE_SIZE)
    vmm::map_page(addr, addr, PRESENT | WRITABLE);

set_pdpt(uint32_t(pdpt));
flush_tlb();

enable_paging();
flush_tlb();

enabled_paging = true;
vga::printf("Paging initialized with identity map up to 4GiB\n");

已确认项

  • CR3指向正确物理地址
  • 启用分页前打印的EIP处于映射范围内
  • PDPT在启用分页前已完成映射
  • GDT有效,0x08为内核代码选择子
  • 所有分页结构均按页大小对齐

问题分析与解决方案

1. PAE模式下CR3的严格对齐要求

PAE模式中,CR3存储的是PDPT物理地址的高27位,地址必须对齐到32字节(低5位必须为0)。TCG模式可能对对齐错误宽松,但KVM/真实硬件会严格校验。检查set_pdpt实现,确保写入CR3前将地址与~0x1F按位与:

set_pdpt:
    mov eax, [esp+4]
    and eax, ~0x1F  ; 清零低5位,符合PAE对齐要求
    mov cr3, eax
    ret

2. 分页结构地址的物理/虚拟混淆

如果pmm::alloc_frame返回的是虚拟地址而非物理地址,分页条目会被错误填充为虚拟地址——TCG模式可能允许虚拟地址直接作为物理地址使用,但KVM/真实硬件严格区分两者。确认pmm::alloc_frame返回物理地址,或在填充条目时将虚拟地址转换为对应物理地址。

3. PAE启用与CR3加载的顺序错误

当前代码先启用PAE再加载CR3,部分CPU/KVM要求PAE启用前CR3必须指向有效PDPT。正确顺序应为:

  1. 准备所有分页结构
  2. 加载CR3(指向PDPT物理地址)
  3. 启用PAE(设置CR4.PAE)
  4. 启用分页(设置CR0.PG)

调整后CPU启用PAE时可立即识别有效页表,避免临时无效状态触发GPF。

4. 页表权限与代码段权限不匹配

内核代码段描述符如果是只读属性,对应页表条目却设置了WRITABLE位,部分CPU会触发权限冲突GPF。同时检查PRESENT/WRITABLE宏定义,确保未意外设置PAE页表条目的XD(执行禁用)位(位63)。

5. TLB刷新实现问题

PAE模式下加载CR3会自动刷新TLB,额外的flush_tlb()若实现错误(如使用非PAE兼容指令),可能导致TLB残留无效条目。确认flush_tlb()的实现为标准的mov cr3, cr3指令。


内容的提问来源于stack exchange,提问作者Ioane Baidoshvili

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 12:44:57