fix(kernel): 分裂 2MB 大页使 .rodata 真正只读,加页表与格式串校验诊断并禁用 App 自动启动
This commit is contained in:
1 parent
efa99a8c4b
commit
2a03330a9d
4 files changed
+110
-73
No files matched your search
+31
-18
@@ -124,6 +124,15 @@ def isrCommonhandler(interrupt: t.CInt):
|
||||
buf: t.CArray[t.CChar, 100]
|
||||
viperlib.snprintf(c.Addr(buf), 100, "[PF] cr2=0x%lx rip=0x%lx err=0x%lx rsp=0x%lx\n", cr2, fault_rip, fault_err, fault_rsp)
|
||||
serial.puts(buf)
|
||||
# [CR3] 当前 pml4:定位栈区/对象写缺页是否因 pml4 被 switch_pid 切走
|
||||
# (内核 pml4=0xf801000,用户 pml4=0x9781xxx —— 若显示用户 pml4 而 rsp
|
||||
# 在 bootloader 栈区 0x60000-0x80000,则是进程切换后未切回内核页表)。
|
||||
cur_cr3: t.CUInt64T = 0
|
||||
c.Asm(f"mov {c.AsmOut(cur_cr3, t.ASM_DESCR.OUTPUT_REG)}, cr3",
|
||||
op=[t.ASM_DESCR.CLOBBER_RAX])
|
||||
cr3_buf: t.CArray[t.CChar, 80]
|
||||
viperlib.snprintf(c.Addr(cr3_buf), 80, "[CR3] pml4=0x%lx\n", cur_cr3)
|
||||
serial.puts(cr3_buf)
|
||||
# [RAX] 崩溃时 rax(isr.s 保存):定位 cr2 与 rax 关系(rax-偏移=cr2)
|
||||
f_rax: t.CUInt64T = 0
|
||||
c.Asm(f"""mov {c.AsmOut(f_rax, t.ASM_DESCR.OUTPUT_REG)}, [_fault_rax]""",
|
||||
@@ -132,34 +141,38 @@ def isrCommonhandler(interrupt: t.CInt):
|
||||
viperlib.snprintf(c.Addr(rax_buf), 80, "[RAX] rax=0x%lx\n", f_rax)
|
||||
serial.puts(rax_buf)
|
||||
# [STK] rbp 链回溯(定位栈损坏源:崩溃帧 + 4 级调用链的返回地址)
|
||||
# 先读崩溃 rbp 判断(用户 rbp 不回溯,避免读用户地址嵌套 #PF)
|
||||
fr0: t.CUInt64T = 0
|
||||
fr1: t.CUInt64T = 0
|
||||
fr2: t.CUInt64T = 0
|
||||
fr3: t.CUInt64T = 0
|
||||
fr4: t.CUInt64T = 0
|
||||
c.Asm(f"""mov rax, [_fault_rbp]
|
||||
mov {c.AsmOut(fr0, t.ASM_DESCR.OUTPUT_REG)}, rax
|
||||
mov rax, [rax + 8]
|
||||
mov {c.AsmOut(fr1, t.ASM_DESCR.OUTPUT_REG)}, rax
|
||||
mov rax, [_fault_rbp]
|
||||
mov rax, [rax]
|
||||
mov {c.AsmOut(fr2, t.ASM_DESCR.OUTPUT_REG)}, rax
|
||||
mov rax, [rax + 8]
|
||||
mov {c.AsmOut(fr3, t.ASM_DESCR.OUTPUT_REG)}, rax
|
||||
mov rax, [_fault_rbp]
|
||||
mov rax, [rax]
|
||||
mov rax, [rax]
|
||||
mov rax, [rax + 8]
|
||||
mov {c.AsmOut(fr4, t.ASM_DESCR.OUTPUT_REG)}, rax""",
|
||||
mov {c.AsmOut(fr0, t.ASM_DESCR.OUTPUT_REG)}, rax""",
|
||||
op=[t.ASM_DESCR.CLOBBER_RAX])
|
||||
stk_buf: t.CArray[t.CChar, 160]
|
||||
if fr0 >= 0x80000000:
|
||||
# 用户态 rbp(如 0x7fffff...):回溯会读用户栈地址 → 嵌套 #PF 死循环
|
||||
viperlib.snprintf(c.Addr(stk_buf), 160, "[STK] user rbp=0x%lx, chain skipped\n", fr0)
|
||||
serial.puts(stk_buf)
|
||||
else:
|
||||
fr1: t.CUInt64T = 0
|
||||
fr2: t.CUInt64T = 0
|
||||
fr3: t.CUInt64T = 0
|
||||
fr4: t.CUInt64T = 0
|
||||
c.Asm(f"""mov rax, [_fault_rbp]
|
||||
mov rax, [rax + 8]
|
||||
mov {c.AsmOut(fr1, t.ASM_DESCR.OUTPUT_REG)}, rax
|
||||
mov rax, [_fault_rbp]
|
||||
mov rax, [rax]
|
||||
mov {c.AsmOut(fr2, t.ASM_DESCR.OUTPUT_REG)}, rax
|
||||
mov rax, [rax + 8]
|
||||
mov {c.AsmOut(fr3, t.ASM_DESCR.OUTPUT_REG)}, rax
|
||||
mov rax, [_fault_rbp]
|
||||
mov rax, [rax]
|
||||
mov rax, [rax]
|
||||
mov rax, [rax + 8]
|
||||
mov {c.AsmOut(fr4, t.ASM_DESCR.OUTPUT_REG)}, rax""",
|
||||
op=[t.ASM_DESCR.CLOBBER_RAX])
|
||||
viperlib.snprintf(c.Addr(stk_buf), 160, "[STK] rbp=0x%lx ret: 0x%lx 0x%lx 0x%lx 0x%lx\n",
|
||||
fr0, fr1, fr2, fr3, fr4)
|
||||
serial.puts(stk_buf)
|
||||
serial.puts(stk_buf)
|
||||
elif interrupt == 13:
|
||||
gp_rip: t.CUInt64T = 0
|
||||
gp_err: t.CUInt64T = 0
|
||||
|
||||
+8
-53
@@ -179,7 +179,11 @@ class ViperKernel:
|
||||
serial.puts("[main] paging and mm init done\n")
|
||||
# 把 .rodata(字符串常量/vtable)映射只读 + CR0.WP=1:
|
||||
# 越界写立即 #PF 暴露 cr2/rip(此前静默破坏格式串/路径/符号名 → P2/P3/P4/P5 连锁)
|
||||
paging.ProtectKernelRO()
|
||||
paging.ProtectKernelRO()
|
||||
# [PG] 页表诊断:确认 .rodata 是否真正只读(2MB 大页会令 MakeRangeRO 失效)
|
||||
paging.DumpMapping(0x100000, "kern")
|
||||
paging.DumpMapping(0x60000, "bstk")
|
||||
paging.DumpMapping(0x80000000, "fb")
|
||||
|
||||
proc.ProcessManager()
|
||||
serial.puts("[main] process manager init done\n")
|
||||
@@ -237,59 +241,10 @@ class ViperKernel:
|
||||
else:
|
||||
serial.puts("[main] APPS dir not found\n")
|
||||
|
||||
# [DIAG] app 自动启动已禁用:不 spawn 任何 .elf,不加载任何 .so,
|
||||
# 专注排查内核自身的概率性崩溃(sched 启动 / desktop 启动后 30s)。
|
||||
sched.Scheduler.disable()
|
||||
hello_pid: t.CInt = elf.spawn_elf("/APPS/hello.elf")
|
||||
if hello_pid < 0:
|
||||
serial.puts("[main] Hello spawn failed\n")
|
||||
else:
|
||||
slog: t.CArray[t.CChar, 64]
|
||||
viperlib.snprintf(c.Addr(slog), 64, "[main] Hello spawned as pid=%d\n", hello_pid)
|
||||
serial.puts(slog)
|
||||
term_pid: t.CInt = elf.spawn_elf("/APPS/terminal.elf")
|
||||
# [DIAG] spawn_elf 返回 pid=1 但 main 收到 0 —— 跨模块返回值间歇性丢失
|
||||
_dbtp: t.CArray[t.CChar, 64]
|
||||
viperlib.snprintf(c.Addr(_dbtp), 64, "[main] term_pid=%d\n", term_pid)
|
||||
serial.puts(c.Addr(_dbtp))
|
||||
if term_pid < 0:
|
||||
serial.puts("[main] Terminal spawn failed\n")
|
||||
else:
|
||||
tlog: t.CArray[t.CChar, 64]
|
||||
viperlib.snprintf(c.Addr(tlog), 64, "[main] Terminal spawned as pid=%d\n", term_pid)
|
||||
serial.puts(tlog)
|
||||
|
||||
garg_pid: t.CInt = elf.spawn_elf("/APPS/gargantua.elf")
|
||||
if garg_pid < 0:
|
||||
serial.puts("[main] Gargantua spawn failed\n")
|
||||
else:
|
||||
glog: t.CArray[t.CChar, 64]
|
||||
viperlib.snprintf(c.Addr(glog), 64, "[main] Gargantua spawned as pid=%d\n", garg_pid)
|
||||
serial.puts(glog)
|
||||
|
||||
s3d_pid: t.CInt = elf.spawn_elf("/APPS/scene3d.elf")
|
||||
if s3d_pid < 0:
|
||||
serial.puts("[main] Scene3D spawn failed\n")
|
||||
else:
|
||||
s3d_log: t.CArray[t.CChar, 64]
|
||||
viperlib.snprintf(c.Addr(s3d_log), 64, "[main] Scene3D spawned as pid=%d\n", s3d_pid)
|
||||
serial.puts(s3d_log)
|
||||
|
||||
so_base: t.CVoid | t.CPtr = elf.load_so("/LIBS/SLOG.SO")
|
||||
if so_base is not None:
|
||||
serial.puts("[main] slog.so loaded, calling log_info...\n")
|
||||
log_info_fn: t.CVoid | t.CPtr = elf.get_so_symbol(so_base, "log_info", "/LIBS/SLOG.SO")
|
||||
if log_info_fn is not None:
|
||||
msg: t.CConst | t.CChar | t.CPtr = "Hello from shared library!"
|
||||
c.Asm(f"""mov rdi, {c.AsmInp(msg, t.ASM_DESCR.REG_ANY)}
|
||||
call {c.AsmInp(log_info_fn, t.ASM_DESCR.REG_ANY)}""",
|
||||
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX,
|
||||
t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX,
|
||||
t.ASM_DESCR.CLOBBER_RDI, t.ASM_DESCR.CLOBBER_RSI,
|
||||
t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9,
|
||||
t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11])
|
||||
else:
|
||||
serial.puts("[main] log_info symbol not found\n")
|
||||
else:
|
||||
serial.puts("[main] failed to load slog.so\n")
|
||||
serial.puts("[main] app autostart disabled\n")
|
||||
sched.Scheduler.enable()
|
||||
|
||||
# FAT32 destructive tests removed to prevent disk.img corruption
|
||||
|
||||
@@ -266,7 +266,26 @@ def MakeRangeRO(vaddr_start: t.CUInt64T, vaddr_end: t.CUInt64T):
|
||||
pd_r: _p | t.CPtr = t.CType(pdp_r.entries[pdp_idx] & ~t.CUInt64T(0xFFF), _p, t.CPtr)
|
||||
pd_idx: t.CUInt64T = (va >> 21) & 0x1FF
|
||||
if not (pd_r.entries[pd_idx] & PTE_PRESENT): va += 0x1000; continue
|
||||
if pd_r.entries[pd_idx] & PTE_PS: va += 0x200000; 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: t.CUInt64T = pd_r.entries[pd_idx] & ~t.CUInt64T(0x1FFFFF)
|
||||
inherit_flags: t.CUInt64T = pd_r.entries[pd_idx] & (PTE_PCD | PTE_PWT | PTE_USER | t.CUInt64T(0x8000000000000000))
|
||||
pt_split: _p | t.CPtr = AllocPage()
|
||||
if not pt_split:
|
||||
va += 0x1000
|
||||
continue
|
||||
split_i: t.CInt = 0
|
||||
while split_i < 512:
|
||||
pt_split.entries[split_i] = (big_phys + t.CUInt64T(split_i) * t.CUInt64T(0x1000)) \
|
||||
| PTE_PRESENT | PTE_WRITABLE | inherit_flags
|
||||
split_i += 1
|
||||
pd_r.entries[pd_idx] = (t.CUInt64T(pt_split) & ~t.CUInt64T(0xFFF)) \
|
||||
| PTE_PRESENT | PTE_WRITABLE | (pd_r.entries[pd_idx] & PTE_USER)
|
||||
c.Asm(f"invlpg [{c.AsmInp(va, t.ASM_DESCR.REG_ANY)}]")
|
||||
# 分裂后落入下方 4KB 常规路径,继续清当前页 W 位
|
||||
pt_r: _p | t.CPtr = t.CType(pd_r.entries[pd_idx] & ~t.CUInt64T(0xFFF), _p, t.CPtr)
|
||||
pt_idx: t.CUInt64T = (va >> 12) & 0x1FF
|
||||
pt_r.entries[pt_idx] = pt_r.entries[pt_idx] & ~t.CUInt64T(PTE_WRITABLE)
|
||||
@@ -291,6 +310,44 @@ def ProtectKernelRO():
|
||||
mov {c.AsmOut(ro_end, t.ASM_DESCR.OUTPUT_REG)}, rax""",
|
||||
op=[t.ASM_DESCR.CLOBBER_RAX])
|
||||
MakeRangeRO(ro_start, ro_end)
|
||||
# [PG] 打印 .rodata 起始页映射:确认只读真正生效。
|
||||
# ps=1 说明仍在 2MB 大页(分裂失败)→ .rodata 保持可写 → 越界写继续被静默吞掉。
|
||||
DumpMapping(ro_start, "ro_s")
|
||||
|
||||
def DumpMapping(va: t.CUInt64T, tag: t.CConst | 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: t.CUInt64T = (va >> 39) & 0x1FF
|
||||
pdp_i: t.CUInt64T = (va >> 30) & 0x1FF
|
||||
pd_i: t.CUInt64T = (va >> 21) & 0x1FF
|
||||
pt_i: t.CUInt64T = (va >> 12) & 0x1FF
|
||||
pml4_e: t.CUInt64T = pml4.entries[pml4_i]
|
||||
pdp_e: t.CUInt64T = 0
|
||||
pd_e: t.CUInt64T = 0
|
||||
pt_e: t.CUInt64T = 0
|
||||
if pml4_e & PTE_PRESENT:
|
||||
pdp_r: _p | t.CPtr = t.CType(pml4_e & ~t.CUInt64T(0xFFF), _p, t.CPtr)
|
||||
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 | t.CPtr = t.CType(pdp_e & ~t.CUInt64T(0xFFF), _p, t.CPtr)
|
||||
pd_e = pd_r.entries[pd_i]
|
||||
if (pd_e & PTE_PRESENT) and not (pd_e & PTE_PS):
|
||||
pt_r: _p | t.CPtr = t.CType(pd_e & ~t.CUInt64T(0xFFF), _p, t.CPtr)
|
||||
pt_e = pt_r.entries[pt_i]
|
||||
buf_d: t.CArray[t.CChar, 200]
|
||||
viperlib.snprintf(c.Addr(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(c.Addr(buf_d))
|
||||
|
||||
def GetCurrentPml4() -> _p | t.CPtr:
|
||||
global pml4, pdp, pd, pt
|
||||
|
||||
@@ -1816,7 +1816,19 @@ def desktop_thread(arg: t.CVoid | t.CPtr) -> t.CInt:
|
||||
if _elapsed_ms > 0:
|
||||
_fps_val = t.CUInt32T(t.CUInt64T(_perf_frames) * 1000 / _elapsed_ms)
|
||||
string.memset(c.Addr(_perf_buf), 0, 256)
|
||||
viperlib.snprintf(c.Addr(_perf_buf), 256, "[perf] fps=%u wall=%lu work=%lu mouse=%lu kbd=%lu render=%lu(bg=%lu win=%lu cur=%lu fb=%lu) dirty=%d vsync=%d\n", _fps_val, t.CUInt64T(_elapsed_ms), t.CUInt64T(_perf_t_total), t.CUInt64T(_perf_t_mouse), t.CUInt64T(_perf_t_kbd), t.CUInt64T(_perf_t_render), t.CUInt64T(_perf_t_bg), t.CUInt64T(_perf_t_win), t.CUInt64T(_perf_t_cur), t.CUInt64T(_perf_t_fb), _dirty_count, _vblank_ok)
|
||||
# [DIAG] .rodata 格式串校验:win=%lu/work=%lu 曾出现 ' / , 字节破坏(越界写)。
|
||||
# 校验和变化 → 确认为真实内存破坏(配合 .rodata 只读后 #PF 定位写源);
|
||||
# 校验和不变 → 疑似 printf/串口输出伪影,走另一条排查线。
|
||||
_perf_fmt_str: str = "[perf] fps=%u wall=%lu work=%lu mouse=%lu kbd=%lu render=%lu(bg=%lu win=%lu cur=%lu fb=%lu) dirty=%d vsync=%d\n"
|
||||
_fmt_sum: t.CUInt32T = 0
|
||||
_fi: t.CInt = 0
|
||||
while _fi < 110:
|
||||
_fmt_sum = _fmt_sum + t.CUInt32T(t.CUInt8T(_perf_fmt_str[_fi]))
|
||||
_fi += 1
|
||||
_chk_buf: t.CArray[t.CChar, 48]
|
||||
viperlib.snprintf(c.Addr(_chk_buf), 48, "[chk] sum=%u\n", _fmt_sum)
|
||||
serial.puts(c.Addr(_chk_buf))
|
||||
viperlib.snprintf(c.Addr(_perf_buf), 256, _perf_fmt_str, _fps_val, t.CUInt64T(_elapsed_ms), t.CUInt64T(_perf_t_total), t.CUInt64T(_perf_t_mouse), t.CUInt64T(_perf_t_kbd), t.CUInt64T(_perf_t_render), t.CUInt64T(_perf_t_bg), t.CUInt64T(_perf_t_win), t.CUInt64T(_perf_t_cur), t.CUInt64T(_perf_t_fb), _dirty_count, _vblank_ok)
|
||||
serial.puts(_perf_buf)
|
||||
_perf_t_mouse = 0
|
||||
_perf_t_kbd = 0
|
||||
|
||||
Reference in new issue
Block a user