diff --git a/VKernel/Kernel/paging/paging.vp b/VKernel/Kernel/paging/paging.vp index 866e4a9..907c43e 100644 --- a/VKernel/Kernel/paging/paging.vp +++ b/VKernel/Kernel/paging/paging.vp @@ -274,73 +274,79 @@ def EnablePaging(): global pml4, pdp, pd, pt __asm__(f"mov cr3, {asminp(uint64(pml4), ASM_DESCR.REG_ANY)}") -def _set_page_writable(phys_addr: uint64): - """把物理页 phys_addr(identity 映射,虚拟=物理)的 PTE 置 W 位 +# Extract the physical address of a non-leaf page-table entry. +# Bits 12..51 hold the address; bits 52..62 are reserved and must be zero. +# Bit 63 (NX) is ignored for non-leaf entries. Returns 0 for unusable entries. +def _table_phys(entry: uint64) -> uint64: + if (entry & PTE_PRESENT) == 0: return 0 + if (entry & uint64(0x7FF0000000000000)) != 0: return 0 + return entry & uint64(0x000FFFFFFFFFF000) - 页表页(bootloader 建立的 pml4/pdp/pd/pt 位于 0xf8xxxx)默认只读, - 而内核运行时 MapPage/clone 需修改页表 → 必须先把这些页表页自身设为可写, - 否则 CR0.WP=1 下写页表触发 #PF(slog.so 映射 0x600000 新建页表项时 - cr2=0xf801000 err=0x3 根因)。 +def _set_page_writable(phys_addr: uint64): + """Mark the identity-mapped page containing phys_addr writable via its PTE. + + Page-table pages created by the bootloader are read-only; MapPage and + clone need to modify them, so they must be made writable while CR0.WP + is clear. Non-leaf entries are validated with _table_phys before being + dereferenced. """ global pml4 if not pml4: return va: uint64 = phys_addr & ~uint64(0xFFF) + if va == 0: return pml4_idx: uint64 = (va >> 39) & 0x1FF - if not (pml4.entries[pml4_idx] & PTE_PRESENT): return - pdp_s: _p* = _p*(pml4.entries[pml4_idx] & ~uint64(0xFFF)) + pdp_phys: uint64 = _table_phys(pml4.entries[pml4_idx]) + if pdp_phys == 0: return + pdp_s: _p* = _p*(pdp_phys) pdp_idx: uint64 = (va >> 30) & 0x1FF if not (pdp_s.entries[pdp_idx] & PTE_PRESENT): return if pdp_s.entries[pdp_idx] & PTE_PS: - # 1GB 大页(PDPTE.PS=1,固件 identity 映射常见):没有下层 PD,W 位在本条目自身。 - # 若仍按 `& ~0xFFF` 取"PD 基址",条目位 63(NX,固件对 1GB 数据页常置 1)会一起被带出来 - # → 派生指针非规范 → 读 %rax+idx*8 即 #GP(0)(现场 rip=0x11a93e,偶发取决于该 VA 是否 - # 落进 1GB 大页)。与下面 2MB 分支同口径:直接置本条目 W 位。 + # 1GB page: the W bit lives in this entry; there is no lower PD. pdp_s.entries[pdp_idx] = pdp_s.entries[pdp_idx] | uint64(PTE_WRITABLE) __asm__(f"invlpg [{asminp(va, ASM_DESCR.REG_ANY)}]") return - pd_s: _p* = _p*(pdp_s.entries[pdp_idx] & ~uint64(0xFFF)) + pd_phys: uint64 = _table_phys(pdp_s.entries[pdp_idx]) + if pd_phys == 0: return + pd_s: _p* = _p*(pd_phys) pd_idx: uint64 = (va >> 21) & 0x1FF if not (pd_s.entries[pd_idx] & PTE_PRESENT): return if pd_s.entries[pd_idx] & PTE_PS: - # 2MB 大页:W 位在 PD 条目自身(无下层 PT),直接置位 + # 2MB page: the W bit lives in this entry; there is no lower PT. pd_s.entries[pd_idx] = pd_s.entries[pd_idx] | uint64(PTE_WRITABLE) __asm__(f"invlpg [{asminp(va, ASM_DESCR.REG_ANY)}]") return - pt_s: _p* = _p*(pd_s.entries[pd_idx] & ~uint64(0xFFF)) + pt_phys: uint64 = _table_phys(pd_s.entries[pd_idx]) + if pt_phys == 0: return + pt_s: _p* = _p*(pt_phys) pt_idx: uint64 = (va >> 12) & 0x1FF pt_s.entries[pt_idx] = pt_s.entries[pt_idx] | uint64(PTE_WRITABLE) __asm__(f"invlpg [{asminp(va, ASM_DESCR.REG_ANY)}]") def MakePTWritable(): - """把当前页表层次所有页表页(pml4 自身 + pdp/pd/pt)设为可写 + """Make all page-table pages currently reachable from pml4 writable. - 内核运行时需修改页表(MapPage/clone_for_process),而 bootloader 建立的 - 页表页映射为只读——CR0.WP=1 下必须先放开页表页自身,否则写页表即 #PF。 + Non-leaf entries are validated with _table_phys. Huge pages (PS=1) are + leaves, so they are not walked; _set_page_writable updates their own W bit. """ global pml4 if not pml4: return _set_page_writable(uint64(pml4)) for pi in range(512): - if not (pml4.entries[pi] & PTE_PRESENT): continue - pdp_m: _p* = _p*(pml4.entries[pi] & ~uint64(0xFFF)) + pdp_phys: uint64 = _table_phys(pml4.entries[pi]) + if pdp_phys == 0: continue + pdp_m: _p* = _p*(pdp_phys) _set_page_writable(uint64(pdp_m)) - pj: uint64 for pj in range(512): - if not (pdp_m.entries[pj] & PTE_PRESENT): continue - # 1GB 大页(PDPTE.PS=1)不是页表页:把它当 PD 解引用,会把大页内存内容当表项读 - # (其中约一半 qword 的 bit0=1)→ 按"表项"递归 _set_page_writable 随机地址 → 任意 - # 内存被置 W 位(偶发数据损坏)且可算出非规范指针 → #GP。跳过(与下面 2MB 的 - # `if PTE_PS: continue` 同口径);若页表页本身落在 1GB 大页里,则由上面 - # _set_page_writable(该页地址) 命中 1GB 分支把大页条目的 W 位置起。 if pdp_m.entries[pj] & PTE_PS: continue - pd_m: _p* = _p*(pdp_m.entries[pj] & ~uint64(0xFFF)) + pd_phys: uint64 = _table_phys(pdp_m.entries[pj]) + if pd_phys == 0: continue + pd_m: _p* = _p*(pd_phys) _set_page_writable(uint64(pd_m)) - pk: uint64 for pk in range(512): - if not (pd_m.entries[pk] & PTE_PRESENT): continue if pd_m.entries[pk] & PTE_PS: continue - pt_m: _p* = _p*(pd_m.entries[pk] & ~uint64(0xFFF)) - _set_page_writable(uint64(pt_m)) + pt_phys: uint64 = _table_phys(pd_m.entries[pk]) + if pt_phys == 0: continue + _set_page_writable(pt_phys) def MakeRangeRO(vaddr_start: uint64, vaddr_end: uint64): """把 [vaddr_start, vaddr_end) 范围内所有页的 PTE 清 W 位(只读)