Files
ViperOS/VKernel/Kernel/paging/paging.vp
T
TermiNexus dcb7c5c2c0 chore(ViperOS): 落库内核侧进行中的改动(26 文件,+1573/-1805)
内容由用户产出,本次仅代为提交、未做任何语义修改;含 static 修饰符前置迁移等(新增 static 前缀 16 行)。提交消息为中性描述——如需更精确的说明请 amend。
2026-09-28 15:24:52 +08:00

481 lines
22 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import drivers.serial.uart.serial as serial
import viperlib
define PTE_PRESENT = (1 << 0)
define PTE_WRITABLE = (1 << 1)
define PTE_USER = (1 << 2)
define PTE_PWT = (1 << 3)
define PTE_PCD = (1 << 4)
define PTE_ACCESSED = (1 << 5)
define PTE_DIRTY = (1 << 6)
define PTE_PS = (1 << 7)
define PTE_GLOBAL = (1 << 8)
class _p:
entries: [512]uint64
define PT_POOL_PAGES = 512
pt_pool_raw: rawptr = None
pt_pool_start: uint64 = 0
pt_pool_used: uint64 = 0
AllocatedPages: uint64 = 0
pml4: _p* = None
pdp: _p*
pd: _p*
pt: _p*
def init(MemmapAddr: uint64, MemmapSize: uint64):
global pml4
pml4 = GetCurrentPml4()
# bootloader/UEFI 的 CR0.WP 通常已是 1(原代码清 WP 正因此)。页表页
# (pml4/pdp/pd/pt,0xf8xxxx)默认只读,而内核运行时 MapPage/clone 需
# 修改页表。顺序:先清 WP(允许写只读页表页)→ MakePTWritable 放开
# 全部页表页 → 再置 WP=1(此后写只读页即 #PF 暴露越界写源)。
__asm__(f"""
mov rax, cr0
and rax, ~0x10000
mov cr0, rax
""")
MakePTWritable()
__asm__(f"""
mov rax, cr0
or rax, 0x10000
mov cr0, rax
""")
def init_pool(buf: rawptr, buf_size: uint64):
global pt_pool_raw, pt_pool_start, pt_pool_used
pt_pool_raw = buf
if buf is None: return
raw_addr: uint64 = uint64(buf)
aligned_addr: uint64 = (raw_addr + uint64(0xFFF)) & ~uint64(0xFFF)
pt_pool_start = aligned_addr
pt_pool_used = 0
def AllocPage() -> rawptr:
global AllocatedPages, pt_pool_used, pml4, pdp, pd, pt
if pt_pool_raw is None: return None
if pt_pool_used >= uint64(PT_POOL_PAGES): return None
page: rawptr = rawptr(pt_pool_start + pt_pool_used * uint64(4096))
pt_pool_used = pt_pool_used + uint64(1)
# rep stosb 向 [rdi] 写 4096 字节:必须声明 CLOBBER_MEMORY,否则优化器
# 无法得知本块改写了内存,可能把其前后的内存读写重排/消除(N8)。
__asm__(f"""mov al, 0
mov ecx, 4096
mov rdi, {asminp(page, ASM_DESCR.REG_ANY)}
rep stosb""",
op = [ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_AL,
ASM_DESCR.CLOBBER_ECX, ASM_DESCR.CLOBBER_RDI])
AllocatedPages = AllocatedPages + uint64(1)
return page
def FreePage(page: rawptr):
global AllocatedPages, pml4, pdp, pd, pt
if page is None: return
if AllocatedPages > 0: AllocatedPages = AllocatedPages - uint64(1)
def MapPage(VirtAddr: uint64, PhysAddr: uint64, flags: uint64):
global pml4, pdp, pd, pt
if VirtAddr == 0 or PhysAddr == 0: return
VirtAddr = VirtAddr & ~uint64(0xFFF)
PhysAddr = PhysAddr & ~uint64(0xFFF)
Pml4Index: uint64 = (VirtAddr >> 39) & 0x1FF
PdpIndex: uint64 = (VirtAddr >> 30) & 0x1FF
PdIndex: uint64 = (VirtAddr >> 21) & 0x1FF
PtIndex: uint64 = (VirtAddr >> 12) & 0x1FF
if not pml4:
pml4 = AllocPage()
if not pml4: return
if not (pml4.entries[Pml4Index] & PTE_PRESENT):
pdp = AllocPage()
if not pdp: return
pml4.entries[Pml4Index] = (uint64(pdp) & ~uint64(0xFFF)) | PTE_PRESENT | PTE_WRITABLE | (flags & PTE_USER)
else:
if (flags & PTE_USER) and not (pml4.entries[Pml4Index] & PTE_USER):
pml4.entries[Pml4Index] = pml4.entries[Pml4Index] | PTE_USER
pdp = _p*(pml4.entries[Pml4Index] & ~uint64(0xFFF))
if not (pdp.entries[PdpIndex] & PTE_PRESENT):
pd = AllocPage()
if not pd: return
pdp.entries[PdpIndex] = (uint64(pd) & ~uint64(0xFFF)) | PTE_PRESENT | PTE_WRITABLE | (flags & PTE_USER)
else:
if (flags & PTE_USER) and not (pdp.entries[PdpIndex] & PTE_USER):
pdp.entries[PdpIndex] = pdp.entries[PdpIndex] | PTE_USER
pd = _p*(pdp.entries[PdpIndex] & ~uint64(0xFFF))
if pd.entries[PdIndex] & 1:
if pd.entries[PdIndex] & 0x80:
big_phys: uint64 = pd.entries[PdIndex] & ~uint64(0x1FFFFF)
inherit_flags: uint64 = pd.entries[PdIndex] & (PTE_PCD | PTE_PWT)
pt = AllocPage()
if not pt: return
split_i: int = 0
while split_i < 512:
pt.entries[split_i] = (big_phys + uint64(split_i) * 0x1000) | PTE_PRESENT | PTE_WRITABLE | inherit_flags
split_i += 1
pd.entries[PdIndex] = (uint64(pt) & ~uint64(0xFFF)) | PTE_PRESENT | PTE_WRITABLE | (flags & PTE_USER)
else:
# PD entry exists as a regular PT (e.g. cloned identity mapping).
# Must propagate PTE_USER to the PD entry so user-mode access is
# allowed at all paging levels, otherwise #PF err=0x5.
if (flags & PTE_USER) and not (pd.entries[PdIndex] & PTE_USER):
pd.entries[PdIndex] = pd.entries[PdIndex] | PTE_USER
else:
pt = AllocPage()
if not pt: return
pd.entries[PdIndex] = (uint64(pt) & ~uint64(0xFFF)) | PTE_PRESENT | PTE_WRITABLE | (flags & PTE_USER)
pt = _p*(pd.entries[PdIndex] & ~uint64(0xFFF))
pt.entries[PtIndex] = PhysAddr | flags | PTE_PRESENT
__asm__(f"invlpg [{asminp(VirtAddr, ASM_DESCR.REG_ANY)}]")
def MapLargePage(VirtAddr: uint64, PhysAddr: uint64, flags: uint64):
global pml4, pdp, pd, pt
VirtAddr = VirtAddr & ~uint64(0x1FFFFF)
PhysAddr = PhysAddr & ~uint64(0x1FFFFF)
Pml4Index: uint64 = (VirtAddr >> 39) & 0x1FF
PdpIndex: uint64 = (VirtAddr >> 30) & 0x1FF
PdIndex: uint64 = (VirtAddr >> 21) & 0x1FF
if not pml4:
pml4 = AllocPage()
if not pml4: return
if not pml4.entries[Pml4Index] & PTE_PRESENT:
pdp = AllocPage()
if not pdp: return
pml4.entries[Pml4Index] = (uint64(pdp) & ~uint64(0xFFF)) | PTE_PRESENT | PTE_WRITABLE
pdp = _p*(pml4.entries[Pml4Index] & ~uint64(0xFFF))
if not (pdp.entries[PdpIndex] & PTE_PRESENT):
pd = AllocPage()
if not pd: return
pdp.entries[PdpIndex] = (uint64(pd) & ~uint64(0xFFF)) | PTE_PRESENT | PTE_WRITABLE
pd = _p*(pdp.entries[PdpIndex] & ~uint64(0xFFF))
pd.entries[PdIndex] = PhysAddr | flags | PTE_PRESENT | PTE_PS
__asm__(f"invlpg [{asminp(VirtAddr, ASM_DESCR.REG_ANY)}]")
def UnMapPage(VirtAddr: uint64):
global pml4, pdp, pd, pt
if VirtAddr == 0: return
if not pml4: return
Pml4Index: uint64 = (VirtAddr >> 39) & 0x1FF
PdpIndex: uint64 = (VirtAddr >> 30) & 0x1FF
PdIndex: uint64 = (VirtAddr >> 21) & 0x1FF
PtIndex: uint64 = (VirtAddr >> 12) & 0x1FF
if not pml4.entries[Pml4Index] & PTE_PRESENT: return
pdp = _p*(pml4.entries[Pml4Index] & ~uint64(0xFFF))
if not pdp.entries[PdpIndex] & PTE_PRESENT: return
pd = _p*(pdp.entries[PdpIndex] & ~uint64(0xFFF))
if not pd.entries[PdIndex] & PTE_PRESENT: return
if pd.entries[PdIndex] & PTE_PS:
pd.entries[PdIndex] = 0
else:
pt = _p*(pd.entries[PdIndex] & ~uint64(0xFFF))
pt.entries[PtIndex] = 0
__asm__(f"invlpg [{asminp(VirtAddr, ASM_DESCR.REG_ANY)}]")
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 位
页表页(bootloader 建立的 pml4/pdp/pd/pt 位于 0xf8xxxx)默认只读,
而内核运行时 MapPage/clone 需修改页表 → 必须先把这些页表页自身设为可写,
否则 CR0.WP=1 下写页表触发 #PF(slog.so 映射 0x600000 新建页表项时
cr2=0xf801000 err=0x3 根因)。
"""
global pml4
if not pml4: return
va: uint64 = phys_addr & ~uint64(0xFFF)
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_idx: uint64 = (va >> 30) & 0x1FF
if not (pdp_s.entries[pdp_idx] & PTE_PRESENT): return
pd_s: _p* = _p*(pdp_s.entries[pdp_idx] & ~uint64(0xFFF))
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),直接置位
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_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)设为可写
内核运行时需修改页表(MapPage/clone_for_process),而 bootloader 建立的
页表页映射为只读——CR0.WP=1 下必须先放开页表页自身,否则写页表即 #PF。
"""
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))
_set_page_writable(uint64(pdp_m))
pj: uint64
for pj in range(512):
if not (pdp_m.entries[pj] & PTE_PRESENT): continue
pd_m: _p* = _p*(pdp_m.entries[pj] & ~uint64(0xFFF))
_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))
def MakeRangeRO(vaddr_start: uint64, vaddr_end: uint64):
"""把 [vaddr_start, vaddr_end) 范围内所有页的 PTE 清 W 位(只读)
定位越界写源:内核态写只读页在 CR0.WP=1 下触发 #PF(int=14),
isrCommonhandler 打印 cr2/rip → 写源立即暴露,不再静默破坏 .rodata。
"""
global pml4
if not pml4: return
va: uint64 = vaddr_start & ~uint64(0xFFF)
# ro_end 下取整到页:不保护含 ro_end 的未对齐尾页。链接脚本输出段未页对齐,
# .data 紧跟 .rodata(__rodata_end 未对齐时 .data 起始落在 rodata 尾页内),
# 整页置只读会把 .data 全局(如 devfs @DevFSObject=0x141630)误保护 →
# devfs_init 写全局 #PF(cr2=0x141630 rip=0x101aaa mov [r15],rax 根因)。
# 尾页含少量 rodata 尾部(对齐填充),保持可写可容忍。
ro_end_align: uint64 = vaddr_end & ~uint64(0xFFF)
while va < ro_end_align:
pml4_idx: uint64 = (va >> 39) & 0x1FF
if not (pml4.entries[pml4_idx] & PTE_PRESENT): va += 0x1000; continue
pdp_r: _p* = _p*(pml4.entries[pml4_idx] & ~uint64(0xFFF))
pdp_idx: uint64 = (va >> 30) & 0x1FF
if not (pdp_r.entries[pdp_idx] & PTE_PRESENT): va += 0x1000; continue
pd_r: _p* = _p*(pdp_r.entries[pdp_idx] & ~uint64(0xFFF))
pd_idx: uint64 = (va >> 21) & 0x1FF
if not (pd_r.entries[pd_idx] & PTE_PRESENT): va += 0x1000; continue
if pd_r.entries[pd_idx] & PTE_PS:
# 2MB 大页:直接跳过会让 .rodata 保持可写——越界写被静默吞掉
# (perf 格式串 win=%lu/work=%lu 被逐步破坏的根因)。拆分为 512
# 个 4KB 页(复用 MapPage 的分裂逻辑),再走下方常规路径清 W 位。
# WP=1 后内核态写只读页即 #PF → isrCommonhandler 打印 cr2/rip 暴露写源。
big_phys: uint64 = pd_r.entries[pd_idx] & ~uint64(0x1FFFFF)
inherit_flags: uint64 = pd_r.entries[pd_idx] & (PTE_PCD | PTE_PWT | PTE_USER | uint64(0x8000000000000000))
pt_split: _p* = AllocPage()
if not pt_split:
va += 0x1000
continue
split_i: int = 0
while split_i < 512:
pt_split.entries[split_i] = (big_phys + uint64(split_i) * uint64(0x1000)) | PTE_PRESENT | PTE_WRITABLE | inherit_flags
split_i += 1
pd_r.entries[pd_idx] = (uint64(pt_split) & ~uint64(0xFFF)) | PTE_PRESENT | PTE_WRITABLE | (pd_r.entries[pd_idx] & PTE_USER)
__asm__(f"invlpg [{asminp(va, ASM_DESCR.REG_ANY)}]")
# 分裂后落入下方 4KB 常规路径,继续清当前页 W 位
pt_r: _p* = _p*(pd_r.entries[pd_idx] & ~uint64(0xFFF))
pt_idx: uint64 = (va >> 12) & 0x1FF
pt_r.entries[pt_idx] = pt_r.entries[pt_idx] & ~uint64(PTE_WRITABLE)
__asm__(f"invlpg [{asminp(va, ASM_DESCR.REG_ANY)}]")
va += 0x1000
def ProtectKernelRO():
"""把内核镜像 .rodata 段(字符串常量/vtable 等)映射为只读
地址经 __asm__ 内联引用链接器符号 __rodata_start/__rodata_end
(linker.ld 定义,TPV 不声明 extern——避免生成定义与链接器符号冲突)。
"""
global pml4
if not pml4: return
# 页表页可写已在 paging.init(WP=0 阶段)由 MakePTWritable 完成,
# 此处只保护 .rodata 内容页(WP=1 后写只读页即 #PF 暴露写源)。
ro_start: uint64 = 0
ro_end: uint64 = 0
__asm__(f"""lea rax, [rip + __rodata_start]
mov {asmout(ro_start, ASM_DESCR.OUTPUT_REG)}, rax
lea rax, [rip + __rodata_end]
mov {asmout(ro_end, ASM_DESCR.OUTPUT_REG)}, rax""",
op=[ASM_DESCR.CLOBBER_RAX])
MakeRangeRO(ro_start, ro_end)
# [PG] 打印 .rodata 起始页映射:确认只读真正生效。
# ps=1 说明仍在 2MB 大页(分裂失败)→ .rodata 保持可写 → 越界写继续被静默吞掉。
DumpMapping(ro_start, "ro_s")
def DumpMapping(va: uint64, tag: const str):
"""[PG] 打印指定 VA 的 pml4/pdp/pd/pt 条目,定位大页(PS)与权限位
内核区域若为 UEFI 2MB 大页,MakeRangeRO 无法只读 .rodata(越界写静默破坏
格式串);1GB 页同理(PS 位在 pdpe)。W=1 且 PS=1 → 该页可写且是大页。
"""
global pml4
if not pml4: return
pml4_i: uint64 = (va >> 39) & 0x1FF
pdp_i: uint64 = (va >> 30) & 0x1FF
pd_i: uint64 = (va >> 21) & 0x1FF
pt_i: uint64 = (va >> 12) & 0x1FF
pml4_e: uint64 = pml4.entries[pml4_i]
pdp_e: uint64 = 0
pd_e: uint64 = 0
pt_e: uint64 = 0
if pml4_e & PTE_PRESENT:
pdp_r: _p* = _p*(pml4_e & ~uint64(0xFFF))
pdp_e = pdp_r.entries[pdp_i]
if pdp_e & PTE_PRESENT:
if pdp_e & PTE_PS:
pd_e = pdp_e
else:
pd_r: _p* = _p*(pdp_e & ~uint64(0xFFF))
pd_e = pd_r.entries[pd_i]
if (pd_e & PTE_PRESENT) and not (pd_e & PTE_PS):
pt_r: _p* = _p*(pd_e & ~uint64(0xFFF))
pt_e = pt_r.entries[pt_i]
buf_d: [200]char
viperlib.snprintf(&buf_d, 200, "[PG] %s va=0x%lx pdpe=0x%lx pde=0x%lx pte=0x%lx (ps=%d W=%d)\n",
tag, va, pdp_e, pd_e, pt_e,
1 if (pd_e & PTE_PS) else 0,
1 if (pt_e & PTE_WRITABLE) or (pd_e & PTE_WRITABLE) or (pdp_e & PTE_WRITABLE) else 0)
serial.puts(&buf_d)
def GetCurrentPml4() -> _p*:
global pml4, pdp, pd, pt
cr3: uint64
__asm__(f"mov {asmout(cr3, ASM_DESCR.OUTPUT_REG)}, cr3")
return _p*(cr3 & ~uint64(0xFFF))
def create_process_page_table() -> uint64:
global pml4
if not pml4: return 0
new_pml4: _p* = AllocPage()
if not new_pml4: return 0
i: uint64
for i in range(512):
entry: uint64 = pml4.entries[i]
if entry & PTE_PRESENT:
new_pml4.entries[i] = entry
return uint64(new_pml4)
def clone_for_process(isolate_vaddr: uint64) -> uint64:
global pml4
_dbg: [120]char
viperlib.snprintf(&_dbg, 120, "[clone] pml4=0x%lx pool_used=%lu iso_va=0x%lx\n", uint64(pml4), pt_pool_used, isolate_vaddr)
serial.puts(_dbg)
if not pml4: return 0
new_pml4_page: _p* = AllocPage()
if not new_pml4_page:
# [DIAG] 定位 AllocPage 失败:pool_used 打印 0 但分配失败 → BSS 全局
# (pt_pool_raw/pt_pool_used)疑似被内存越界写覆盖。
_dbgf: [120]char
viperlib.snprintf(&_dbgf, 120, "[clone] AllocPage FAIL raw=0x%lx used=%lu PP=%d\n",
uint64(pt_pool_raw), pt_pool_used, PT_POOL_PAGES)
serial.puts(&_dbgf)
serial.puts("[clone] AllocPage pml4 failed\n")
return 0
_dbg2: [120]char
viperlib.snprintf(&_dbg2, 120, "[clone] new_pml4=0x%lx\n", uint64(new_pml4_page))
serial.puts(_dbg2)
i: uint64
for i in range(512):
new_pml4_page.entries[i] = pml4.entries[i]
pml4_idx: uint64 = (isolate_vaddr >> 39) & 0x1FF
_dbg3: [120]char
viperlib.snprintf(&_dbg3, 120, "[clone] pml4_idx=%lu entry0=0x%lx\n", pml4_idx, pml4.entries[pml4_idx])
serial.puts(_dbg3)
if not (pml4.entries[pml4_idx] & PTE_PRESENT): return uint64(new_pml4_page)
kernel_pdp: _p* = _p*(pml4.entries[pml4_idx] & ~uint64(0xFFF))
new_pdp_page: _p* = AllocPage()
if not new_pdp_page: return uint64(new_pml4_page)
for i in range(512):
new_pdp_page.entries[i] = kernel_pdp.entries[i]
new_pml4_page.entries[pml4_idx] = (uint64(new_pdp_page) & ~uint64(0xFFF)) | (pml4.entries[pml4_idx] & uint64(0xFFF))
pdp_idx: uint64 = (isolate_vaddr >> 30) & 0x1FF
if not (kernel_pdp.entries[pdp_idx] & PTE_PRESENT): return uint64(new_pml4_page)
kernel_pd: _p* = _p*(kernel_pdp.entries[pdp_idx] & ~uint64(0xFFF))
new_pd_page: _p* = AllocPage()
if not new_pd_page: return uint64(new_pml4_page)
for i in range(512):
new_pd_page.entries[i] = kernel_pd.entries[i]
new_pdp_page.entries[pdp_idx] = (uint64(new_pd_page) & ~uint64(0xFFF)) | (kernel_pdp.entries[pdp_idx] & uint64(0xFFF))
pd_idx: uint64 = (isolate_vaddr >> 21) & 0x1FF
if not (kernel_pd.entries[pd_idx] & PTE_PRESENT): return uint64(new_pml4_page)
if kernel_pd.entries[pd_idx] & PTE_PS: return uint64(new_pml4_page)
kernel_pt: _p* = _p*(kernel_pd.entries[pd_idx] & ~uint64(0xFFF))
new_pt_page: _p* = AllocPage()
if not new_pt_page: return uint64(new_pml4_page)
for i in range(512):
new_pt_page.entries[i] = kernel_pt.entries[i]
new_pd_page.entries[pd_idx] = (uint64(new_pt_page) & ~uint64(0xFFF)) | (kernel_pd.entries[pd_idx] & uint64(0xFFF))
return uint64(new_pml4_page)
def save_pt_range(vaddr_start: uint64, vaddr_end: uint64, save_buf: uint64*, save_count: uint32*) -> int:
global pml4
if not pml4: return -1
*save_count = uint32(0)
va: uint64 = vaddr_start & ~uint64(0xFFF)
while va < vaddr_end:
pml4_idx: uint64 = (va >> 39) & 0x1FF
if not (pml4.entries[pml4_idx] & PTE_PRESENT): va += 0x1000; continue
pdp_: _p* = _p*(pml4.entries[pml4_idx] & ~uint64(0xFFF))
pdp_idx: uint64 = (va >> 30) & 0x1FF
if not (pdp_.entries[pdp_idx] & PTE_PRESENT): va += 0x1000; continue
pd_: _p* = _p*(pdp_.entries[pdp_idx] & ~uint64(0xFFF))
pd_idx: uint64 = (va >> 21) & 0x1FF
if not (pd_.entries[pd_idx] & PTE_PRESENT): va += 0x1000; continue
if pd_.entries[pd_idx] & PTE_PS: va += 0x200000; continue
pt_: _p* = _p*(pd_.entries[pd_idx] & ~uint64(0xFFF))
pt_idx: uint64 = (va >> 12) & 0x1FF
saved: uint32 = *save_count
# [待复核·指针缩放] save_buf 是 `uint64*`;若该联合解析为 Ptr(u64),则下面的
# `* 16` 与 `+ 8` 都会再乘 8(GEP 按元素缩放)⇒ 记录步长 128 字节、字段偏移 64 字节,
# 与本处"16 字节一条记录(va 在 +0、pte 在 +8)"的设计不符。若本意是字节偏移,
# 需改用整数加法推进地址(或显式 rawptr/字节指针)——迁移到 rawptr[T] 时一并复核,
# 见 Debugger/rawptr-migration-and-fastfail.md。结论待定(需一次 IR 取证 GEP 的元素类型)。
entry_ptr: uint64* = save_buf + uint64(saved) * 16
*(entry_ptr + 0) = va
*(entry_ptr + 8) = pt_.entries[pt_idx]
*save_count = uint32(saved + 1)
va += 0x1000
return 0
def restore_pt_range(save_buf: uint64*, save_count: uint32):
global pml4
if not pml4: return
i: uint32 = 0
while i < save_count:
# [待复核·指针缩放] 同 save_pt_range:`* 16` / `+ 8` 若按 Ptr(u64) 缩放会变成 128/64 字节
# (该函数必须与 save_pt_range 的写入布局严格一致,两者要一起复核/一起改)。
entry_ptr: uint64* = save_buf + uint64(i) * 16
va: uint64 = *(entry_ptr + 0)
orig_pte: uint64 = *(entry_ptr + 8)
pml4_idx: uint64 = (va >> 39) & 0x1FF
if not (pml4.entries[pml4_idx] & PTE_PRESENT): i += 1; continue
pdp_: _p* = _p*(pml4.entries[pml4_idx] & ~uint64(0xFFF))
pdp_idx: uint64 = (va >> 30) & 0x1FF
if not (pdp_.entries[pdp_idx] & PTE_PRESENT): i += 1; continue
pd_: _p* = _p*(pdp_.entries[pdp_idx] & ~uint64(0xFFF))
pd_idx: uint64 = (va >> 21) & 0x1FF
if not (pd_.entries[pd_idx] & PTE_PRESENT): i += 1; continue
if pd_.entries[pd_idx] & PTE_PS: i += 1; continue
pt_: _p* = _p*(pd_.entries[pd_idx] & ~uint64(0xFFF))
pt_idx: uint64 = (va >> 12) & 0x1FF
pt_.entries[pt_idx] = orig_pte
__asm__(f"invlpg [{asminp(va, ASM_DESCR.REG_ANY)}]", op=[ASM_DESCR.CLOBBER_MEMORY])
i += 1
def set_active_pml4(cr3_val: uint64):
global pml4
pml4 = _p*(cr3_val & ~uint64(0xFFF))