TermiNexus
|
82e951ee84
|
fix(desktop): 建窗与置脏标记加关键区,消除并发导致的共槽/同 z_order
create() 由各 App 线程经 syscall 并发调用(开机多 App 同时建窗最易命中),但 _win_count/_next_z 的读-改-写无互斥:抢占后读到同一 _win_count ⇒ 两个 App 共用同一 AppWindow 槽,标题/坐标/z_order/fb 互相覆盖;读到同一 _next_z ⇒ z_order 相同,渲染排序退化成槽位下标顺序 —— 均表现为更下方窗口的 title 突然显示到最上方。
改为:关键区①只原子分配槽位,结构体填充留在区外(避免长时间关中断);关键区②把 active/z_order/焦点一次性发布(渲染线程只在 active==1 后读该槽)。_bring_to_front 的 _next_z 自增同加关键区;_mark_rows_dirty/_mark_all_dirty/_clear_dirty_flags 同加关键区,避免与 App 线程并发时丢置脏标记(丢标记会让该行永不重绘,旧合成滞留在屏幕上)。
|
2026-10-16 15:20:49 +08:00 |
|
TermiNexus
|
1b2f6853fc
|
fix(usb): uhci 控制传输逐 TD 校验并暴露错误位,补描述符落盘探针
- _wait_for_complete 原先只查 status TD 的 bit21|bit22,DATA 阶段的 CRC/超时(bit18)、bitstuff(bit17)、babble(bit20) 会被当成成功 ⇒ GET_DESCRIPTOR 拿到零字节缓冲区仍返回 0,最终表现为 [usb] device class=00 proto=00 ep=0、usb_kbd 找不到键盘(现场)。改为对齐 Linux usb-uhci.h:新增 TD_CTRL_ERR_MASK/NAK_MASK 与 _td_error(),control_transfer 收尾逐个校验 setup/data/status 三个 TD,失败时打印三个状态字。
- usb.vp 增加两条探针:[usb] devdesc blen/btype/maxpkt0 与 [usb] cfgdesc total/b0/t0/iface/proto/ep,用于区分数据未落盘与解析未命中。
|
2026-10-16 15:15:58 +08:00 |
|
TermiNexus
|
89907682ec
|
fix(keyboard): init() 对齐 mouse 写法 - 去掉 0xAD/0xAE 键盘接口禁用序列(CCB 回写失败会停在禁用态),并补 pic.clearMask(1)
|
2026-10-16 14:14:51 +08:00 |
|
TermiNexus
|
d37eac32fe
|
fix(keyboard): init() 改为幂等(避免重复初始化禁用键盘接口);按键时打印扫描码/字符到串口便于验证
|
2026-10-16 14:12:50 +08:00 |
|
TermiNexus
|
0d0dba476f
|
fix(vkernel): 键盘无法输入 - 键盘/鼠标共享 0x60 统一为单一消费者(IRQ1 ISR→buffer→desktop 轮询),IRQ1 只收键盘字节
|
2026-10-15 17:24:17 +08:00 |
|
TermiNexus
|
0ee5ec5dcc
|
fix(vkernel/ide): PIO 命令块寄存器连写补 io_wait + 读完核 ERR - 修读盘读错扇区(根目录扇区读回 ELF 头致 app spawn 失败)
|
2026-10-15 17:14:12 +08:00 |
|
TermiNexus
|
2eea065a67
|
chore(debug): fat32 dir_find/win_read 加取证探针 - 请求8.3短名 vs 磁盘目录项、读盘失败扇区、win_dirty 写盘痕迹
|
2026-10-15 17:08:58 +08:00 |
|
TermiNexus
|
152c4c5683
|
feat(vkernel/vpsdk): 用户线程生命周期 - THREAD_EXIT 显式退出;异常终止时回收用户栈并释放槽位;返回哨兵可辨识
|
2026-10-15 16:50:58 +08:00 |
|
TermiNexus
|
29e1bc6e34
|
chore(debug): 移除定位用的 MMAP/get_winbuf/redraw 临时探针(线程模型修复后 PF 与卡死均已消失)
|
2026-10-15 16:48:19 +08:00 |
|
TermiNexus
|
a7faeed21b
|
fix(vkernel): THREAD_CREATE 改用户态线程 - 独立用户栈与内核栈经 drop_to_user_mode 进 Ring3;调度器按线程切 TSS.RSP0/gs[8];同步 _thread_entry 硬编码偏移并加 fail-fast 自检
|
2026-10-15 16:45:09 +08:00 |
|
TermiNexus
|
89a319e1be
|
chore(debug): 加临时探针定位 MMAP 返回高半污染与 Tk.buf 野指针 - MMAP/get_winbuf/redraw
|
2026-10-15 16:20:14 +08:00 |
|
TermiNexus
|
9745f8832a
|
fix(vkernel): UHCI frame list 步长越界 12KB 砸坏 buddy 堆(堆损坏根因);paging 页表记录指针缩放;清理临时探针
根因:uhci.vp 两处写成 _frame_list + uint64(i) * 4 —— uint32* 的 +n 已按元素(4字节)缩放,
再乘 4 使实际步长变成 16 字节;1024 次循环写到 16KB,而缓冲只有 UHCI_NUM_FRAMES*4 = 4KB
⇒ 越界 12KB 覆盖相邻 buddy 空闲块头(现场:[0E][PF] cr2=0xba4c000 err=0x3 rip=0x10e598
rax=0x3000=12288),把空闲链 next/prev 写坏 ⇒ 随机 [mm] BAD blk、malloc 失败、app 崩溃。
定位手段:把空闲链表头页在内核页表标只读(paging.init 已置 CR0.WP=1),任何写入立即 #PF
并给出 rip/cr2 直接指认写者(该探针定位完已删除)。
paging:save_pt_range / restore_pt_range 里的 save_buf + i*16 在 uint64* 上实际是 128 字节
步长、+8 实际是 +64 字节(IR 取证:getelementptr i64 ⇒ 按 8 字节元素缩放),改为显式字节
地址推进记录 + 元素索引(+1 = +8 字节),两处布局保持一致。
保留的加固(非探针):memory_block.magic 与 allocate_block/free_block/merge_blocks 的块头
校验、free() 的堆范围校验、slab 建页的 total 边界护栏、页表池按真实 buf_size 计页与
pt_pool_magic/pt_pool_limit 哨兵、#GP 分支补打 rax 便于定位非规范地址写。
elf.vp 新增 ELF_LOAD 开关(=1 正常装载;置 0 可整体跳过 app/.so 装载,便于隔离内核问题)。
|
2026-10-15 13:35:34 +08:00 |
|
TermiNexus
|
ec57603bb3
|
fix(vkernel): 分配器与页表池加固(块头魔数/双链校验、池边界与哨兵),并清理定位用临时探针
mm: memory_block 加 magic(塞进原有 padding,布局仍 32B);allocate/free/merge 前校验块头(magic、size==2^order、state、prev/next 在堆内且 8 字节对齐),坏块只报不改并限流;free() 增加堆范围校验;slab 建页前校验 total 与地址范围自洽(防 uint64 下溢 + uint16 截断导致成片写页外);NO FREE BLOCK 附带 used/free/total。paging: 页表池按调用者给的真实 buf_size 计算可用页数(此前丢掉 buf_size、上限硬编码 PT_POOL_PAGES ⇒ 池会发到缓冲之外,把页表内容盖到 buddy 空闲块头);加 pt_pool_magic/pt_pool_limit 哨兵与 CORRUPT/EXHAUSTED fail-fast。idt: #GP 分支补打 rax(rax+0x10 即触发访存地址),与 #PF 分支同口径。同时移除本轮定位用临时探针(elf.vp/isr.s/main.vp 恢复原状)。
|
2026-10-15 12:47:39 +08:00 |
|
TermiNexus
|
f8eed7f300
|
fix(vkernel): GS 归一化的说明改为 asm 外的英文注释(本编译器 asm 模板不接受注释/符号名)
上一版把说明写进 __asm__ 模板里:① 编译器会扫描 asm 模板中的符号名(注释里的 _per_cpu 也被判为需导出而报 TypeError);② 本编译器的 __asm__ 模板本身不接受注释。现把注释全部移出 asm,并改用英文;汇编指令本体不变(cli → mov rsp,ksp → 按 GS_BASE 是否为 0 决定 swapgs → 压 iretq 帧)。
|
2026-10-14 22:15:26 +08:00 |
|
TermiNexus
|
1f2faf0db8
|
fix(vkernel): 进用户态前归一化 GS_BASE,修线程从用户态定时器 ISR 切出后 GS 状态错乱导致的三重故障
根因:线程可能是在**别人的用户态定时器 ISR 里**被 _yield 切过来的(timer 路径不经 isrCommonhandler 的 GS 恢复,且切换后 ISR 出口的 swapgs 永不执行),此时 GS_BASE 仍 = _per_cpu。带着这个状态跑用户代码 ⇒ app 第一条 syscall 再 swapgs 就反了 ⇒ mov rsp,gs:[0x8] 读到地址 0x8 的垃圾 ⇒ rsp 变垃圾 ⇒ push 触发 #PF 时异常帧也压不进去 ⇒ 三重故障(QEMU 消失、零输出、ISR 入口探针都照不到)。现场:pid=1(Terminal) 正常、pid=2 起静默三崩。修复:drop_to_user_mode 在 iretq 前按 GS_BASE 是否为 0 决定是否 swapgs(幂等),保证用户代码一律以 GS_BASE=0 运行。
|
2026-10-14 22:11:19 +08:00 |
|
TermiNexus
|
ee29ce37d1
|
fix(vkernel): 异常处理器 rbp 链回溯改为每跳校验,避免处理 #PF 时再触发 #PF 造成无限重入
旧实现用 mov rax,[rax] / mov rax,[rax+8] 裸链式解引用。当崩溃上下文是 ring3(或被 iret 帧影响)时,链上会取到 iret 帧里压入的 user_rsp=0x7fffffe00000 并对其解引用 ⇒ 在 #PF 处理中再次 #PF,rsp 每次下移 ~0x2A0,无限重入耗尽内核栈 ⇒ 三重故障(QEMU 窗口消失、看不到真正的首故障)。现新增 _SafeStackWord(仅 0x1000..0x80000000 内才解引用),三处分支(#PF/#GP/其他)统一改用安全回溯,并照旧跳过用户态 rbp。
|
2026-10-14 21:48:58 +08:00 |
|
TermiNexus
|
c977caec91
|
fix(vkernel/vpsdk): 窗口缓冲改走“用户缓冲 + WIN_FLUSH 拷贝”,修 app 写内核页 #PF(err=0x7)
- 现场:四个 app 一画就用户态 #PF,cr2 全在内核堆区(~0xb7c1000/0x9f81000/0xb851000/0xb881000),err=0x7(present + 用户写),RAX 正是各自颜色常量(0x1E1E32/0xC0C0C/0xF0F19)
- 根因:GET_WINBUF 把 desktop 的**内核**窗口后备缓冲地址直接交给 app(该页 U=0),app 当用户指针写 → 必然 #PF,四个 app 全中(tid=5 那条 cr2=0xffffffff52209000 是同一指针继续走坏)
- 内核:WIN_FLUSH 增加源指针 `WIN_FLUSH(win_id, src)`:先 desktop.get_fb 取窗口缓冲、再 desktop.get_fb_bytes 取字节数(配对取,避免 resize 重建导致尺寸/指针不一致),memcpy 后 desktop.flush;syscall_handler 的 WIN_FLUSH 分支透传 arg2;desktop 新增 get_fb_bytes
- app(vpsdk/window.vp):get_winbuf 改为按窗口分配**用户态**缓冲(stdlib.malloc → MMAP,落在 0x20000000 用户堆)并缓存;flush(win_id) 改为 WIN_FLUSH(win_id, 用户缓冲)
|
2026-10-14 19:30:29 +08:00 |
|
TermiNexus
|
4289d9b190
|
fix(vkernel/intr): GDT/STAR 根治 —— 用户代码段移到第5项(0x28)、TSS 移 0x30,统一用户 CS=0x2B
- 背景:syscall 返回走 sysretq,硬件按 STAR[63:48]=0x1B 算 CS=+16=0x2B、SS=+8=0x23;而 GDT 原来把用户代码放在第 3 项(0x18)、第 5 项(0x28)放 TSS ⇒ app 实际是“拿 TSS 描述符当代码段”在跑(QEMU 不校验 SYSRET 目标描述符类型才没暴露;严格实现会在 sysretq 处 #GP)
- gdt.vp:GDT 扩到 8 项、limit 0x37→0x3F;新增 gdt[5]=用户代码段(0xFA/0x20,与 gdt[3] 同属性;第 3 项保留为兼容镜像);TSS 改放第 6/7 项(setTSS64(6,…) = 选择子 0x30),ltr 改用 0x30;USER_CODE_SEG→0x28、TSS_SEG→0x30
- process.vp:drop_to_user_mode 首次进用户态的 iretq 也改用 CS=0x2B(与 sysretq 一致,两条入口不再产生不同 CS)
- isr.s:用户返回校验同时接受 0x2B/0x1B(0x1B 仅作兼容镜像兜底);上一个 commit 已修“只认 0x1B 导致用户态一被中断就 cli;hlt 假死”
|
2026-10-14 19:07:54 +08:00 |
|
TermiNexus
|
79e9dfd5a4
|
fix(vkernel/intr): ISR 用户返回同时接受 CS=0x2B(sysretq 选择子),修“用户态一被中断就 cli;hlt 假死”
- 现象:日志尾部只有 "B 2B 23",随后整机假死(QEMU 窗口消失、没有 dump)
- 根因:isr\num\()_ret_user 硬编码校验 CS==0x1B;但 app 只要发过一次 syscall,返回走 sysretq 后 CS 就是 STAR[63:48]=0x1B 算出的 0x2B(SS=0x23)→ 之后任何用户态中断/异常(例如定时器抢占)都被判“坏 CS/SS” → 走 cli;hlt 死循环,既假死又掩盖真实异常现场
- 现在同时接受 0x1B 与 0x2B(SS 仍须 0x23);注释里记录 GDT/STAR 隐患:0x2B 当前指向 GDT 第 5 项,而那里是 TSS(QEMU 不校验 SYSRET 目标描述符类型才没暴露),根治需把用户代码段挪到第 5 项
|
2026-10-14 19:04:07 +08:00 |
|
TermiNexus
|
c232c0c0d5
|
fix(vkernel/intr): syscall 来源标记改放内核栈(可嵌套),修 app 调完 .so 后跑飞
- 现象:app 在用户态成功调 .so(“[so] call returned”)后立刻跑飞:用户态 #BP,rip=0x7c0b9(既不在 app 映像 0x400000-0x41c824、也不在 app 栈 0xbb11000)、rsp=0
- 根因:来源标记原来放在全局 _syscall_from_kernel,但 syscall 会**嵌套**:app 的 SO_CALL1 → 内核侧执行 .so → .so 内部又发 3 次 syscall(SERIAL_PUTS,内核态发起)→ 内层把标记改成 1 → 外层(用户态)返回时误走内核返回分支:jmp rcx 回到用户 rip 却不换回用户栈 → app 用内核栈继续跑 → 栈错位跑飞
- 改为把来源标记 push 在内核栈上(入口 push 0/1,出口 cmp [rsp] 判定),每层各压各的、天然可嵌套;内核返回路径 add rsp,8 弹掉本层标记槽
- 对齐保持:and rsp,~15 之后的深度 = 补齐8 + arg6 8 + call 8 = 24 ≡ 8 (mod 16) ✓(标记槽在 and 之前,不参与)
|
2026-10-14 18:56:40 +08:00 |
|
TermiNexus
|
5916822184
|
fix(vkernel/intr): syscall 入口按 rsp bit46 判来源并记标记,支持内核态发起(修三重故障)
- 现场:内核自动启动里直接调 .so 的 log_info(其内部走 `syscall`)→ 入口用 rsp bit47 判来源时,本系统内核栈是**低位**(boot 0x7d8d0 / 线程 ks≈0xbb6b000)被误判成用户态 → 多执行一次 swapgs → gs:[8] 读到垃圾 → rsp 变垃圾 → 三重故障(QEMU 窗口直接消失、无任何 dump)
- 入口改用 bit46(64TB)阈值:用户栈由 drop_to_user_mode 固定在 0x7fff_ffff_...(≈2^47)、内核栈 <1GB;并把来源记进全局 _syscall_from_kernel(不用 gs:[…]:引导早期可能还没 swapgs 过)
- 出口改为读该标记决定 sysretq(用户)或 jmp rcx(内核)。原 rcx>>47 判据两个错:覆盖 rax 返回值;且用户 rip(0x40xxxx) 与内核 rip(0x10xxxx) 都 <2^47,无法区分
|
2026-10-14 18:47:32 +08:00 |
|
TermiNexus
|
14e69f62f5
|
fix(vkernel): 不再 unmount 根文件系统,app 运行期才能读盘
- app 是 spawn 之后异步运行的,运行期仍要 fat32.open(load_so / 资产文件);原来在自动启动块后面立刻 fat32.unmount(),导致 app 第一次 LOAD_SO 就 "[so] failed to open file"
- 该 unmount 原本只为防“破坏性 FAT32 测试”改坏 disk.img,而那些测试已删除,留着只剩切断 app 文件访问的副作用
- 卸载能力保留给非根挂载点(U 盘/多分区),将来配挂载点表 + 引用计数
|
2026-10-14 18:42:24 +08:00 |
|
TermiNexus
|
6b2b24ccdf
|
fix(vkernel/elf): .so 符号“已定义”判据改用 st_shndx,修 log_info 恒 not found
- 根因:get_so_symbol 与两处重定位回退都以 `st_value != 0` 当“已定义”,但导出函数在自身段内偏移可以为 0(实测 slog.so:log_info 的 st_value=0x0、st_shndx=.text)→ 该符号被整条跳过 → 恒报 “[so] symbol not found”,内核自动启动里的 log_info 冒烟调用因此失败
- 改为只看 `st_shndx != SHN_UNDEF(0)`;同样修 R_X86_64_* 与 JMPREL/PLT 两处重定位回退(否则段内偏移 0 的内部符号不会被重定位)
|
2026-10-14 18:41:45 +08:00 |
|
TermiNexus
|
09b72614d9
|
fix(vkernel/intr): isr80(int 0x80) 修调用号/arg4 被覆盖 + 收尾原地 iretq;syscall stub 补 SysV 栈对齐
- isr80:prologue 原拿 rax/r10 当临时(取 CS 与 RFLAGS),之后 push rax/push r10 存进保存块的其实是 CS/RFLAGS → handler 收到乱掉的调用号与 arg4。现在入口先 push rax + push r10 保住二者,CS 判定改用内存操作数 `test byte ptr [rsp+24],3`,翻译段按新偏移取 arg4([rsp+168])
- isr80 收尾:原实现把帧字段 pop 进 r8/r9/r10/rax 再重新 push iret 帧,其中 `pop rax(RSP)` 会冲掉 handler 返回值,且 is_user 判断早被 `pop rcx`(arg2) 覆盖(等于没用)。改为跳过统一帧/保存槽回到 CPU 压下的原始帧直接 iretq:环切换与 RSP/SS 由 iretq 依 CS.RPL 自行处理(内核弹 3、用户弹 5),rax=返回值,其余寄存器全部原样恢复
- syscall stub:`and rsp,~15` 后直接 push arg6 使 syscall_handler 入口 rsp≡0;Viper 代码生成依赖标准 SysV(入口 rsp≡8,内核 printf 的 movaps %xmm0,0x60(%rsp) 即证据)→ 补 `sub rsp,8` 于 arg6 之前
|
2026-10-14 18:38:17 +08:00 |
|
TermiNexus
|
4f9660d23b
|
fix(vkernel/intr): 修正 syscall_entry 两处 rax 覆盖(系统调用号/返回值),用户 syscall 不再恒返回 0
- 入口:原 `mov rax,rsp; shr rax,47`(判入口来源)把 rax 中的系统调用号覆盖成 rsp>>47(用户态=0);随后 `mov rdi,rax` 使 syscall_handler 收到 num=0 → 落入未知调用 `return 0`。改为 `bt rsp,47; jc`(只读 CF,与旧判据等价,不占用 rax)
- 返回:原 `mov rax,rcx; shr rax,47`(判返回去向)把 handler 的返回值覆盖成返回地址>>47(用户地址 < 2^47 → 0)→ app 的 syscall trampoline 永远取到 0。改为 `bt rcx,47; jc`
- 现场:elf 自启动后 app 的 malloc→syscall 29(MMAP) 拿到 0 → 紧接着 `movq %r14,(%rax)` 写 NULL → 用户态 [0E][PF] cr2=0x0 err=0x7(hello rip=0x400153、terminal rip=0x40007c)
|
2026-10-14 18:31:24 +08:00 |
|
TermiNexus
|
077a150c92
|
feat(vkernel): 还原 elf 自启动(spawn 四个 app + slog.so 冒烟调用),改写为新语法
- 初版 6813947 的 main.py 在 FAT32 挂载后、破坏性测试前 spawn hello/terminal/gargantua/scene3d 并加载 slog.so 调 log_info;此前为排查内核概率性崩溃被 [DIAG]“app autostart disabled”禁用,现按要求还原
- 新语法改写:c.Addr(x) -> &x;t.CArray[t.CChar,N] -> [N]char;t.CInt -> int;t.CVoid|t.CPtr -> rawptr;c.Asm/c.AsmInp -> __asm__/asminp(调用符号用 intr/syscall.vp SO_CALL1 同款:rdi 传首参 + call 符号地址);字符串常量 -> const str
- 打印统一 [64]char + viperlib.snprintf + serial.puts(&buf),与同文件既有写法一致
|
2026-10-14 18:25:45 +08:00 |
|
TermiNexus
|
0de7cfad60
|
refactor(vkernel/paging): _split_1gb_to_pd 形参改为 uint64 物理地址
- 与本文件 MapPage/UnMapPage/_set_page_writable 同口径(都收 uint64 地址),不引入“结构体指针作形参”这种本文件没用过的写法,降低编译器特性风险
- 函数内自行重建 _p*;pdp_pa==0 直接返回 0
|
2026-10-14 18:19:03 +08:00 |
|
TermiNexus
|
c067ca76e3
|
fix(vkernel/paging): save/restore_pt_range 处理 1GB 大页,避免把大页内存当表项存回/写回
- save_pt_range:PDP 层遇 PTE_PS 整片跳过(前进 1GB)—— 当 PD 用会把大页内存内容当表项存进保存缓冲
- restore_pt_range:PDP 层遇 PTE_PS 跳过 —— 该情形正常无记录(save 已跳过),若出现说明缓冲陈旧/错配,绝不把 garbage 基址当 PT 写回(restore 是写路径,会写坏任意内存)
|
2026-10-14 18:18:27 +08:00 |
|
TermiNexus
|
1222d6caef
|
fix(vkernel/paging): clone_for_process 处理 1GB 大页(先拆 PD 再逐层拷贝)
- 源端 PDPTE.PS=1 时没有下层 PD:原实现会把大页内存内容当 PD 拷进新页表,再用该垃圾 PD 替换新地址空间的该条目(该区间映射被写坏,后续写入会落到任意物理内存)
- 现在先 _split_1gb_to_pd(kernel_pdp, pdp_idx) 拆成常规 PD(语义不变,512 个 2MB 条目仍映射同一物理 1GB),再沿用既有逐层拷贝;PD 层命中 2MB 时按既有 `if PTE_PS: return` 早退
|
2026-10-14 18:17:06 +08:00 |
|
TermiNexus
|
07f4dbbb78
|
feat(vkernel/paging): 1GB 大页拆分(1GB->PD),MapPage/MapLargePage/UnMapPage 不再把大页当 PD 写坏内存
- 新增 _split_1gb_to_pd:PDPTE.PS=1 时新建 PD 并把 1GB 拆成 512 个 2MB 条目(继承 PCD/PWT/USER/NX;新 PDPTE 是**非叶条目**,只带 PRESENT|WRITABLE|USER,绝不带 bit63(NX),否则 & ~0xFFF 派生出的表指针非规范 = 刚修的 #GP 根因);条目不存在/已是 PD 时直接返回 1,建 PD 仍由调用方既有逻辑负责
- MapPage:取到 PDPT 后先拆分,再走既有 2MB/4KB 逻辑(否则 pd.entries 会写进大页内存,读到随机“表项”)
- MapLargePage:同样先拆分,再写入 2MB 条目
- UnMapPage:先拆分再撤除命中的条目(此前会把大页内存内容当表项 → 往派生出的随机地址写 0,即“fb/pitch/w/h 被清零”那类隐性写坏)
|
2026-10-14 18:14:47 +08:00 |
|
TermiNexus
|
9119c74232
|
fix(vkernel/paging): 补 1GB 大页(PTE_PS)判据,消除偶发 #GP(0) 与任意内存写坏
- 现场:rip=0x11a93e 在 _set_page_writable 的 testb $1,(%rax,%rcx,8);rax 由 PDPTE & ~0xFFF 得到。固件 identity 映射的 1GB 大页条目会置 NX(bit63),& ~0xFFF 把它一起带出 → 派生“PD 基址”非规范 → 读表项即 #GP(0)(偶发取决于该 VA 是否落进 1GB 大页)
- _set_page_writable:PDP 层补 PTE_PS 分支(1GB 大页无下层 PD,W 位在条目自身),与既有 2MB 分支同口径
- MakePTWritable / MakeRangeRO:PDP 层遇 PTE_PS 跳过 —— 把 1GB 大页当 PD 解引用会把大页内存内容当表项(约一半 qword bit0=1),据此递归/写页表 → 任意内存被改(偶发数据损坏)并算出非规范指针
|
2026-10-14 18:12:15 +08:00 |
|
TermiNexus
|
30f47ca4f4
|
fix(vkernel/desktop): 时钟改为按时间每秒刷新 RTC,不再绑到渲染速率
现象:任务栏时间区域不会自己走,必须鼠标移过去才刷新。
根因:_draw_taskbar 里 RTC 缓存按“渲染次数”节流重读 ——
if _render_count % 30 == 0: rtc.rtc_read_time()
而渲染频率完全取决于活动量:空闲约 1 帧/秒 → 要 30 秒才重读一次 RTC;鼠标一移动渲染
变快,_render_count 迅速累加,很快命中 %30==0 → 时钟立刻跟上。这正是“必须鼠标移动
才刷新”的成因。
修改:
- desktop_thread 的“每秒”分支(cur_sec != _last_clock_sec)里先 rtc.rtc_read_time()
再标脏任务栏行 —— 时钟严格按秒刷新,与渲染速率解耦。
- _draw_taskbar 移除 _render_count % 30 的 RTC 重读,只取缓存值绘制。
|
2026-10-14 15:46:29 +08:00 |
|
TermiNexus
|
cc061b2ad3
|
fix(vkernel): 修复 PS/2 鼠标位移符号误判(|位移|≥128 时方向翻转、光标向左飞没)
根因(静态定位,非速度问题):PS/2 协议里第 2/3 字节是位移“幅值”,符号在第 1 字节的
bit4(X)/bit5(Y)。而驱动把位移按 int8 存储/解释,于是 |位移| ≥ 128 时字节最高位被当成
符号位:
- 向右快移(幅值 0x80~0xFF)被读成负数 → _cursor_x 一路减小 → 撞上 if _cursor_x<0
被夹成 0 → 表现为“光标往左飞没了”;
- int8 装不下 9 位有符号位移(符号位在 flags 里),结构上必然丢符号。
修改:
- drivers/input/mouse/mouse.vp:mouse_packet.dx/dy 与 byte2/byte3 改为 uint8,保存原始
幅值字节,消除 int8 截断;flags(含符号位)照旧保留。
- services/desktop.vp::_update_mouse:按协议用“幅值 + 符号位”重建带符号位移
(命中 flags.bit4/bit5 时幅值 -256),再做屏幕 Y 轴反向。
- USB HID 分支不动:USB 报告的 X/Y 本身即 int8 有符号,原解释正确。
|
2026-10-14 15:12:19 +08:00 |
|
TermiNexus
|
a44f55574c
|
fix(vkernel): 鼠标“飞”与刷屏性能 —— 帧缓冲改回写映射 + 鼠标驱动丢弃溢出包/细化分辨率
方案 A(帧缓冲映射):
- 去掉帧缓冲的 PTE_PCD,改为回写映射。原先非缓存映射下每次写屏直通设备,大块刷新极慢;
QEMU 下回写显示正确(TCG 不模拟缓存不一致),真机严格正确应改 write-combining(PAT),
已在代码注释里写明。
- 说明:“只刷脏行”本已是现状(_mark_rows_dirty 按区域标脏,全屏刷仅启动那一次)。
方案 C(鼠标驱动加固,drivers/input/mouse/mouse.vp):
- irq_handler 丢弃带 X/Y overflow 位(bit6/bit7)的包:此时设备给的是饱和值,直接采用
会产生 ±127 级跳变,表现为光标“飞”。
- init 中在 0xF6(Set Defaults) 之后用 0xE8/0x03 把分辨率设为 8 counts/mm,避免默认最粗的
4 counts/mm 造成每计数位移偏大。
|
2026-10-14 15:05:02 +08:00 |
|
TermiNexus
|
73a499b0c1
|
feat(vkernel): 桌面线程每秒校验 .bss 帧参数不变量(fail-fast 抓 .bss 侧写坏)
与 main.vp 的帧参数影子校验互补:那边抓的是 __init__ 栈局部量在启动阶段被写坏,
这边抓的是 .bss 全局在被写坏后、使用前被拦下。
- _SetFrameInvariants():在 start() 完成全部 .bss 赋值后记录影子
(_fb/_screen_w/_screen_h/_fb_pitch/_back_fb/_row_buf/_bg_row_buf/_dirty_rows)
- _CheckFrameInvariants():桌面线程每秒比对一次,不一致即打印 [FATAL]
(左右并列显示 实际值/期望值)并停机
这样两类写坏都能在使用前变成可定位现场,而不是继续静默破坏(如把后备缓冲刷到物理 0 起)。
|
2026-10-14 14:57:59 +08:00 |
|
TermiNexus
|
c0c83cdc83
|
feat(vkernel): __init__ 帧参数加影子副本校验(fail-fast 抓偶发写坏源)
fb/width/height/pitch 局部量曾被偶发清零(→ desktop.start 拿到 fb=0 → 刷新把后备缓冲
写到物理 0 起、覆盖内核自身而卡死/崩宿主 QEMU)。USB/UHCI 路径已排查:TD 块走堆分配、
QH 在 free 前置 terminate、枚举缓冲均为堆分配且长度有钳制,未见明显越界零写。
故加常驻轻量校验(非临时探针,可长期保留):
- _SetFrameShadow:BootInfo 读取后保存影子
- _CheckFrameLocals:在 after_usb / after_vga / pre_desktop 三处比对,不一致即打印
[FATAL](含期望值与实际值)并停机 —— 把“偶发写坏”变成可定位的现场,
并明确是哪一个阶段之间被写坏。
|
2026-10-14 14:56:26 +08:00 |
|
TermiNexus
|
9cf33ab374
|
fix(vkernel): _render 脏行行界由“夹取兜底”改为 fail-fast(越界即报错停机)
按“禁止兜底、改 fail-fast”的要求:_dirty_y_min/_dirty_y_max 是 .bss 全局,
被写坏时(曾观测 fb/pitch/w/h 被随机清零)原夹取会静默掩盖上游写坏源。
现改为越界立即打印 FATAL 并停机,直接暴露写坏。
合法的空脏行集合状态(_dirty_count==0、_dirty_y_max==-1、_dirty_y_min==h)已放行。
|
2026-10-14 14:55:40 +08:00 |
|
TermiNexus
|
b6cc64a5c9
|
chore(vkernel): 移除本轮排查加的全部临时诊断探针,并把 fb 为空改为 fail-fast
现象已定位清楚:fb=0 时刷新会把后备缓冲写到物理 0 起,覆盖 IVT/BDA + 低端内存 +
内核镜像自身(边跑边改坏自己正在执行的代码 → 卡死;宿主 QEMU abort 0x40000015)。
本轮实测 fb/pitch 正确时桌面渲染正常、[perf] 数值单调(.bss/中断那条线的修复生效)。
清理内容:
- main.vp:删除 _WhProbe 探针函数与 7 处调用、2 处 mm.DumpBuddy() 调用
- desktop.vp:删除 [rc] 阶段面包屑 7 处、[rc] preflush 转储、[dt] 每秒心跳、[dst] 入口探针
- mm.vp:删除已无调用点的 DumpBuddy()(保留 3 条 malloc 失败诊断——它们是错误信息,非调试噪音)
新增(fail-fast,替代静默兜底):
- desktop.start:fb_ptr == 0 时打印 FATAL 并停机,杜绝“刷新把内核自己写掉”这类灾难
- _render:脏行行界夹取保留(越界会静默写穿帧缓冲),但这是防破坏下限,非修复
|
2026-10-14 14:52:56 +08:00 |
|
TermiNexus
|
d58833d3b5
|
diag(vkernel): 增加 post_vga / pre_start 探针,夹出 fb/pitch 被清零的位置
[rc] preflush 显示 fb=0x0(帧缓冲指针为 NULL),[dst] entry 显示 pitch=0:
刷新循环把 8MB 后备缓冲写到物理 0x0 起 —— 覆盖 IVT/BDA + 低端内存 + 内核镜像自身
(0x100000 起),边跑边覆盖正在执行的代码 → 卡死;QEMU 因客户机跑飞
abort(0x40000015 = STATUS_FATAL_APP_EXIT)。
VGA 文本(315-317 行)用同一组 fb/pitch 画出来是正常的,说明清零发生在
317 行之后、323 行 desktop.start 之前。故加两条探针把元凶夹死:
_WhProbe("post_vga", uint32(fb), pitch) # 317 行后
_WhProbe("pre_start", uint32(fb), pitch) # 323 行前
若 post_vga 正常而 pre_start 为 0,元凶即这三条之一:
serial.puts("你好") / serial.puts("[main] starting desktop") / _WhProbe("pre_desktop")。
|
2026-10-14 14:47:53 +08:00 |
|
TermiNexus
|
0cee9612ae
|
diag(vkernel): 刷新循环前打印全部参与量(定向内存转储),定位跑飞来源
已给刷新/背景循环夹取行界,但现象不变(仍卡在 “[rc] vblank done” 之后、
没有 “[rc] flush done”)——说明跑飞的不是循环边界(_dirty_y_max),
而是循环体内的量:row_bytes/run_bytes 或指针,或 _dirty_rows 本身。
故在 _wait_vblank() 之后、刷新循环之前,一次性打印所有参与量:
dc dmin dmax w h pitch row_bytes
fb back dirty_rows row_buf bg_row_buf
任一异常即为被写坏的对象,且直接给出被写坏后的数值(量级)。
(这就是你说的“内存转储”的定向版:只打这块状态,编译快、迭代方便。)
|
2026-10-14 14:26:26 +08:00 |
|
TermiNexus
|
c9c507c67d
|
fix(vkernel): _render 的刷新/背景循环按屏幕高度夹取行界,避免脏行值被写坏时死循环越界写
面包屑定位:卡在 “[rc] vblank done” 与 “[rc] flush done” 之间 —— 即帧缓冲刷新循环。
_dirty_y_max 是 .bss 全局,一旦被写坏成很大的正数:
- 循环退化成死循环 → 卡死(故 [dt] 心跳一行都不打印);
- run_start*pitch 越写越远,写出 0x80000000 帧缓冲之外 → 落进 MMIO →
宿主 QEMU 被写崩(unknown software exception 0x40000015 = STATUS_FATAL_APP_EXIT),
且屏幕只留下顶部已写的那一小块。
两个循环(背景铺底 + 帧缓冲刷新)的行界现夹到 [0, h-1]。这不是根因修复
(.bss 仍被写坏),但:
- 止血:不再死循环、不再把宿主 QEMU 写崩;
- 兼作探针:[dt] 心跳里的 dymin/dymax 若明显越界,即可确认 .bss 被写坏的量级。
|
2026-10-14 14:18:21 +08:00 |
|
TermiNexus
|
cfbb0c1286
|
diag(vkernel): _render() 各阶段埋 [rc] 面包屑,定位卡死步骤
日志证据:本轮 [dt] 心跳一行都没打印 → 桌面线程没走到“每秒”分支,即卡在第一轮循环里;
而屏幕顶部出现一小块蓝色 → 很可能执行到第一次 _render() 中途卡住(写了一半)。
宿主 QEMU 随后 abort(0x40000015 = STATUS_FATAL_APP_EXIT)。
在 _render() 埋 6 个面包屑:
[rc] entry / bg done / taskbar done / windows done / cursor done / vblank done / flush done
最后打印的那条 = 卡住的那一步。若连 [rc] entry 都没有,则卡点在其之前
(_update_mouse / _update_keyboard / _heartbeat_check),再补那边。
定位后连同所有 [DIAG] 一并移除。
|
2026-10-14 14:13:10 +08:00 |
|
TermiNexus
|
164228e15a
|
diag(vkernel): 桌面线程每秒心跳,打印脏行/渲染/窗口/光标状态
现象:日志停在 “[desktop] thread started”,QEMU 卡死;屏幕只有上方一小块变蓝、
UEFI 文本未被覆盖 —— 指向渲染/刷新阶段的 _dirty_* 状态不正确(而非整屏写入卡住)。
在 desktop_thread 主循环的“每秒”分支打印:
[dt] sec=… dc=… dymin=… dymax=… rc=… win=… cx=… cy=…
作用:
- 若完全没有 [dt] 输出 → 线程在首轮之前就卡住(_mark_all_dirty 或循环顶部);
- 若有输出且 sec 递增 → 线程在跑,问题在渲染/刷新(dc/dymax 数值即可解释
“只刷顶部一小块”)。
定位后连同其它 [DIAG] 一并移除。
|
2026-10-14 14:08:56 +08:00 |
|
TermiNexus
|
3bb6a9a102
|
diag(vkernel): desktop.start 入口加形参探针(w/h/pitch 是否原样到达)
已排除/确认:
- main 的 __init__ 局部量正确(5 个 [wh] 探针全对,pre_desktop 仍为 1360/768);
- 调用点机器码正确(0x100A86: mov 0x10(%rsp),%esi = 1360);
- mm 模块 34 个函数全部合规(无“用到 callee-saved 却未 push”的函数)。
故在 desktop.start 入口打印收到的 w/h/pitch:
- 若此处已为 0 → 实参在调用/ABI 传递环节丢失;
- 若此处正常 → 丢失发生在函数内部(malloc/memset 之后,w 存于 ebx)。
据此继续定点,定位后连同所有 [DIAG] 一并移除。
|
2026-10-14 13:58:02 +08:00 |
|
TermiNexus
|
38ccc9cdf6
|
diag(vkernel): main __init__ 插 width/height 探针,定位局部槽被清零的阶段
“[desktop] dirty_rows/back_fb malloc FAILED”的实质是 width/height 被随机清零:
malloc(0) 直接返回 NULL;伙伴系统经 DumpBuddy 实测健康(free≈160MB,order 3/5..12/15 有块)。
ISR 不保存 rax/rcx/r8-r11 的问题已修(0c63969),但现象仍在 → 主线程栈另有写坏源。
在 __init__ 的 5 个阶段打印 w/h(decl → pre_pg → post_mm → pre_usb → pre_desktop),
二分出被写坏的区间;定位到具体调用后,连探针一起移除。
|
2026-10-14 13:50:48 +08:00 |
|
TermiNexus
|
66eb70b131
|
feat(vkernel): #GP 分支补 rbp 链回溯(与 #PF 分支同款)
#GP 此前只打印 rip/err/rsp/cr4/cr0,看不到调用链;#PF 与“其他异常”分支早已有
[STK] rbp 回溯。现给 #GP 补上同样的 5 级回溯(rbp + 4 个返回地址),
内核态 rbp 低地址直接回溯,用户态(>=0x80000000)跳过以免读用户栈嵌套 #PF。
用于定位当前 “paging.init 的 MakePTWritable 页表遍历读到坏表项 → #GP
rip=0x11a7be” 的调用来源。
|
2026-10-14 13:46:57 +08:00 |
|
TermiNexus
|
449f2dbc37
|
fix(mm): 加分配失败分步诊断;build.py 改用 --phase2command 产出 kernel.elf
- mm.vp: 在 malloc(slab 路径)、_create_page、allocate_block 三处失败分支各加一条
[mm] 诊断打印,用于定位“[desktop] dirty_rows malloc FAILED”
(768B → ≤SLAB_THRESHOLD → slab size class 5(1024B) → _create_page 需 order≥1 伙伴块;
DumpBuddy 显示伙伴空闲链健康,故需进一步区分是哪一步返回 NULL)
- build.py: kernel.elf 改为调用 viperc --phase2command 拿到“精确链接命令”再改写产出。
自行 glob .obj 去链会因对象顺序不同得到与 kernel.bin 不一致的段布局
(实测前 247KB 里 230KB 不同),无法把日志 rip 映射回函数。
|
2026-10-14 13:36:01 +08:00 |
|
TermiNexus
|
54e88ce957
|
feat(build): 额外链接带符号 kernel.elf;mm 增加 DumpBuddy 诊断
- build.py: 用同一批 .obj + 同一 linker.ld 再链一份
VKernel/__vpcbuild__/output/kernel.elf(仅去掉 --oformat binary/--strip-all),
段布局与 kernel.bin 一致 → 可把日志里的 rip 用 objdump/nm 映射回函数
- mm.vp: 新增 export DumpBuddy(),打印 arena start/end 与 total/used/free
及各阶空闲链表首块(上限 8 个),定位分配失败是“链表空”还是“块指针坏”
- main.vp: mm.init 之后、desktop.start 之前各调一次 DumpBuddy(),
用于排查“[desktop] dirty_rows malloc FAILED”
(768B → ≤SLAB_THRESHOLD → slab size class 5(1024B) → _create_page 需 order≥1 伙伴块)
|
2026-10-14 13:27:40 +08:00 |
|
TermiNexus
|
0c6396912b
|
fix(vkernel): ISR 保存/恢复 rax/rcx/r8-r11,修中断破坏被中断代码寄存器
根因:isr.s 的 ISR_NOERR/ISR_ERR 宏把 rax/rcx/r8-r11 当临时寄存器用
(读 RIP/CS/RFLAGS、算 is_user),却从未保存/恢复原值,rcx 更是根本不在
保存列表里。于是任意一次中断(定时器 1000Hz)只要落在“这些寄存器里存有
活跃值”的瞬间,就会破坏被中断代码的活跃值:
- r8 被写成被中断处的 RIP;rcx 被清 0(xor ecx,ecx / setnz cl 造 is_user)
实测吻合:DumpMapping 把 tag 放 rcx、va 放 r8,中断落点恰在其中 → 打印
“ [PG] (None) va=0x11ab82 ”,其中 0x11ab82 正是 DumpMapping 内部的 RIP;
同一机制把 main.vp 的 width 槽写成 0 → “ [desktop] back_fb malloc FAILED ”。
最初日志里的 [0D][GP] rip=0x130d45(vsnprintf 读 %d 变参)亦由此而来。
旧 TPC 编译的寄存器分配没踩到该窗口故“正常”,新版编译器踩到 → 概率性破坏
(对应 VKERNELERR.md 记录的“新版概率性崩溃”)。
- isr.s: 新增 ISR_SAVE_CLOBBERED / ISR_RESTORE_CLOBBERED 与 _isr_scratch;
入口第一件事保存 rax/rcx/r8-r11,各返回路径在 push 完 iretq 帧之后恢复
(iretq 从栈取帧,恢复寄存器不影响返回)
- mm.vp: arena 起点抬高后做 4KB 对齐(KernelEnd 未必页对齐),避免伙伴系统块错位
|
2026-10-14 13:06:26 +08:00 |
|
TermiNexus
|
0919a058a8
|
fix(vkernel): 预留内核 .bss 与栈区,修堆 arena 覆盖未预留内核内存
根因:bootloader 只按 kernel 文件大小 AllocatePages(.bss 不占文件),于是 .bss
尾部(ticks/_perf_* 等全局量)与主线程栈区(0x60000~0x80000)在 memmap 里仍是
EfiConventionalMemory,被内核 mm 的堆 arena 当作空闲内存占用 → 内核全局量被写坏、
堆元数据被反向破坏,最终桌面线程栈上的 va_list 被改坏,vsnprintf 读 %d 变参时
#GP(rip=0x130d45, err=0x0)。
- linker.ld: .bss 末尾加 LONG(0) 强制 PROGBITS,使 kernel.bin 的 FileSize 涵盖
.bss,bootloader 的 PagesNeeded 得以预留整块内核镜像(含 .bss)
- mm.init: 新增 KernelAddr/KernelEnd,挑 arena 时把与内核镜像 [kernel_PhysAddr,
__bss_end) 重叠的 region 起点抬到镜像末端(不整段丢弃)
- main.vp: 取 __bss_end 并传入 mm.init
- boot.c: ExitBootServices 前用 AllocatePages(AllocateAddress, EfiLoaderData)
占位内核栈区 0x60000~0x80000,使其在 memmap 中标记已用
另含在途整理:多处补显式收窄转换(uint8()/uint16() 等)、kbd_svc 构造改为
kbd_svc.__init__() 等。
|
2026-10-14 12:54:43 +08:00 |
|