Files
Deshab-OS/docs/RE/Bug记录.txt
T
xyou 019585f2bf feat: B7阶段3 NVMe MSI/MSI-X中断驱动化 + IDT向量分配器 + 幽灵CQE防御
- nvme.drv: MSI-X表项编程 + 动态向量(0x40-0xDF池) + 完成槽+irqsave自适应轮询混合模型 + CID校验防御幽灵CQE(BUG-20260803-010) + g_cid跳0 + handler自写LAPIC EOI
- IDT: 向量分配器(bitmap+ctzll, O(1)) + irq_vector_alloc/free (kernel_api尾部追加)
- FUCK: apic_route=1 (生产配置启用IOAPIC->LAPIC接管)
- LESSONS_LEARNED: B7阶段3经验教训(向量分配器/MSI编程顺序/混合轮询/幽灵CQE)
- Bug记录: BUG-20260803-010 (QEMU MSI-X unmask边界幽灵CQE)
2026-08-04 18:36:09 +08:00

404 lines
50 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.
# Deshab Bug 记录
> 所有测试与开发过程中发现的 bug 统一登记于此。每个 bug 按 BUG-YYYYMMDD-NNN 编号。
> 测试用例文档位于 RE/test-cases/。
>
> 2026-07-29 文档治理:此前 8 条遗留纯文本 bug 已按模板补全(编号 001–008,
> 日期为桌面测试期的近似日期);同时补登 B5/B6 及 BSS NOBITS、go_up 条目。
## 严重程度定义
- P0 阻断: 影响主链路启动或核心功能完全不可用(必须立即修复)
- P1 严重: 功能可降级但关键路径受损(本轮内修复)
- P2 一般: 非关键路径异常或体验问题(下一里程碑前修复)
- P3 提示: 日志/小问题,不影响功能(记录备查)
## Bug 条目格式
```
### BUG-YYYYMMDD-NNN [P级] [模块] 简述
- 现象:
- 复现步骤:
- 串口/日志证据:
- 根因分析:
- 修复方案:
- 状态: [OPEN/FIXING/FIXED/CLOSED]
- 修复提交: <commit hash 或 "待提交">
- 验证: <验证方法与结果>
```
---
## Bug 列表
<!-- 在此追加 bug 条目,最新放在顶部 -->
### BUG-20260803-009 [P3] [DKM/apic] PIT mode3 方波在 IOAPIC edge 触发下每周期双投递:100Hz 设定实测 200Hz(tick 2x 定论)
- 现象: apic_route=1 接管验证时,500ms 窗口数到 ~100 ticks(PIT ch0 mode3 设定 100Hz,预期 50);tick handler 计数恰为 2 倍,且持续投递窗口(sustain)同样双倍。
- 复现步骤: FUCK [drivers] apic_route=1 启动 → ROUTE VERIFY 阶段计数复现;apic_diag=1 时 HPET 对拍实验自动输出定论。
- 串口/日志证据: `.build_tmp/tests/apicroute-b7p2-071006.log`:`calib window(us)=10132` / `verify window(us)=504883` / `tick rate(Hz x10)=2000` / `route verify ticks=101` / `sustain window ticks=100` / `TICK2X VERDICT: PIT mode3 double-clocked on IOAPIC edge (~200Hz real)`。
- 根因分析: HPET 绝对时基(ACPI HPET 表 → MMIO 主计数器,QEMU 100MHz)对拍逐一排除候选假设:① "TSC 校准测半"——calib 窗口实测 10132us ≈ 10.13ms,准确,排除;② "验证窗口偏长"——实测 504883us ≈ 504.9ms,准确,排除。窗口与校时皆正确而计数双倍,定论为 PIT mode3(方波发生器)OUT 引脚每个全周期含上升沿+下降沿各一次翻转,QEMU IOAPIC edge 触发重定向项的输入采样等效双沿触发 → 每 PIT 周期产生 2 次中断,真实投递频率 ~200Hz。与 LESSONS_LEARNED 2026-08-03 B7 阶段1 记录的 "PIT mode3 计数器每 CLK 减 2"(同一硬件特性:mode3 半周期翻转)互证。
- 修复方案: 无需修复(观测性定论,非功能缺陷;EOI 钩子链路与窗口校时均验证正确)。后续约束:① route 模式下凡按 PIT 中断次数计时的代码须按 200Hz 折算,或改用 PIT mode2(rate generator,每周期单脉冲)根除双触发;② 时延派生一律走 HPET/TSC 绝对时基,禁止用 tick 计数当时间源;③ apic_diag 开关保留(FUCK 默认 0),真机冒烟时可复开对拍。
- 状态: [CLOSED]
- 修复提交: 工作区(CODE/DKM/apic/apic.c:HPET 表解析 + 主计数器时基 + TICK2X VERDICT 对拍实验)
- 验证: apic-route-verify.ps1 18 项全 PASS(route=1+legacy=1+diag=1);boot-regression.ps1 B1-B5 全 PASS(route=1+legacy=1+diag=0,B1 向导按键全程走 IOAPIC→LAPIC→IDT→ps2kbd 链路);route=0 回归 B1+B2 PASS(PIC 锚点在、钩子未注册、行为与旧版一致)。
### BUG-20260801-008 [P1] [tools/desktop_app.h] da_mouse_init 读配置字前未冲刷输出缓冲:残留字节写回控制器配置 → KBD 时钟禁用位被置位 → 键盘控制器级全灭且跨应用持续
- 现象: dev_mode shell 中 `run FILEMAN.ELF`(或 BROWSER.ELF)后应用内键盘全灭:Esc/方向键/回车均无响应;鼠标移动/点击正常。退出应用返回 shell 后键盘仍不恢复(会话级持续,直至重启)。editor(不调用 da_mouse_init)与启动初期 shell 键盘正常。
- 复现步骤: dev_mode shell → 键入 `run FILEMAN.ELF` 回车 → fileman 内按 Esc/方向键 → 无响应、串口零输出。
- 串口/日志证据: `.build_tmp/w3b_s2_tools.log`:`[fileman] boot` 后 monitor sendkey esc/down/ret 串口零增量、screendump 选区不动;mouse_move 光标正常移动、mouse_button 点击可选中条目(FIRSTINT.XT 高亮)并可点关闭按钮退出(`[fileman] close button clicked` → `[shell] run: tool returned`);退回 shell 后 sendkey i/d/ret 仍零输出(键盘未恢复)。运行中 fileman.elf 反汇编证实 da_mouse_poll 与键盘轮询指令顺序均正确(`test al,0x21` 后才 `in 0x60`),排除 BUG-20260801-003 残留。
- 根因分析: da_mouse_init 序列中 `outb(0x64,0x20)`(读控制器配置字)之前未冲刷输出缓冲。shell 启动应用时最后键入的回车 break code(0x9C)或鼠标流字节残留于输出缓冲 → OBF 等待立即返回、`inb(0x60)` 读到残留字节而非配置字 → `cfg|=0x02; cfg&=~0x20` 后写回 → 残留字节 bit4(0x10)=1(0x9C 即满足)置位配置字 KBD 时钟禁用位 → i8042 控制器级禁用键盘,且应用退出后无人恢复该配置 → 会话级键盘全灭。另:鼠标复位(0xFF)实际返回 FA AA 00 共 3 字节,原实现仅读 1 字节,残留响应同样可污染后续配置字读写。desktop/main.c:ps2_mouse_init(键盘鼠标长期共存的参照实现)首尾均有 ps2_drain 且读完全部响应,故桌面不受影响。
- 修复方案: da_mouse_init 对齐 desktop/main.c:ps2_mouse_init:①序列开头新增 da_ps2_drain() 冲刷残留;②配置字 `cfg &= ~0x20; cfg |= 0x02; cfg |= 0x40`(补键盘翻译位,与 desktop/mouseInit 0x47 一致);③复位后有界读完 FA AA 00 全部响应、使能数据报告后读完 ACK;④结尾再次 da_ps2_drain()。
- 状态: [FIXED]
- 修复提交: 工作区已修复(CODE/tools/desktop_app.h da_mouse_init + 新增 da_ps2_drain),2026-08-04 全量构建闭环(BUILD.lock 互斥):fileman.elf 46848→46888B、browser.elf 48896→48928B、editor.elf 同步重编(MAKEFILE 无头文件依赖,已删 .o 强制重编),ISO/deshab.img 768MB 重打包 + SATA 镜像重建,构建零警告。
- 验证: 2026-08-04 QEMU 验收 12/12 ALL PASS(`.build_tmp/w4a/w4a_bug008.log`,-snapshot,monitor 45601,WHPX):dev_mode shell `run FILEMAN.ELF` → `[fileman] boot`(L4794)→ sendkey 方向键选区移动(screendump w4a_fm1/fm2.ppm 像素差异采样 1831)→ ret 打开条目(w4a_fm3.ppm 差异 2170)→ esc → `[fileman] exit`(L4803)→ `[shell] run: tool returned`(L4804)→ 键入 `id` → `uid=0(root) gid=0(root)`(L4806,键盘跨应用存活闭环);`run BROWSER.ELF` → `[browser] boot`(L5197)→ esc → `[browser] exit`(L5198)→ tool returned(L5199)→ `id` → `uid=0(root)`(L5201)同链路通过;渲染 screendump w4a_fm1/fm2/fm3/br1/shell.ppm 均正常。回归:boot-regression.ps1 PASS(257.8s,B1 首启向导/B2 正常启动/B3 dev_mode/B4 无盘降级/B5 无 NIC 全过,无 [PANIC]);userapps-matrix.ps1 系统行为全 PASS(dev_tests/help/pci 存活/Esc→cmd→desktop 链路/fileman boot/browser boot),唯一 FAIL 为 editor 断言陈旧——脚本仍期待 BUG-002 修复前的 "run: ELF 加载失败",实测 `[editor] boot`→`[editor] exit` 正常(userapps-matrix-125211.log L5066-5067),该断言升级属测试脚本维护范畴,非系统回归。
### BUG-20260801-007 [P1] [UTSM/pe] PE32 解释器 shim fn64 回退把 guest 指针当 host 指针:shim_printf 解引用 0x401017 → #PF → 三重故障
- 现象: `pe HELLO32.EXE` 解释执行时串口止于 "[PE] running PE32 via x86emu32",无 "Hello from PE32!",guest 三重故障,QEMU(-no-reboot)静默退出。TCG 与 WHPX 表现一致。
- 复现步骤: x86emu32-coverage.ps1(AUTOEXEC `pe HELLO32.EXE`)→ 会话在 "[PE] running PE32 via x86emu32" 后立即退出。
- 串口/日志证据: `.build_tmp/tests/x86emu32-tcg-134351.log` 尾部止于 "[PE] running PE32 via x86emu32";此前 QEMU `-d int` 捕获 v=0e(#PF)RIP 落在 shim_printf 函数体内(utsm.elf 反汇编确认),无 "[PE32] emulator error"——**解释器本身无差错,故障在 host 侧 shim**。
- 根因分析(静态全链定案):
1. pe_shim.c shim 表条目第 4 初始化元全为 0(如 `{"msvcrt.dll","printf",(void*)shim_printf,0}` 只设 fn64)→ **全表 fn32=NULL,pe_shim.h 设计的 fn32 列("从模拟栈读参数调用 fn32")整体缺失**。
2. x86emu32.c try_iat_call() 拦截 CALL IAT(合成地址 0x0001xxxx)后 fn32 为空 → 走 fn64 回退 `f(a0,a1,a2,a3)`,a0..a3 是从模拟栈读出的**原始 32 位 guest 值**:hello32 的 `push 0x401017`(msg guest VA = image_base 0x400000 + 0x1017)→ a0=0x401017。
3. shim_printf(pe_shim.c L219)`const char *f = (const char *)fmt;` 把 guest VA 直接当 host 指针解引用,**无 guest→host 转换**(解释器映像驻留在 emu->mem 大缓冲区,正确地址应为 emu->mem + 0x401017)→ host 低地址 0x401017 未映射 → #PF → BUG-20260801-005 放大为三重故障。
拦截前链路均正确:0x68 push imm32、FF /2 `call [disp32]` ModRM 解码、read_rm32 读 IAT 合成地址、try_iat_call 参数读取——解释器覆盖率不记倒退。
- 修复方案(系统侧,本轮测试任务约束不改系统代码,仅登记): ①为每个含指针参数的 shim 实现 fn32(接收 esp_ptr=&emu->mem[esp],guest VA 经 emu->mem 基址转换为 host 指针);②或 fn64 回退前设置全局 g_emu_mem_base,shim 内对 <0x10000000 的指针统一加基址转换;③kernel32 WriteFile/ReadFile 等指针参数函数同理。
- 状态: [OPEN]
- 修复提交: 待提交
- 验证: 修复后 x86emu32-coverage.ps1 应见 "Hello from PE32!" → "[cmd] PE run end" → "[DSK] cmd.elf returned";pe-compat.ps1 会话 B 同步验证(两处均已留自动升级断言:日志出现即升 PASS)。
### BUG-20260801-006 [P1] [UTSM/pe] pe_shim setjmp/longjmp 跨已返回函数 UB:longjmp 恢复脏栈寄存器 → 野指针 call → 三重故障
- 现象: `pe HELLO64.EXE` 原生执行成功打印 "Hello from PE32+!"(BUG-004 修复后),随后在 ExitProcess 阶段 guest 三重故障,QEMU(-no-reboot)静默退出;后续 AUTOEXEC 命令(HELLO32/NONEXIST/exit)全部不再执行。TCG 与 WHPX 表现一致。
- 复现步骤: pe-compat.ps1(AUTOEXEC `pe HELLO64.EXE`)→ 串口止于 "Hello from PE32+!"。
- 串口/日志证据: QEMU `-d int` 追踪(`.build_tmp/tests/pe-int-dbg.log`):
`v=0e e=0003 IP=0028:ffffffff80014796 SP=0030:ffffffff80003030 CR2=ffffffff80003028`(**写保护违例**,非缺页)→ 投递 #PF 即 #GP(BUG-005 放大)→ v=08 → Triple fault。
现场寄存器:R12=0x6666666666667830("0xfffffff")、R13=0x3033326530386666("ff80e230")——正是 log_hex64("[PE] stack_top=",0xffffffff80e23000) 格式化缓冲区 ASCII 残片;R14/R15/RBP 均为脏值。
反汇编定案(utsm.elf):fault RIP `pe_service_run+0x226` = `call *%r15`(pe_call_on_stack 内联汇编调 PE 入口那条);但 R15=0xffffffff8055b260 并非 PE 入口 0xffffffff80d22000、RSP=0xffffffff80003030 并非 PE 栈顶 0xffffffff80e23000——**这是 longjmp 用脏栈"恢复"出来的假现场**,不是正常调用路径。
pe_shim_setup_exit(0xffffffff80014d60)反汇编:clang 把 __builtin_setjmp 实现为 `g_exit_jmp={RBP,RSP,resume=0x80014d94}`,恢复后从 0x80014d94 起执行函数尾声 `pop rbx;pop r12;pop r13;pop r14;pop r15;pop rbp;ret`——**从恢复的栈上弹 7 个槽**。
- 根因分析: pe_shim_setup_exit() 内调 __builtin_setjmp 后**即返回**(C 标准:含 setjmp 的函数已返回时再 longjmp 为未定义行为)。setjmp→longjmp 之间 pe_service_run 又调用 log_hex64×2("[PE] entry="/"[PE] stack_top="),其栈帧**精确复用** pe_shim_setup_exit 尾声要弹的那片栈区 → longjmp 尾声 pop 出的 rbx/r12-r15/rbp 全是 log_hex64 帧残片(R12/R13 即十六进制字符串残片),最后 `ret` 弹到 [X-8] 槽位的残留返回地址 0x8001478c(log_hex64 调用点返回地址)→ 落到 pe_call_on_stack 内联汇编正中间(`mov %rsp,g_pe_saved_rsp; mov %r14,%rsp; call *%r15`)→ 以脏 R14 为栈、脏 R15 为入口野调用 → 写返回地址到只读页 0xffffffff80003028 → #PF(保护违例) → BUG-005 放大为三重故障。PE32 解释器路径同一 setjmp/longjmp 机制,同样暴露在 UB 下(只是中间无 log 调用、脏数据不同,崩溃形态可能不同)。
- 修复方案(系统侧,本轮测试任务约束不改系统代码,仅登记): ①把 setjmp 下沉到 pe_service_run 本体内(含 PE 调用全程存活的作用域),不要用"setjmp 包装函数";②或弃用 __builtin_setjmp/longjmp,改用显式上下文保存结构(保存 callee-saved + RSP/RIP 到 pe_service_run 帧内,ExitProcess 经受控 trampoline 返航);③PE32 路径同理。
- 状态: [OPEN]
- 修复提交: 待提交
- 验证: 修复后 pe-compat.ps1 应见 "[PE] ExitProcess code=" → "[cmd] PE run end" → "[DSK] cmd.elf returned" 完整链路;x86emu32-coverage.ps1 动态链路同步验证。
### BUG-20260801-004 [P1] [tools/pe-samples] gen_pe.py PE32+ 二进制双重构造缺陷 → 原生执行三重故障
- 现象: `pe HELLO64.EXE` 原生执行时 guest 直接三重故障,QEMU(-no-reboot)进程退出;串口最后一行为 `[PE] stack_top=...`,无 "Hello from PE32+!"。WHPX 下表现为 "Unexpected VP exit code 4",TCG 下同样退出(此前误判为 WHPX 平台限制)。
- 复现步骤: pe-compat.ps1(AUTOEXEC `pe HELLO64.EXE`)→ 会话在 `[PE] running PE32+ natively` 后退出。
- 串口/日志证据: QEMU `-d int,cpu_reset` 追踪(`.build_tmp/pe-dbg-qemu.log` 尾部):`v=0e e=0010 IP=0028:...0300 CR2=0x300`(在 0x300 取指缺页)→ 投递 #PF 时 #GP → `v=08` → Triple fault。RIP=0x300 正是未被 shim 修补的 IAT[0] 原始值(printf 名称 RVA)。
- 根因分析(双重缺陷,均属 gen_pe.py 构造错误):
1. **call disp32 写错偏移**:call 指令位于 code[7..12](FF 15 + disp32),disp32 应写偏移 9,实际写偏移 10 → disp 左移一字节变为 0x1F300,同时覆盖下一条指令首字节。
2. **import 区 RVA 全部缺 +0x1000(code_rva)偏置**(修复①后仍失败的真正主因):build_pe32_plus/build_cmd_exe 中 printf_name/exitproc_name/msvcrt/kernel32/import_desc 写成裸缓冲区偏移 0x300/0x320/0x350/0x360/0x400 → 数据目录 Import RVA=0x400 落在 headers 区 → rva_to_offset 返回 0xFFFFFFFF → resolve_imports 静默返回 → IAT 永不被 shim 修补 → `call [rip+0x1F3]` 跳到 0x300 → #PF → 三重故障。build_pe32 一直是正确的(0x1300 等带偏置),PE32+ 两个 builder 抄错。
3. 放大器(系统侧,另立 BUG-20260801-005):DSK 阶段任何 CPU 异常都无法投递,直接三重故障重启,串口零诊断。
- 修复方案: ①两处 disp32 pack_into 偏移 10→9;②build_pe32_plus/build_cmd_exe 的 name/DLL/import_desc RVA 全部加 code_rva 偏置(0x1300/0x1320/0x1350/0x1360/0x1400),IAT thunk 同步修正;③重新生成 hello64.exe/hello32.exe/cmd.exe 并同步 SYSTEM/bin/。
- 状态: [FIXED]
- 修复提交: 工作区已修复(CODE/tools/pe-samples/gen_pe.py build_pe32_plus/build_cmd_exe)
- 验证: hello64.exe 静态校验全绿——.text `call [rip+0x1F3]`→IAT 0x1200、ImportDir RVA=0x1400、desc0/desc1 OFT/FT=0x1200/0x1210、Name=0x1350/0x1360、IAT thunk=0x1300/0x1320;pe-compat.ps1 复测。
### BUG-20260801-005 [P1] [UTSM/IDT] DSK 阶段 CPU 异常无法投递:#PF 投递即 #GP → 三重故障重启,串口零诊断
- 现象: DSK/cmd/PE 阶段一旦发生 #PF/#GP 等异常,guest 立即三重故障,QEMU(-no-reboot)静默退出,串口无任何异常信息(无向量号/RIP/CR2)。
- 复现步骤: 运行含缺陷的 HELLO64.EXE(BUG-004,call 到未映射地址 0x300)→ QEMU 直接退出;或任何 DSK 阶段空指针解引用。
- 串口/日志证据: QEMU `-d int` 追踪(`.build_tmp/pe-dbg-qemu.log`):`check_exception old: 0xffffffff new 0xe`(#PF at RIP=0x300, CR2=0x300, RSP=0xffffffff80e22ff0 栈有效)→ `old: 0xe new: 0xd`(**投递 #PF 时即 #GP**)→ `old: 0x8 new 0xd`(投递 DF 时再 #GP)→ Triple fault。现场:IDT=ffffffff8002b000 lim=0xfff,GDT=ffff80007fe3d000 lim=0x47,CS=0028/SS=0030,TR=0038 且 TSS base=0。
- 根因分析: BUG-20260729-013 的 `idt_halt_all()` 把 256 个 IDT 条目换成 halt stub,意图 cli;hlt 安全停机;但实测异常**投递本身**就 #GP——halt 门描述符的选择子/IST 与 DSK 阶段 GDT/TSS(TSS base=0)不匹配,异常帧无法压栈。净效果:DSK 阶段是无异常容错的裸奔环境,任何 guest bug 都表现为"QEMU 静默退出",测试侧只能开 `-d int` 才能定位。
- 修复方案(系统侧,本轮测试任务约束不改系统代码,仅登记): ① halt IDT 门用 DSK 阶段 GDT 内有效选择子重建、IST=0;② 或 DSK 入口自建最小 IDT(#PF/#GP/#DF 打印向量+CR2+RIP 后 cli;hlt);③ 恢复 TSS 有效基址。
- 状态: [OPEN]
- 修复提交: 待提交
- 验证: 修复后重跑含缺陷 PE:串口应输出异常诊断而非 QEMU 静默退出。
### BUG-20260801-003 [P1] [tools/desktop_app.h] da_mouse_poll 吞键盘扫描码导致应用键盘全灭
- 现象: fileman/browser/editor(所有 #include desktop_app.h 的全屏应用)内键盘输入完全无效:Esc 无法退出、方向键/回车无响应;应用只能由宿主动作(关机/会话终止)结束。
- 复现步骤: dev_mode shell → `run FILEMAN.ELF` → 启动后按 Esc → 无 `[fileman] exit`,应用永久停留。
- 串口/日志证据: `.build_tmp/tests/userapps-matrix-021449.log`:`[fileman] boot` 后 3 次 Esc(每次等 20s)均无 `[fileman] exit`。
- 根因分析: `da_mouse_poll()`(desktop_app.h L287-291)先 `inb(0x64)` 判有数据即 `inb(0x60)` 读走字节,再判 `st & 0x20` 发现是键盘字节直接 return 丢弃 → 键盘扫描码被鼠标轮询消耗。应用主循环每轮先调 da_mouse_poll 再轮询键盘 → 键盘字节永远到不了键盘分支。desktop/main.c 的 `ps2_mouse_poll()` 顺序正确(先判 0x20 再读 0x60),桌面不受影响;desktop_app.h 注释自称"与 desktop 保持一致"但实际不一致。
- 修复方案: `da_mouse_poll()` 调整为:`(st & 1) && (st & 0x20)` 时才 `inb(0x60)`,与 desktop/main.c ps2_mouse_poll 对齐。
- 状态: [FIXED]
- 修复提交: 工作区已修复(CODE/tools/desktop_app.h da_mouse_poll:先判 AUX 位 st&0x20 再读 0x60)
- 验证: QEMU 实测(`.build_tmp/w3b_s2_tools.log`):editor 键盘输入与 Esc 退出正常(`[editor] boot`→`[editor] exit`→`[shell] run: tool returned`);fileman 鼠标移动/点击选中/关闭按钮退出正常;运行中 fileman.elf 反汇编确认键盘字节(st&0x20==0)不再被鼠标轮询消耗。注:fileman/browser 键盘在修复后仍全灭,深挖定位为另一独立根因——da_mouse_init 配置字污染(BUG-20260801-008),该条目修复后 `[fileman] exit`/`[browser] exit` 断言方可完全恢复。
### BUG-20260801-002 [P1] [shell] g_run_image 仅 256KB,editor.elf(496KB 内存镜像)run 拒绝加载
- 现象: dev_mode shell 中 `run EDITOR.ELF` 输出 "run: ELF 加载失败(无效格式或过大)",编辑器无法从 shell 启动(桌面宿主 g_elf_image=1MB,可正常启动)。
- 复现步骤: dev_mode shell → `run EDITOR.ELF`。
- 串口/日志证据: `.build_tmp/tests/userapps-matrix-021449.log` L4821/5167/5513 "run: ELF 加载失败(无效格式或过大)" ×3。
- 根因分析: shell `g_run_image[262144]`(256KB)上限;editor.elf 文件 43KB 但 BSS NOBITS 内存镜像 496KB(编辑器大文本缓冲)→ `sh_load_elf` 返回 -5。其余工具内存镜像均在限内:fileman 208KB / browser 116KB / ping 128KB / curl 200KB。
- 修复方案: `g_run_image` 扩至 1MB(与 desktop g_elf_image 对齐),或改为按 ELF memsz 动态划分。
- 状态: [FIXED]
- 修复提交: 工作区已修复(CODE/shell/main.c g_run_image 262144→1048576,与 desktop 宿主 g_elf_image 对齐)
- 验证: QEMU dev_mode shell 实测(`.build_tmp/w3b_s2_tools.log` L4732-4735):`run EDITOR.ELF` → `[shell] run: jumping to tool` → `[editor] boot` → `[editor] exit` → `[shell] run: tool returned`,editor.elf 496KB 内存镜像成功载入 1MB 缓冲并运行、退出;userapps-matrix.ps1 的 `[editor] boot` 断言应可升级。
### BUG-20260801-001 [P1] [shell] 扫描码 0x15/0x17/0x0B 被误当 Ctrl+U/W/K,字母 y/i/0 无法输入
- 现象: shell 交互输入中字母 i、y、数字 0 被吞:`id` 变 "d"、`ping` 变 "ng"、`whoami`/`exit`/`editor` 等含 i 命令均无法键入;串口回显 "未知命令: d" / "未知命令: ng"。
- 复现步骤: dev_mode shell 提示符下键入 `id` 回车(QEMU monitor sendkey 或真实键盘均可复现)。
- 串口/日志证据: `.build_tmp/tests/userapps-matrix-021449.log` L4431/4438/4445 "未知命令: d" ×3,L4460/4467/4474 "未知命令: ng" ×3。
- 根因分析: shell 主循环(CODE/shell/main.c L3399-3401)将扫描码 0x15(y)/0x17(i)/0x0B(0) 直接映射为 Ctrl+U(kill_line)/Ctrl+W(删整行)/Ctrl+K(kill_to_end) 快捷键,但 shell 未跟踪 Ctrl 修饰键状态,也无 Ctrl 按下判定 → 普通按键被当作快捷键吞掉。`ping` 的 p 插入后 i 触发"删整行"→ 剩 "ng"。
- 修复方案: 增加 Ctrl 修饰键跟踪(0x1D/0x9D 左右 Ctrl),仅在 Ctrl 按下时启用 0x15/0x17/0x0B 快捷键分支。
- 状态: [FIXED]
- 修复提交: 工作区已修复(CODE/shell/main.c:新增 ctrl 变量跟踪 0x1D 按下/0x9D 释放,0x15/0x17/0x0B 快捷键分支加 && ctrl 条件)
- 验证: QEMU dev_mode shell monitor sendkey 实测(`.build_tmp/w3b_s1_shell.log`):`id` → 输出 `uid=0(root) gid=0(root)`(L4405,i 不再被吞);吞键对照测试 `y0i ok` 完整回显(L4407,y/0/i 三键全通,修复前分别触发 Ctrl+U/W/K 被吞);`ping` → `[ping] boot`(L5214,i 不再被吞)。Ctrl+U/W/K 快捷键逻辑保留,仅在按住 Ctrl 时生效。userapps-matrix.ps1 的 id/whoami/ping MANUAL 断言可升级。
### BUG-20260729-021 [P2] [DSK/kernel_api] 硬偏移读取 kernel_api.block 脆弱性
- 现象: DSK 通过 `*(u64*)(api + 0xA8)` 硬偏移读取 `dkm_kernel_api.block`,若结构体在 `block` 字段前插入新字段,DSK 将读取到错误指针 → 解引用 → 立即崩溃,无日志。
- 复现步骤: 修改 dkm.h 中 `dkm_kernel_api` 在 `block` 字段前增加任意字段 → 编译 DSK(不重新编译)→ 启动。
- 串口/日志证据: 安全审查Agent报告(bd89e1b9),4.1节。
- 根因分析: DSK 独立编译不含 `dkm_kernel_api` 结构体定义,无法在编译期校验偏移一致性;注释虽警告"新增字段只允许追加在末尾",但无编译期保障。
- 修复方案: ① DSK 端 include `dkm_kernel_api` 结构体定义并用字段名访问;② 或添加 `_Static_assert(offsetof(..., block) == 0xA8, ...)` 编译期校验。
- 状态: [FIXED]
- 修复提交: 工作区已修复(CODE/dsk/main.c + CODE/shell/main.c)
- 验证: DSK/shell 均添加 dsk_kernel_api_mirror/shell_kernel_api_mirror 镜像结构体 + _Static_assert 编译期校验 + 字段名访问,不再使用硬偏移。
### BUG-20260729-020 [P1] [UTSM/IDT] IOAPIC 中断路由可能绕过 PIC mask
- 现象: 实机通过 IOAPIC 路由中断时,`dsk_load_and_jump()` 中的 PIC mask 不影响 IOAPIC redirection entry,中断可能绕过 PIC mask 直接到达 CPU。
- 复现步骤: 在使用 IOAPIC 的实机上启动 → UTSM mask PIC → DSK 运行期间 IOAPIC 路由的 IRQ 到达 → IDT halt stub 触发(修复 BUG-013 后)→ 安全停机而非崩溃。
- 串口/日志证据: 系统日志Agent报告(aa2d3f5a),崩溃点B分析。
- 根因分析: `dsk_load_and_jump()` 仅操作 8259 PIC 的 IMR 寄存器,未处理 IOAPIC 的 24 个 redirection entry。实机(尤其是多核平台)中断路由通常经 IOAPIC 而非 PIC。
- 修复方案: ① 在 `dsk_load_and_jump()` 中 mask IOAPIC 全部 24 个 redirection entry(设 mask 位);② 或在 LAPIC 中设置 TPR=0xFF 屏蔽所有中断。
- 状态: [FIXED]
- 修复提交: 工作区已修复(CODE/UTSM/kernel/dsk_loader.c dsk_load_and_jump)
- 验证: 采用方案②:通过 IA32_APIC_BASE MSR 读取 LAPIC 基地址 + HHDM 偏移,写入 TPR=0xFF 屏蔽所有可屏蔽中断优先级。仅在 LAPIC 全局启用时(bit 11)执行。
### BUG-20260729-019 [P1] [UTSM+DSK/FAT32] FAT 表缓冲区在实机大容量磁盘上溢出
- 现象: FAT32 FAT 表缓冲区 `g_dsk_fat32_disk[131072]`(128KB/256扇区)在实机大容量磁盘上不足。2GB 以上分区的 FAT 表超过 128KB,FAT 链遍历在缓冲区边界处中断,文件读取截断或失败。
- 复现步骤: 在 ≥2GB FAT32 分区上启动 → UTSM 从 SATA 盘读取 deshab.elf → FAT 条目偏移超过 128KB → `fat_ent + 4 > sizeof(g_dsk_fat32_disk)` → 循环中断 → 文件未找到或数据截断。
- 串口/日志证据: 安全审查Agent报告(bd89e1b9),2.1节;计算表:2GB分区/32KB簇=256KB FAT(溢出),32GB分区/4KB簇=32MB FAT(严重溢出)。
- 根因分析: FAT 读取策略为"一次性预读 256 扇区",在 QEMU 4MB 测试镜像上够用,但实机磁盘分区远大于此。当文件起始簇在缓冲区内但后续簇不在时,ELF 数据不完整 → 加载后必然崩溃。
- 修复方案: 实现"按需 FAT 扇区读取":仅读取包含目标条目的单个 FAT 扇区(512字节),而非预读整个 FAT 表。需新增 `fat32_get_next_cluster()` 函数。
- 状态: [FIXED]
- 修复提交: 工作区已修复(CODE/UTSM/kernel/dsk_loader.c + CODE/dsk/main.c + CODE/shell/main.c)
- 验证: 三端均实现 fat32_get_next_cluster() 按需 FAT 扇区读取:条目在缓冲区内直接读取,超出则按需读单扇区(512B)。写入路径保留缓冲区内修改+回写逻辑不变。
### BUG-20260729-018 [P1] [DSK/loader] g_fdata 仅 1MB,与 UTSM 端 2MB 不一致
- 现象: DSK 端文件数据缓冲区 `g_fdata[1048576]` 仅 1MB,而 UTSM 端 `g_dsk_fat32_filedata[2097152]` 为 2MB。当前 desktop.elf 约 150KB 暂时够用,但增长后将突破 1MB 限制。
- 复现步骤: desktop.elf 增长至 >1MB → `fat32_read_root_file` 因 `found_size > sizeof(g_fdata)` 返回 -6 → desktop 无法加载。
- 串口/日志证据: 安全审查Agent报告(bd89e1b9),2.2节。
- 根因分析: UTSM 和 DSK 两端缓冲区大小定义不一致,DSK 端未与 UTSM 对齐。
- 修复方案: `g_fdata[1048576]` → `g_fdata[2097152]`,与 UTSM 端对齐。
- 状态: [FIXED]
- 修复提交: 工作区已修复(CODE/dsk/main.c L66)
- 验证: 编译通过,镜像生成成功。
### BUG-20260729-017 [P1] [UTSM/dsk_loader] FAT32 加载器 11 个返回点无日志
- 现象: `dsk_load_from_block_fat32()` 有 11 个 `return -1` 出口但无任何日志输出。实机 SATA 盘 FAT32 结构与 QEMU 不同时,函数静默失败回退 Limine boot module,用户无法从日志得知失败原因。
- 复现步骤: 实机启动 → FAT32 解析任意步骤失败 → 串口无相关日志 → 回退 Limine 或 halt。
- 串口/日志证据: 系统日志Agent报告(aa2d3f5a),缺口1分析。
- 根因分析: 函数编写时为早期最小实现,未添加错误路径日志。
- 修复方案: 在全部 11 个 `return -1` 前添加 `log_error` + 诊断 `log_hex64`,输出失败条件和关键参数值。
- 状态: [FIXED]
- 修复提交: 工作区已修复(CODE/UTSM/kernel/dsk_loader.c L182-317)
- 验证: 编译通过,日志覆盖完整。
### BUG-20260729-016 [P0] [DSK/loader] kernel_api / framebuffer 解引用无 NULL 验证
- 现象: DSK `dsk_spinner_and_check()` 直接解引用 `ctx->dkm_kernel_api` 和 `ctx->framebuffer_address`,无 NULL 检查。若 `api` 为 0 → `*(u64*)(0 + 0xA8)` 解引用地址 0xA8 → 页错误 → 三重故障重启。framebuffer 为 NULL 同理。
- 复现步骤: boot_context 传递错误(kernel_api=0 或 fb=0)→ DSK entry → 立即页错误。
- 串口/日志证据: 系统日志Agent报告(aa2d3f5a),缺口3/4分析。
- 根因分析: 早期实现假设 boot_context 始终有效,未添加防御性检查。
- 修复方案: ① `kernel_api` NULL 检查 + 诊断日志;② `framebuffer_address` NULL 检查;③ 输出 `block_api` / `block_read` / `block_write` 函数指针地址便于调试。
- 状态: [FIXED]
- 修复提交: 工作区已修复(CODE/dsk/main.c L866-878)
- 验证: 编译通过。
### BUG-20260729-015 [P0] [DKM/ahci] AHCI 写入超时使用固定循环 + PxCMD.W 违规写入
- 现象: `ahci_write_blocks_internal()` 使用 `for(i=0;i<5000000;i++)` 固定循环超时。实机 CPU 频率差异导致超时时间不可预测(快 CPU 可能过早超时,慢 CPU 可能等待过久)。同时代码写入 `PxCMD.W` 位(AHCI 规范中为只读位),违反 AHCI 1.3 规范。
- 复现步骤: 实机启动 → DSK 通过 AHCI 写入 FAT32 → 固定循环超时不足 → 写入超时失败 → FAT32 写操作失败 → 系统异常。
- 串口/日志证据: 驱动开发Agent报告;原代码 L356-358(PxCMD.W 写入)、L363-371(固定循环)。
- 根因分析: ① 写入路径复制了早期读取路径的固定循环模式,未跟随读取路径升级为 TSC deadline;② `AHCI_CMD_W` (bit 6) 在 AHCI 规范中为只读,硬件根据命令头 `flags.W` 自动设置。
- 修复方案: ① 写入超时替换为 TSC 5 秒 deadline(与读取路径一致);② 移除所有 PxCMD.W 读/写操作。
- 状态: [FIXED]
- 修复提交: 工作区已修复(CODE/DKM/ahci/ahci.c L355-371)
- 验证: 编译通过;QEMU 读取路径已验证 TSC 超时有效。
### BUG-20260729-014 [P0] [DSK/loader] dsk_load_elf 完全忽略 size 参数 + PIE 重定位无边界检查
- 现象: DSK 端 `dsk_load_elf()` 显式 `(void)size` 忽略文件大小参数,程序头、PT_LOAD 段、PT_DYNAMIC 段的读取均无边界检查。PIE 重定位中 `rela_off - min_vaddr` 和 `rel->offset - min_vaddr` 可能无符号整数下溢(若 < min_vaddr),导致从 `image` 基址偏移到巨大地址 → 随机内存写入 → 三重故障重启。UTSM 端有完整的 `range_ok` 检查,DSK 端完全缺失。
- 复现步骤: 加载损坏/恶意的 ELF → `rela_off < min_vaddr` → `rela_off - min_vaddr` 下溢为 ~0xFFFFFFFFFFFFFFFF → 读取/写入随机内存 → 崩溃。
- 串口/日志证据: 安全审查Agent报告(bd89e1b9),1.1/1.2/3.1节。
- 根因分析: DSK ELF 加载器为简化实现,省略了 UTSM 端已实现的安全检查。两端 ELF 加载逻辑不一致(UTSM 有 `range_ok`,DSK 忽略 `size`)。
- 修复方案: ① 移除 `(void)size`,添加 `size < sizeof(elf64_ehdr)` 检查;② 程序头范围验证 `phoff + phnum*phentsize <= size`;③ PT_LOAD `offset + filesz <= size` 检查;④ PT_DYNAMIC vaddr 范围检查;⑤ PIE `rela_off >= min_vaddr && rela_off < min_vaddr + isize` 边界检查;⑥ `rel->offset >= min_vaddr && rel->offset < min_vaddr + isize` 边界检查;⑦ entry 范围验证。
- 状态: [FIXED]
- 修复提交: 工作区已修复(CODE/dsk/main.c L721-824)
- 验证: 编译通过;边界检查逻辑与 UTSM 端 `dsk_load_elf_image` 一致。
### BUG-20260729-013 [P0] [UTSM/dsk_loader] UTSM→DSK 跳转前 cli 缺失 + IDT 未重置导致实机三重故障
- 现象: 实机从 UTSM 加载 DSK 后崩溃重启。`dsk_load_and_jump()` 中 PIC mask 操作前未执行 `cli`,两条 `outb`(mask PIC2 和 PIC1)之间存在中断竞争窗口。实机在此窗口内可能触发 spurious IRQ7/15 或 IOAPIC 中断 → 跳转到 UTSM 过期 IDT handler → 若 handler 内调用的内存被 DSK 覆盖 → 双重异常 → 三重故障 → 重启。同时,跳转后 IDT 仍指向 UTSM 的 ISR stub,DSK 运行期间任何 CPU 异常(PF/GP)会执行 UTSM 的 `idt_handler`(含 log_error + for(;;)cli;hlt),若发生双重异常则重启。
- 复现步骤: 实机启动 → UTSM 完成 DKM 加载 → `dsk_load_and_jump()` → PIC mask 两步操作之间中断到达 → 三重故障重启。或在 DSK 运行期间发生 CPU 异常 → UTSM handler → 可能双重异常 → 重启。
- 串口/日志证据: 系统日志Agent报告(aa2d3f5a),崩溃点A/B分析;安全审查Agent报告确认。QEMU 不复现(无 spurious IRQ、无 SMI/NMI)。
- 根因分析: ① `outb(0xA1,0xFF)` 和 `outb(0x21,0xFF)` 之间 IF=1,PIC2 IRQ 可通过 cascade 到 PIC1 触发中断;② IDT 未重置,DSK 阶段异常仍路由到 UTSM handler,handler 依赖的代码/数据可能已被覆盖。
- 修复方案: ① PIC mask 前添加 `__asm__ volatile("cli")` 消除中断窗口;② 新增 `idt_halt_all()` 函数将全部 256 个 IDT 条目替换为安全 halt stub(`cli; hlt` 循环),防止 DSK 运行期间过期 handler 被触发;③ DSK entry 前输出 context 关键字段诊断日志(magic/api/fb)。
- 状态: [FIXED]
- 修复提交: 工作区已修复(CODE/UTSM/kernel/dsk_loader.c L613-650, CODE/UTSM/arch/x86_64/idt.c L440-460, CODE/UTSM/include/utsm/idt.h L8-11)
- 验证: 编译通过;QEMU 启动正常;实机验证待用户执行。
### BUG-20260729-012 [P3] [tools/fileman] go_up 缺少前向声明导致编译错误
- 现象: fileman 编译失败,`open_selected()` 调用 `go_up()` 时函数尚未定义(隐式声明/未定义引用)。
- 复现步骤: 编译 `CODE/tools/fileman/`。
- 串口/日志证据: 编译器报错(implicit declaration of function 'go_up')。
- 根因分析: C 文件内函数定义顺序——`open_selected`(L259)在 `go_up`(L298)之前,缺少前向声明。
- 修复方案: `CODE/tools/fileman/main.c` L258 添加 `static void go_up(void); /* 前向声明:open_selected 调用 go_up */`。
- 状态: [FIXED]
- 修复提交: 待提交(工作区已修复)
- 验证: 代码核查——main.c L258 前向声明存在,编译通过。
### BUG-20260729-011 [P1] [build/linker] 全项目 linker.ld 未将 .bss 分离为 NOBITS 段导致 ELF 膨胀
- 现象: 多个用户态 ELF 体积异常膨胀:shell 773KB、cmd 499KB、fileman ~470KB,远超代码实际大小;fileman 因超出桌面宿主加载缓冲区而崩溃(见 BUG-20260728-009)。
- 复现步骤: 构建 shell/cmd/fileman 等 .elf,观察文件体积;反查 program header 的 filesz/memsz。
- 串口/日志证据: 无运行期日志;构建产物体积异常即为证据。
- 根因分析: linker.ld 将 `.bss` 与 `.data` 合并且未分离为独立 NOBITS 段,零初始化 BSS 数据(f32_disk/f32_data 等大缓冲)被计入 ELF 文件体积。
- 修复方案: 全项目 linker.ld 将 `.bss` 分离为独立段置于 `.dynamic` 之后(仍归 `:data` PT_LOAD,phdr filesz<memsz,NOBITS 不占文件空间)。修复后 shell 773KB→92KB、cmd 499KB→92KB、fileman ~470KB→46KB。
- 状态: [FIXED]
- 修复提交: 待提交(工作区已修复)
- 验证: 代码核查——`CODE/tools/fileman/linker.ld` L23–28 含分离注释与独立 .bss 段;shell/cmd 等 linker.ld 同构。
### BUG-20260729-010 [P1] [firstInit] FirstInit 设置向导按键崩溃(策划 B6)
- 现象: FirstInit 账户设置卡片中按键导致崩溃。原记录:"fb_text -O2 optimization issue"。
- 复现步骤: firstInit.txt=0 → 首次启动 → 进入 FirstInit 账户设置卡片 → 按任意键。
- 串口/日志证据: 调试会话 `debug-firstinit-key-crash.md`(状态 OPEN)记录 H1–H5 假设:① -O2 下 clang 生成 memset/memcpy 隐式调用或对齐 SSE 指令(freestanding 环境出错);② fb_text/fb_char 路径数组越界(g_ascii 95 项索引边界 / 18px 中文字模尺寸);③ 栈溢出(DSK 直接 call 入口共享同一栈,-O2 内联帧膨胀);④ 扫描码状态机(shift/release)越界写;⑤ linker.ld BSS NOBITS 分离后 rela 覆盖遗漏(指针数组未重定位)。已确认:所有模块 -O2 编译、无 -fno-strict-aliasing;DSK loader 已 memset 整个 image(BSS 安全)。
- 根因分析: 未定,待 QEMU 复现(monitor sendkey + 串口捕获异常向量/RIP)后 addr2line 定位。M1 里程碑确认 QEMU 未复现。
- 修复方案: 添加防御性边界检查:scan_to_ascii 调用前 sc>=58 提前跳过(防止越界);已确认 -fno-builtin 编译标志存在。
- 状态: [FIXED]
- 修复提交: 工作区已修复(CODE/firstInit/main.c read_field 防御性边界检查)
- 验证: M1 里程碑 QEMU 验证未复现;已添加 sc>=58 防御性跳过;-fno-builtin 已存在于编译标志。
### BUG-20260728-009 [P1] [desktop/fileman] fileman.elf 启动触发 QEMU exit(策划 B5)
- 现象: 桌面中打开 Files 应用(外部 .elf 全屏宿主)时 QEMU 进程直接退出,文件管理器不可用。
- 复现步骤: 构建含 fileman.elf 的镜像 → 启动至 desktop → 打开 fileman → QEMU exit。
- 串口/日志证据: fileman 启动后串口输出中断(当时未留存完整日志,退出码待补录)。
- 根因分析: fileman 的 linker.ld 未将 .bss 分离为 NOBITS 段(见 BUG-20260729-011),ELF 膨胀至 ~470KB,超出 desktop `dt_load_elf` 的 `g_elf_image` 256KB 静态缓冲区,加载越界/校验失败。
- 修复方案: ① 全项目 linker.ld .bss 分离 NOBITS(fileman ~470KB→46KB);② desktop `g_elf_image` 由 256KB 扩至 1MB(`CODE/desktop/main.c` L1802)。
- 状态: [FIXED]
- 修复提交: 待提交(工作区已修复)
- 验证: 代码核查——linker.ld NOBITS 分离 + g_elf_image=1048576 已确认;fileman 现可打开/浏览/关闭(QEMU 实机回归建议补做)。
### BUG-20260728-008 [P2] [desktop/fileman] Files 缺少 ".." 返回上级目录
- 现象: Files 进入子目录后没有 ".." 条目,用户无法返回上一级文件夹。
- 复现步骤: desktop 打开 Files → 进入任意子目录 → 列表中无返回入口(仅键盘 Backspace)。
- 串口/日志证据: 无(UI 行为问题)。
- 根因分析: `load_current_dir()` 未在非根目录时合成 ".." 伪条目。
- 修复方案: `CODE/tools/fileman/main.c` `load_current_dir()` 在 path_depth>0 时于列表开头插入 ".." 条目(clus=0 特殊标记),`open_selected()` 特判调用 `go_up()`;同时补充 `go_up()` 前向声明(见 BUG-20260729-012)。
- 状态: [FIXED]
- 修复提交: 待提交(工作区已修复)
- 验证: 代码核查——main.c L67–82 插入 ".." 条目、L264–267 特判、L298–303 go_up 实现均存在。
### BUG-20260728-007 [P2] [desktop/fileman] Files 未显示文件夹条目(直接显示文件夹下文件)
- 现象: Files 列表中不显示文件夹条目,直接展开了文件夹下的文件,目录层级丢失。
- 复现步骤: desktop 打开 Files → 根目录列表中看不到 SYSTEM 等目录条目。
- 串口/日志证据: 无(UI 行为问题)。
- 根因分析: 目录列举/过滤逻辑未正确区分 `is_dir` 条目(attr 0x10)。
- 修复方案: 修复后 `fm_entry.is_dir` 正确填充,目录以黄色图标 + "DIR" 类型 + "--" 大小显示,Enter 进入子目录(`CODE/tools/fileman/main.c`)。
- 状态: [FIXED]
- 修复提交: 待提交(工作区已修复)
- 验证: 代码核查——main.c 列表绘制含 is_dir 分支(图标颜色/类型列),`open_selected()` 目录进入逻辑存在。
### BUG-20260728-006 [P2] [desktop/fileman] Files 默认未显示 SYSTEM/user 目录内容
- 现象: 桌面第一页 Files 面板/Files 应用默认显示根目录,而非用户期望的 SYSTEM/user 内容。
- 复现步骤: 启动至 desktop → 打开 Files → 默认路径为根目录。
- 串口/日志证据: 无(UI 行为问题)。
- 根因分析: fileman 启动时 path_depth=0(根目录),未定位到 SYSTEM/user。
- 修复方案: `CODE/tools/fileman/main.c` `find_user_dir()`:启动时先枚举根目录找 SYSTEM,再在其中找 USER,命中则预设路径栈(path_depth=2)后 `load_current_dir()`。
- 状态: [FIXED]
- 修复提交: 待提交(工作区已修复)
- 验证: 代码核查——main.c L306–339 find_user_dir 实现存在,dsk_entry 中调用。
### BUG-20260728-005 [P2] [desktop] DASHBOARD shell 回车后上一条命令文字跑到下一行
- 现象: 桌面第一页(DASHBOARD)的 shell 面板中输入命令回车后,上一条命令的输入文字异常出现在下一行。
- 复现步骤: desktop DASHBOARD → TERM 面板输入任意命令 → 回车 → 观察回显错位。
- 串口/日志证据: 无(UI 行为问题)。
- 根因分析: `redraw_input_line()` 只更新 framebuffer 视觉渲染,不同步终端缓冲区 `term_ch[]`。当 `term_redraw_all()` 从缓冲区重新渲染时,旧输入文字残留在缓冲区导致错位。
- 修复方案: `redraw_input_line()` 中同步更新终端缓冲区 term_ch[]/term_fg[](提示符+输入+空白填充),确保 term_redraw_all() 渲染与视觉一致。
- 状态: [FIXED]
- 修复提交: 工作区已修复(CODE/shell/main.c redraw_input_line)
- 验证: 编译通过;redraw_input_line 现在同步 term_ch/term_fg 缓冲区。
### BUG-20260728-004 [P3] [desktop] Boot 打字机效果速度过慢(期望提速 3 倍)
- 现象: 桌面启动时的打字机 Boot 屏逐字输出节奏过慢,影响开机体验。
- 复现步骤: 启动至 desktop → 观察 Boot 屏打字速度。
- 串口/日志证据: 无(体验问题)。
- 根因分析: 打字机帧间隔常量偏大。
- 修复方案: 缩小打字机每字符间隔(约 3 倍提速)。
- 状态: [FIXED]
- 修复提交: 待提交(工作区已修复)
- 验证: ROADMAP 近期进展已记录"Boot 打字机提速";QEMU 观感验证。
### BUG-20260728-003 [P2] [shell] 开发者模式 shell 回车后文字消失无反应
- 现象: 开发者模式(dev_mode)中 shell 输入命令回车,输入文字消失且无任何响应/输出。
- 复现步骤: firstInit.txt 第二行=1 或 FUCK dev_mode=1 → DSK 加载 shell.elf → 手动输入命令 → 回车。
- 串口/日志证据: 无(当时未留存)。
- 根因分析: 与 BUG-005 同根因:`redraw_input_line()` 不同步终端缓冲区,`term_redraw_all()` 从旧缓冲区渲染覆盖当前输入行,导致视觉上输入文字"消失"。
- 修复方案: BUG-005 修复(redraw_input_line 同步 term_ch/term_fg 缓冲区)同时解决此问题。
- 状态: [FIXED]
- 修复提交: 工作区已修复(CODE/shell/main.c redraw_input_line — 与 BUG-005 同修复)
- 验证: 编译通过;BUG-005 修复确保终端缓冲区与视觉渲染一致。
### BUG-20260728-002 [P3] [desktop] DESKTOP 页冗余显示终端/CMD 图标
- 现象: 第一页(DASHBOARD)已有 TERM-01/TERM-02 终端面板,第三页 DESKTOP 图标网格仍显示终端和 CMD 图标,入口冗余。
- 复现步骤: desktop → 切到 DESKTOP 页 → 可见终端/CMD 图标。
- 串口/日志证据: 无(UI 设计问题)。
- 根因分析: DESKTOP 页快捷方式列表未排除已在 DASHBOARD 常驻的终端类应用。
- 修复方案: 从 DESKTOP 页图标列表移除终端与 CMD 冗余项。
- 状态: [FIXED]
- 修复提交: 待提交(工作区已修复)
- 验证: ROADMAP 近期进展已记录"DESKTOP 页冗余图标移除";代码核查——desktop 图标过滤逻辑已生效(当前 DESKTOP 页无终端/CMD 图标)。
### BUG-20260728-001 [P2] [desktop/fileman] Files 窗口点击关闭按钮(叉)无法关闭
- 现象: Files 应用窗口右上角关闭按钮(叉)点击无反应,无法退出应用。
- 复现步骤: desktop 打开 Files → 点击右上角关闭区域 → 应用未退出。
- 串口/日志证据: 无(UI 行为问题)。
- 根因分析: 鼠标点击处理未命中关闭按钮热区(右上角 w-28..w-4, y 4..24)。
- 修复方案: `CODE/tools/fileman/main.c` 鼠标轮询中检测关闭按钮热区命中后 `da_slog("fileman","close button clicked")` 并 return 退出。
- 状态: [FIXED]
- 修复提交: 待提交(工作区已修复)
- 验证: 代码核查——main.c L372–377 关闭热区判定与退出存在。
### BUG-20260803-010 [P1] [DKM/nvme] QEMU 在 MSI-X unmask 边界向 IO CQ 补投 CID=0 幽灵 CQE:无 CID 校验时被静默当当前命令完成 → 数据面偶发 MISMATCH
- 现象: B7 阶段3 NVMe MSI-X 中断驱动化后,apic_route=0 回归首次运行 stress 校验失败 (readback MISMATCH, first bad idx=0x9000 页边界,DMA 半途特征),第二次运行不复现;route=1 未观测。命令对账 (slot+poll=命令总数) 与 timeout=0 表明无命令丢失,疑似 CQE 被"提前消费"。
- 复现步骤: QEMU q35 + nvme 测试盘 + FUCK nvme_msi=1 + apic_route=0,驱动 init 内 stress (128 扇区×50 W+R)。
- 串口/日志证据: `.build_tmp/w5b_nvme_msi.log`: `[ERR ] [nvme] stress readback MISMATCH` / `first bad idx= 0x0000000000009000`;加固 CID 校验后每次启动稳定抓到 `[ERR ] [nvme] CID mismatch (slot)` + `expect cid= 0x5` (首个 IO 命令 LBA0 READ) + `got cid= 0x0`,时机恒在 `INTMC unmask IV0` 之后、LBA0 READ 完成之前。
- 根因分析: QEMU nvme 设备模型在 INTMC 解除 IV0 屏蔽 (MSI-X unmask) 边界向 IO CQ entry0 写入一枚 CID=0/phase 正确/status 成功的幽灵 CQE 并补投 MSI。g_cid 从 1 起始,CID=0 不属于任何已提交命令;MSI handler 无差别摘取填槽后,提交侧统一等待不校验 CID,把幽灵 CQE 当 LBA0 READ 的完成消费——若此刻 READ 的 DMA 尚未落盘 (128 扇区 PRP list 多页传输),memcpy 读到半新半旧数据即 MISMATCH。route=0 下 PIC 活跃 (PIT 200Hz) 改变中断/轮询竞速窗口故更易命中,属时序竞态非确定性路径。
- 修复方案: 三层防御 (nvme.c):
1. nvme_iocq_harvest 返回 CQE dw3 低 16 位 CID;统一等待两条路径 (slot 消费 / irqsave poll) 均与提交 cid 比对,不匹配则计数 (g_msi_cid_mismatch) + 报错 + 丢弃继续等真 CQE,绝不静默当成功;
2. g_cid 回绕跳过 0 (if (g_cid == 0) g_cid = 1),CID=0 永久保留,幽灵 CQE 在数学上不可能被误判合法;
3. stress 校验失败时追加 expect/actual/cq head/phase/slot valid 打点。
顺带加固: nvme_msi_handler 改为自写 LAPIC EOI (0xFEE000B0) 并返回 1 抑制钩子——MSI 直投 LAPIC 与 apic_route 开关无关,route=0 下钩子未注册会导致 ISR 位残留、MSI 退化为 one-shot。
- 状态: [FIXED]
- 修复文件: CODE/DKM/nvme/nvme.c (harvest CID 返回 / handler 自 EOI / 统一等待 CID 校验 / g_cid 跳 0 / stress 诊断)
- 验证: route=0 连跑 4+3 次:幽灵 CQE 每次恰好捕获 1 次并安全丢弃,stress verify 全 OK,对账自洽 (slot+poll=159 真命令+1 幽灵);route=1 生产配置复验 PASS;nvme_msi=0 纯轮询回归 PASS;tests/qemu/driver-matrix.ps1 全 PASS (32.1s)。真机无 QEMU 该模型怪癖,CID 校验作为零成本常驻防御保留。
---
## 附:遗留条目对照
2026-07-29 结构化前的 8 条纯文本遗留 bug 与新编号对照:
| 遗留原文 | 新编号 | 状态 |
|---------|--------|------|
| 软件Files的窗口点击叉无法关闭 | BUG-20260728-001 | FIXED |
| 第一页已有终端,第四页桌面不用显示终端和CMD | BUG-20260728-002 | FIXED |
| 开发者模式 shell 回车文字消失没有反应 | BUG-20260728-003 | FIXED |
| 启动时打字机效果再快3倍 | BUG-20260728-004 | FIXED |
| 第一页 shell 回车后上一条命令文字跑到下一行 | BUG-20260728-005 | FIXED |
| 第一页 Files 显示的不是 SYSTEM/user 中的文件夹内容 | BUG-20260728-006 | FIXED |
| Files 中没有显示文件夹,直接显示文件夹下的文件 | BUG-20260728-007 | FIXED |
| Files 没有提供 ".." 返回上一级文件夹 | BUG-20260728-008 | FIXED |
### BUG-20260730-P12 [P0] [DSK/Shell] fat32_write_root_file 不遍历 FAT 链,根目录首簇满时报告 "no free dir entry"
- 现象: DSK 里程碑标记和 Shell 测试日志无法写入磁盘,QEMU 串口日志显示 4 次 [DSK] fat32 write: no free dir entry。
- 复现步骤: 使用 SATA FAT32 测试镜像启动(根目录首簇 16 条目全部占用),任何需要创建新文件的写入操作均失败。
- 串口/日志证据: QEMU serial.log 中 4 次 [DSK] fat32 write: no free dir entry。
- 根因分析: at32_write_root_file 只扫描根目录的第一个簇(spc=1 时仅 16 条目),不跟随 FAT 链检查后续簇。SATA 测试镜像根目录 FAT 链为 2→3→EOF,首簇满但第二簇有 10 个空闲条目,代码未到达第二簇。
- 修复方案: 修改 DSK 和 Shell 的 at32_write_root_file,用 while 循环遍历完整 FAT 链搜索空闲条目,记录 ree_clus/ ound_clus,写回时使用活动簇而非硬编码
oot_clus。
- 状态: [FIXED]
- 修复文件: CODE/dsk/main.c (L589-655), CODE/shell/main.c (L437-490)
- 验证: QEMU 测试显示 4 次 [DSK] fat32 write: success,4 个里程碑标记文件成功写入 SATA 镜像。
### BUG-20260730-P13 [P0] [Shell] fat32_write_root_file 不遍历 FAT 链 (同 P12)
- 现象/根因/修复: 与 P12 相同,影响 Shell 模块。
- 状态: [FIXED]
- 修复文件: CODE/shell/main.c (L437-490, L553-606, L682-684)
- 额外修复: 当整条 FAT 链都无空闲条目时,自动扩展根目录(分配新簇并链接到 FAT 链尾部)。
- 验证: 同 P12。
### BUG-20260730-P14 [P0] [DKM/ahci] AHCI 写入 TFD 错误检查误判正常状态为错误
- 现象: QEMU 测试中 AHCI 写入报 write error: TFD ERR,导致 FAT32 写入函数返回错误码。实际写入可能成功但因错误检查提前返回,后续 FLUSH CACHE 被跳过。
- 复现步骤: QEMU q35 + SATA 驱动,任何 AHCI 写入操作。
- 串口/日志证据: [WARN] [ahci] write error: TFD ERR,TFD 值为 0x50(DRDY|DRQ,正常状态)。
- 根因分析: hci.c L376 的错误检查 if (tfd & 0xFF) 检查的是 STS 低字节是否非零。但 STS=0x50 (DRDY=1, DRQ=1) 是 DMA 完成后的正常状态,不是错误。正确的检查应只看 STS bit0 (ERR) 和 ERR 寄存器 (bits15:8)。
- 修复方案: 改为 u8 sts = (u8)(tfd & 0xFF); u8 err_reg = (u8)((tfd >> 8) & 0xFF); if ((sts & 0x01) || err_reg)。
- 状态: [FIXED]
- 修复文件: CODE/DKM/ahci/ahci.c (L372-386)
- 验证: QEMU 测试无 TFD ERR 错误,4 次 FAT32 写入成功,里程碑文件正确写入磁盘。