Files

850 lines
40 KiB
Plaintext
Raw Permalink 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
import string
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)
# 4KB PTE bit 7 = PAT select (for large pages the same bit is PS).
define PTE_PAT = (1 << 7)
# PAT entry 4 is reprogrammed to write-combining (WC) for the framebuffer.
# IA32_PAT is configured in main.vp after CPUID tells us PAT is available.
_pat_wc: int = 0
def set_pat_wc():
global _pat_wc
_pat_wc = 1
def has_pat_wc() -> int:
return _pat_wc
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
# 实际可用页数(由 init_pool 依调用者给的 buf_size 算出,上限 PT_POOL_PAGES)。
# 不能用硬编码 PT_POOL_PAGES 当唯一上限:池的缓冲由调用者提供,缓冲小于
# PT_POOL_PAGES*4K 时,池会发到缓冲之外(那些页属于 buddy 空闲块/别人内存)。
pt_pool_pages: uint64 = 0
# 池全局量的完整性哨兵:pt_pool_start/pt_pool_pages 一旦被越界写覆盖,池就会把页表页发到
# 任意地址(例如 buddy 的空闲块 0xba4c000 上)—— 那正是本文件 line ~444 注释里的老怀疑。
define PT_POOL_MAGIC = 0x50544C21 # "PTL!"
pt_pool_magic: uint64 = 0
pt_pool_limit: uint64 = 0 # pt_pool_start + pt_pool_pages*4096
AllocatedPages: uint64 = 0
pml4: _p* = None
# 内核启动时的 PML4;运行时 clone 新进程必须以它为模板,不能克隆调用者(父进程)
# 的用户页表,否则父子进程会共享 heap/stack 的 PT/PTE。
_kernel_pml4: uint64 = 0
pdp: _p*
pd: _p*
pt: _p*
def init(MemmapAddr: uint64, MemmapSize: uint64):
global pml4, _kernel_pml4
pml4 = GetCurrentPml4()
_kernel_pml4 = uint64(pml4)
# 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_pages, pt_pool_magic, pt_pool_limit
pt_pool_raw = buf
if buf is None:
pt_pool_pages = 0
pt_pool_magic = 0
pt_pool_limit = 0
return
raw_addr: uint64 = uint64(buf)
aligned_addr: uint64 = (raw_addr + uint64(0xFFF)) & ~uint64(0xFFF)
pt_pool_start = aligned_addr
pt_pool_used = 0
# 边界以调用者给的 buf_size 为准。此前只保存 buf、把 buf_size 丢掉,AllocPage 又拿硬编码
# PT_POOL_PAGES 当唯一上限 ⇒ 缓冲小于 PT_POOL_PAGES*4K 时池会发到缓冲之外,那几页属于
# buddy 空闲块/别人的内存,页表内容会直接覆盖它们(现场:buddy 块头 size/next 被
# "页基址|0x22" 形态的 PTE 成片覆盖 ⇒ 空闲链坏 ⇒ 后续 allocate_block -> NULL)。
slack: uint64 = aligned_addr - raw_addr
avail: uint64 = 0
if buf_size > slack:
avail = (buf_size - slack) / uint64(4096)
if avail > uint64(PT_POOL_PAGES): avail = uint64(PT_POOL_PAGES)
pt_pool_pages = avail
pt_pool_limit = pt_pool_start + avail * uint64(4096)
pt_pool_magic = uint64(PT_POOL_MAGIC)
if avail < uint64(PT_POOL_PAGES):
serial.puts("[ptpool] ERROR: caller buffer smaller than PT_POOL_PAGES - capped, later exhaustion expected\n")
def AllocPage() -> rawptr:
global AllocatedPages, pt_pool_used, pml4, pdp, pd, pt
if pt_pool_raw is None: return None
# 哨兵:池的全局量被越界写覆盖时,池会把页表页发到任意地址(例如 buddy 空闲块 0xba4c000)
# ⇒ 页表内容直接覆盖别人的块头。这里先自证清白,再发页。
if pt_pool_magic != uint64(PT_POOL_MAGIC):
_pmb: [192]char
viperlib.snprintf(&_pmb, 192, "[ptpool] CORRUPT: magic=0x%lx start=0x%lx used=%lu pages=%lu - refusing to hand out pages\n",
pt_pool_magic, pt_pool_start, pt_pool_used, pt_pool_pages)
serial.puts(&_pmb)
return None
# 上限用"真实可用页数"(init_pool 依 buf_size 算出);耗尽时显式报出,
# 既不静默失败、也绝不越界发页(越界发的页会覆盖别人内存)。
if pt_pool_used >= pt_pool_pages:
_apb: [176]char
viperlib.snprintf(&_apb, 176, "[ptpool] EXHAUSTED: used=%lu/%lu pages - raise PT_POOL_PAGES or the caller buffer\n",
pt_pool_used, pt_pool_pages)
serial.puts(&_apb)
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)
# ============================================================
# _split_1gb_to_pd - 1GB 大页(PDPTE.PS=1)拆成 512 个 2MB 的 PD
# ============================================================
# 固件的 identity 映射常以 1GB 大页表示一大片(不可执行)内存。此时 PDPTE 没有下层 PD,
# 若把它当 PD 用(`pd.entries[i] = ...`),页表项会写进**大页内存本身**(写坏数据),
# 读到的“表项”是大页内存内容(随机,约一半 qword bit0=1)→ 据此再派生指针即非规范地址
# → #GP(0)(现场 rip=0x11a93e)。拆分后 PDPTE 指向新建 PD 且 PS 清 0,每个 2MB 条目
# 继承原 1GB 条目的 PCD/PWT/USER/NX,后续沿用既有 2MB/4KB 逻辑。
# 参数用 uint64 物理地址(与本文件 MapPage/UnMapPage/_set_page_writable 同口径,
# 不引入结构体指针形参),函数内自行重建 _p*。
# 返回 1 = 可继续(条目本来就不存在、或本来就是 PD、或拆分成功);0 = 拆分失败(页表池耗尽)。
# 注意:条目**不存在**时返回 1 —— 此时“建 PD”由调用方既有逻辑处理(MapPage/MapLargePage
# 的 `if not present: pd = AllocPage()`),本函数不能替它失败。
def _split_1gb_to_pd(pdp_pa: uint64, pdp_idx: uint64) -> int:
if pdp_pa == 0: return 0
pdp_t: _p* = _p*(pdp_pa & ~uint64(0xFFF))
e: uint64 = pdp_t.entries[pdp_idx]
if not (e & PTE_PRESENT): return 1
if not (e & PTE_PS): return 1
base: uint64 = e & ~uint64(0x3FFFFFFF)
# 只继承缓存/执行属性,**绝不继承 PTE_USER**:这些 2MB 条目是 identity 映射,
# 用户态只能通过 MapPage 为具体 4KB 页显式加 USER。若把固件 1GB 条目的 U=1
# 带进 2MB 叶条目,应用一旦越界写到尚未映射的 VA(例如 heap 0x21780000),
# 就会命中 identity 叶页、直接改物理内存里的 buddy 块头(BAD blk 源头)。
inherit: uint64 = e & (PTE_PCD | PTE_PWT | uint64(0x8000000000000000))
new_pd: _p* = AllocPage()
if not new_pd: return 0
for si in range(512):
new_pd.entries[si] = (base + uint64(si) * uint64(0x200000)) | PTE_PRESENT | PTE_WRITABLE | PTE_PS | inherit
# 新 PDPTE 是**非叶条目**:只带 PRESENT|WRITABLE;USER 由 MapPage 在真正建立
# 用户页时逐级补上。非叶条目也不能带 bit63(NX)(见下)。
pdp_t.entries[pdp_idx] = (uint64(new_pd) & ~uint64(0xFFF)) | PTE_PRESENT | PTE_WRITABLE
return 1
# 把 1GiB 范围内"物理地址 == 虚拟地址"的 identity 叶条目的 USER 位清掉。
# 参数 gb_base 必须是 1GiB 对齐的虚拟基址(pdp_idx << 30)。
def _sanitize_identity_pd(pd: _p*, gb_base: uint64):
i: int = 0
while i < 512:
e: uint64 = pd.entries[i]
if (e & PTE_PRESENT) == 0:
i += 1
continue
va2m: uint64 = gb_base + uint64(i) * uint64(0x200000)
if (e & PTE_PS) != 0:
if (e & ~uint64(0x1FFFFF)) == va2m and (e & PTE_USER) != 0:
pd.entries[i] = e & ~PTE_USER
else:
pt_s: _p* = _p*(e & ~uint64(0xFFF))
j: int = 0
while j < 512:
ee: uint64 = pt_s.entries[j]
if (ee & PTE_PRESENT) != 0 and (ee & PTE_USER) != 0:
va4k: uint64 = va2m + uint64(j) * uint64(0x1000)
if (ee & ~uint64(0xFFF)) == va4k:
pt_s.entries[j] = ee & ~PTE_USER
j += 1
i += 1
# 为 dst 进程克隆 vaddr 所在的 PML4/PDP/PD/PT 分支。已克隆过的层级直接复用,
# 因此可以对同一 1GiB 内的多个 2MiB 区域连续调用。
# 关键用途:用户栈都映射在同一个 USER_STACK_VTOP;若不把该分支私有化,
# 新进程映射自己的栈会改写共享页表,把父进程的栈 PTE 覆盖掉 → ret 到 0。
def _clone_vaddr(dst: _p*, vaddr: uint64):
pml4_i: uint64 = (vaddr >> 39) & 0x1FF
if not (pml4.entries[pml4_i] & PTE_PRESENT): return
src_pdp: _p* = _p*(pml4.entries[pml4_i] & ~uint64(0xFFF))
dst_pdp_ent: uint64 = dst.entries[pml4_i]
if dst_pdp_ent == pml4.entries[pml4_i]:
new_pdp: _p* = AllocPage()
if not new_pdp: return
i: int = 0
while i < 512:
new_pdp.entries[i] = src_pdp.entries[i]
if (new_pdp.entries[i] & PTE_PRESENT) != 0 and (new_pdp.entries[i] & PTE_PS) != 0:
new_pdp.entries[i] = new_pdp.entries[i] & ~PTE_USER
i += 1
dst.entries[pml4_i] = (uint64(new_pdp) & ~uint64(0xFFF)) | (pml4.entries[pml4_i] & uint64(0xFFF) & ~PTE_USER)
dst_pdp_ent = dst.entries[pml4_i]
dst_pdp: _p* = _p*(dst_pdp_ent & ~uint64(0xFFF))
pdp_i: uint64 = (vaddr >> 30) & 0x1FF
if not (src_pdp.entries[pdp_i] & PTE_PRESENT): return
if src_pdp.entries[pdp_i] & PTE_PS:
if _split_1gb_to_pd(uint64(src_pdp), pdp_i) == 0: return
src_pd: _p* = _p*(src_pdp.entries[pdp_i] & ~uint64(0xFFF))
if dst_pdp.entries[pdp_i] == src_pdp.entries[pdp_i]:
new_pd: _p* = AllocPage()
if not new_pd: return
i = 0
while i < 512:
new_pd.entries[i] = src_pd.entries[i]
if (new_pd.entries[i] & PTE_PRESENT) != 0 and (new_pd.entries[i] & PTE_PS) != 0:
new_pd.entries[i] = new_pd.entries[i] & ~PTE_USER
i += 1
dst_pdp.entries[pdp_i] = (uint64(new_pd) & ~uint64(0xFFF)) | (src_pdp.entries[pdp_i] & uint64(0xFFF) & ~PTE_USER)
dst_pd: _p* = _p*(dst_pdp.entries[pdp_i] & ~uint64(0xFFF))
pd_i: uint64 = (vaddr >> 21) & 0x1FF
if not (src_pd.entries[pd_i] & PTE_PRESENT): return
if src_pd.entries[pd_i] & PTE_PS: return
if dst_pd.entries[pd_i] == src_pd.entries[pd_i]:
src_pt: _p* = _p*(src_pd.entries[pd_i] & ~uint64(0xFFF))
new_pt: _p* = AllocPage()
if not new_pt: return
i = 0
while i < 512:
new_pt.entries[i] = src_pt.entries[i] & ~PTE_USER
i += 1
dst_pd.entries[pd_i] = (uint64(new_pt) & ~uint64(0xFFF)) | (src_pd.entries[pd_i] & uint64(0xFFF) & ~PTE_USER)
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))
# 1GB 大页(PDPTE.PS=1):先拆成 512 个 2MB 的 PD,否则下面的 pd.entries[...] 会写进
# 大页内存(写坏数据)并读到随机“表项”。拆完就是 2MB 条目,按既有逻辑继续。
if _split_1gb_to_pd(uint64(pdp), PdpIndex) == 0: return
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
for split_i in range(512):
pt.entries[split_i] = (big_phys + uint64(split_i) * 0x1000) | PTE_PRESENT | PTE_WRITABLE | inherit_flags
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))
# 1GB 大页(PDPTE.PS=1):先拆成 PD,否则下面会把 2MB 条目写进大页内存(写坏数据)。
if _split_1gb_to_pd(uint64(pdp), PdpIndex) == 0: return
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
# 1GB 大页(PDPTE.PS=1):先拆成 2MB 的 PD,否则下面会把大页内存内容当表项 →
# 往派生出的随机地址写 0(此前“fb/pitch/w/h 被清零”那类隐性写坏)。拆完按既有
# 口径撤除命中的条目(PS 覆盖时整条撤除,与 2MB 分支一致)。
if _split_1gb_to_pd(uint64(pdp), PdpIndex) == 0: 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)}")
# Physical address backing a 4KB user page, or 0 if unmapped / a huge page.
# munmap() needs this before UnMapPage() clears the PTE, so it can hand the
# frame back to the buddy allocator.
# (Local helper: _table_phys is defined later in this module and Viper does
# not allow same-module forward references.)
def _entry_table(e: uint64) -> uint64:
if (e & PTE_PRESENT) == 0: return 0
if (e & uint64(0x7FF0000000000000)) != 0: return 0
return e & uint64(0x000FFFFFFFFFF000)
def GetPagePhys(VirtAddr: uint64) -> uint64:
if VirtAddr == 0: return 0
p4: _p* = pml4
if not p4: return 0
va: uint64 = VirtAddr & ~uint64(0xFFF)
i4: uint64 = (va >> 39) & 0x1FF
pdp_pa: uint64 = _entry_table(p4.entries[i4])
if pdp_pa == 0: return 0
p3: _p* = _p*(pdp_pa)
i3: uint64 = (va >> 30) & 0x1FF
if p3.entries[i3] & PTE_PS: return 0
pd_pa: uint64 = _entry_table(p3.entries[i3])
if pd_pa == 0: return 0
p2: _p* = _p*(pd_pa)
i2: uint64 = (va >> 21) & 0x1FF
if p2.entries[i2] & PTE_PS: return 0
pt_pa: uint64 = _entry_table(p2.entries[i2])
if pt_pa == 0: return 0
p1: _p* = _p*(pt_pa)
i1: uint64 = (va >> 12) & 0x1FF
e: uint64 = p1.entries[i1]
if not (e & PTE_PRESENT): return 0
return e & uint64(0x000FFFFFFFFFF000)
# Rewrite the protection bits of an existing 4KB mapping, keeping the frame.
# Returns 1 on success, 0 if the page is not a present 4KB page.
def SetPageFlags(VirtAddr: uint64, flags: uint64) -> int:
if VirtAddr == 0: return 0
p4: _p* = pml4
if not p4: return 0
va: uint64 = VirtAddr & ~uint64(0xFFF)
i4: uint64 = (va >> 39) & 0x1FF
pdp_pa: uint64 = _entry_table(p4.entries[i4])
if pdp_pa == 0: return 0
p3: _p* = _p*(pdp_pa)
i3: uint64 = (va >> 30) & 0x1FF
if p3.entries[i3] & PTE_PS: return 0
pd_pa: uint64 = _entry_table(p3.entries[i3])
if pd_pa == 0: return 0
p2: _p* = _p*(pd_pa)
i2: uint64 = (va >> 21) & 0x1FF
if p2.entries[i2] & PTE_PS: return 0
pt_pa: uint64 = _entry_table(p2.entries[i2])
if pt_pa == 0: return 0
p1: _p* = _p*(pt_pa)
i1: uint64 = (va >> 12) & 0x1FF
e: uint64 = p1.entries[i1]
if not (e & PTE_PRESENT): return 0
p1.entries[i1] = (e & uint64(0x000FFFFFFFFFF000)) | flags | PTE_PRESENT
__asm__(f"invlpg [{asminp(va, ASM_DESCR.REG_ANY)}]")
return 1
# 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)
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
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 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_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 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_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():
"""Make all page-table pages currently reachable from pml4 writable.
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):
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))
for pj in range(512):
if pdp_m.entries[pj] & PTE_PS: continue
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))
for pk in range(512):
if pd_m.entries[pk] & PTE_PS: continue
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 位(只读)
定位越界写源:内核态写只读页在 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
# 1GB 大页(PDPTE.PS=1):同样不是 PD,当 PD 用会把大页内容当表项读(并据其"表项"
# 写页表 → 写坏任意内存)。1GB 大页拆分需新建 1 个 PD + 若干 PT(513 页),此处不支持
# → 跳过该页。后果:若 .rodata 落在 1GB 大页内则该段保持可写(不保护),但绝不再写坏内存。
if pdp_r.entries[pdp_idx] & PTE_PS: 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
for split_i in range(512):
pt_split.entries[split_i] = (big_phys + uint64(split_i) * uint64(0x1000)) | PTE_PRESENT | PTE_WRITABLE | inherit_flags
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 定义,VPC 不声明 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, stack_vaddr: uint64 = 0) -> 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]
# 固件 identity 大页可能带 U=1:克隆到进程地址空间后必须降为 supervisor。
# 用户页的 USER 位由后续 MapPage 在具体 4KB PTE 上显式添加。
if (new_pdp_page.entries[i] & PTE_PRESENT) != 0 and (new_pdp_page.entries[i] & PTE_PS) != 0:
new_pdp_page.entries[i] = new_pdp_page.entries[i] & ~PTE_USER
new_pml4_page.entries[pml4_idx] = (uint64(new_pdp_page) & ~uint64(0xFFF)) | (pml4.entries[pml4_idx] & uint64(0xFFF) & ~PTE_USER)
pdp_idx: uint64 = (isolate_vaddr >> 30) & 0x1FF
if not (kernel_pdp.entries[pdp_idx] & PTE_PRESENT): return uint64(new_pml4_page)
# 1GB 大页(PDPTE.PS=1):源端没有下层 PD。先把它拆成常规 PD(语义不变:512 个 2MB
# 条目仍映射同一物理 1GB),否则下面会把**大页内存内容**当 PD 拷进新页表,再用这个
# 垃圾 PD 替换新地址空间的该条目(该区间映射被写坏 / 后续写入落到任意物理内存)。
# 拆完后面沿用既有逐层拷贝逻辑;PD 层命中 2MB 时按既有 `if PTE_PS: return` 早退。
if kernel_pdp.entries[pdp_idx] & PTE_PS:
if _split_1gb_to_pd(uint64(kernel_pdp), pdp_idx) == 0: return uint64(new_pml4_page)
kernel_pd: _p* = _p*(kernel_pdp.entries[pdp_idx] & ~uint64(0xFFF))
# 固件 identity 映射可能把 4KB/2MB 叶条目的 U 置 1。只要 U=1,用户态一旦
# 写到尚未显式映射的 VA(应用越界写),就会直接落到**同数值物理页**上——
# buddy 块头、页表、别人的数据全会被踩(现场:1GiB 下 0x21780000 的
# order-16 空闲块头被清零)。这里把该 1GiB 内"物理地址 == 虚拟地址"的
# identity 叶条目降为 supervisor;真正的用户页(phys != va)保持原样。
_sanitize_identity_pd(kernel_pd, isolate_vaddr & ~uint64(0x3FFFFFFF))
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]
if (new_pd_page.entries[i] & PTE_PRESENT) != 0 and (new_pd_page.entries[i] & PTE_PS) != 0:
new_pd_page.entries[i] = new_pd_page.entries[i] & ~PTE_USER
new_pdp_page.entries[pdp_idx] = (uint64(new_pd_page) & ~uint64(0xFFF)) | (kernel_pdp.entries[pdp_idx] & uint64(0xFFF) & ~PTE_USER)
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] & ~PTE_USER
new_pd_page.entries[pd_idx] = (uint64(new_pt_page) & ~uint64(0xFFF)) | (kernel_pd.entries[pd_idx] & uint64(0xFFF) & ~PTE_USER)
# 以 kernel PML4 为模板时,PML4[511](用户栈所在高半区)通常没有映射;
# 之后 _elf_entry_wrapper 里的 MapPage 会为该进程新建私有 PDP/PD/PT。
# 不要在这里额外 clone 高半区:kernel PML4[511] 可能含有 1GiB/大页或
# 非用户映射,强行拆分会在 clone 阶段访问用户栈 VA 触发内核 #PF。
return uint64(new_pml4_page)
# Deep-copy every USER mapping from src into dst (dst must already contain the
# kernel mappings, e.g. from create_process_page_table()). Data frames are
# allocated through alloc_fn(4096); page-table pages come from the paging pool.
#
# A table is only duplicated when its subtree really contains user pages;
# otherwise the parent's table is shared. Without that check a single user
# page at 0x400000 drags the whole kernel identity map (one PT page per 2MB
# region) into the new address space and exhausts the page-table pool.
def _alloc_user_frame(alloc_fn: rawptr) -> rawptr:
newp: rawptr = None
__asm__(f"""mov r11, {asminp(alloc_fn, ASM_DESCR.REG_ANY)}
push rbp
mov rbp, rsp
and rsp, -16
mov rdi, 4096
call r11
mov rsp, rbp
pop rbp
mov {asmout(newp, ASM_DESCR.OUTPUT_REG)}, rax""",
op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX,
ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDX,
ASM_DESCR.CLOBBER_RSI, ASM_DESCR.CLOBBER_RDI,
ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9,
ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_R11])
return newp
# level: 1 = PT, 2 = PD, 3 = PDPT (4 = PML4 only used as the entry point).
def _table_has_user(t: _p*, level: int) -> int:
i: int = 0
while i < 512:
e: uint64 = t.entries[i]
if (e & PTE_PRESENT) != 0:
if level == 1:
if (e & PTE_USER) != 0: return 1
elif (e & PTE_PS) != 0:
if (e & PTE_USER) != 0: return 1
elif (e & PTE_USER) != 0:
if _table_has_user(_p*(e & ~uint64(0xFFF)), level - 1) != 0: return 1
i += 1
return 0
def _copy_user_table(src: _p*, dst: _p*, level: int, alloc_fn: rawptr) -> int:
i: int = 0
while i < 512:
e: uint64 = src.entries[i]
if (e & PTE_PRESENT) == 0:
dst.entries[i] = e
elif level == 1:
if (e & PTE_USER) == 0:
dst.entries[i] = e
else:
src_phys: uint64 = e & uint64(0x000FFFFFFFFFF000)
newp: rawptr = _alloc_user_frame(alloc_fn)
if newp is None: return -1
string.memcpy(newp, rawptr(src_phys), 4096)
dst.entries[i] = (uint64(newp) & ~uint64(0xFFF)) | (e & uint64(0xFFF))
elif (e & PTE_PS) != 0:
dst.entries[i] = e
elif (e & PTE_USER) == 0:
dst.entries[i] = e
else:
csrc: _p* = _p*(e & ~uint64(0xFFF))
if _table_has_user(csrc, level - 1) == 0:
dst.entries[i] = e
else:
np: _p* = AllocPage()
if not np: return -1
dst.entries[i] = (uint64(np) & ~uint64(0xFFF)) | (e & uint64(0xFFF))
if _copy_user_table(csrc, np, level - 1, alloc_fn) != 0: return -1
i += 1
return 0
def CopyUserSpace(src_pa: uint64, dst_pa: uint64, alloc_fn: rawptr) -> int:
if src_pa == 0 or dst_pa == 0: return -1
s4: _p* = _p*(src_pa)
d4: _p* = _p*(dst_pa)
return _copy_user_table(s4, d4, 4, alloc_fn)
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
# 1GB 大页(PDPTE.PS=1):不是 PD 页表——当 PD 用会把大页内存内容当表项存进保存缓冲
# (后续 restore 会把这些 garbage 写回页表 → 写坏任意内存)。整片跳过(前进 1GB)。
if pdp_.entries[pdp_idx] & PTE_PS: va += 0x40000000; 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
# 记录布局:每条 **16 字节**(va 在 +0、pte 在 +8)。
# 注意:`uint64*` 的 `+ n` 会按元素(8 字节)缩放,所以这里用**显式字节地址**推进记录,
# 字段用元素索引(+1 即 +8 字节)。此前写 `save_buf + saved * 16` 会变成 128 字节步长、
# `+ 8` 会变成 +64 字节,与上面的布局不符(缓冲若按 16 字节/条分配就会越界)。
entry_ptr: uint64* = uint64*(uint64(save_buf) + uint64(saved) * 16)
*(entry_ptr + 0) = va
*(entry_ptr + 1) = 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 字节**(va 在 +0、pte 在 +8);
# 记录用显式字节地址推进,字段用元素索引(+1 即 +8 字节)。两处必须一起改。
entry_ptr: uint64* = uint64*(uint64(save_buf) + uint64(i) * 16)
va: uint64 = *(entry_ptr + 0)
orig_pte: uint64 = *(entry_ptr + 1)
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
# 1GB 大页(PDPTE.PS=1):save 侧已整片跳过,正常不会有记录落在这里;若出现说明
# 保存缓冲陈旧/错配 —— 同样跳过,绝不把 garbage 基址当 PT 去写回(写坏任意内存)。
if pdp_.entries[pdp_idx] & PTE_PS: 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))
export def get_kernel_pml4() -> uint64:
return _kernel_pml4