From 2a03330a9dbb469aefd2cfc7fdda6e89bde18f67 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sun, 30 Aug 2026 07:27:45 +0800 Subject: [PATCH] =?UTF-8?q?fix(kernel):=20=E5=88=86=E8=A3=82=202MB=20?= =?UTF-8?q?=E5=A4=A7=E9=A1=B5=E4=BD=BF=20.rodata=20=E7=9C=9F=E6=AD=A3?= =?UTF-8?q?=E5=8F=AA=E8=AF=BB=EF=BC=8C=E5=8A=A0=E9=A1=B5=E8=A1=A8=E4=B8=8E?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E4=B8=B2=E6=A0=A1=E9=AA=8C=E8=AF=8A=E6=96=AD?= =?UTF-8?q?=E5=B9=B6=E7=A6=81=E7=94=A8=20App=20=E8=87=AA=E5=8A=A8=E5=90=AF?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VKernel/Kernel/intr/idt.vp | 49 +++++++++++++++--------- VKernel/Kernel/main.vp | 61 ++++-------------------------- VKernel/Kernel/paging/paging.vp | 59 ++++++++++++++++++++++++++++- VKernel/Kernel/services/desktop.vp | 14 ++++++- 4 files changed, 110 insertions(+), 73 deletions(-) diff --git a/VKernel/Kernel/intr/idt.vp b/VKernel/Kernel/intr/idt.vp index 5bbd473..a1cda19 100644 --- a/VKernel/Kernel/intr/idt.vp +++ b/VKernel/Kernel/intr/idt.vp @@ -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 diff --git a/VKernel/Kernel/main.vp b/VKernel/Kernel/main.vp index 835d77a..4b0fb69 100644 --- a/VKernel/Kernel/main.vp +++ b/VKernel/Kernel/main.vp @@ -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 diff --git a/VKernel/Kernel/paging/paging.vp b/VKernel/Kernel/paging/paging.vp index cff46f9..5c43e4c 100644 --- a/VKernel/Kernel/paging/paging.vp +++ b/VKernel/Kernel/paging/paging.vp @@ -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 diff --git a/VKernel/Kernel/services/desktop.vp b/VKernel/Kernel/services/desktop.vp index 2699ae8..0e1ab8e 100644 --- a/VKernel/Kernel/services/desktop.vp +++ b/VKernel/Kernel/services/desktop.vp @@ -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