# 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 参数读取——解释器覆盖率不记倒退。
- 修复方案: 引入 g_emu_mem_base 全局基址 + pe_shim_set_emu_base() 设置 + gp() 转换函数（va < 0x10000000 且 g_emu_mem_base 非零时返回 g_emu_mem_base+va）；x86emu32 解释执行 PE32 前设基址，shim 内指针参数经 gp() 转换；原生 PE32+ 路径 g_emu_mem_base 恒为 0，gp() 恒等映射零影响。
- 状态: [FIXED]
- 修复提交: a26866e（CODE/UTSM/pe/pe_shim.c g_emu_mem_base + pe_shim_set_emu_base + gp()）
- 验证: x86emu32-coverage.ps1 PASS——HELLO32.EXE 解释执行输出 "Hello from PE32!"；pe-compat.ps1 会话 B 同步验证通过。

### 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 调用全程存活的作用域）；g_exit_jmp[24] 缓冲区，pe_shim_setup_exit 只提供缓冲区并重置退出状态，__builtin_setjmp 由 pe_service_run 本体直接调用，确保 setjmp 帧不再跨已返回函数。
- 状态: [FIXED]
- 修复提交: a26866e（CODE/UTSM/pe/pe_shim.c g_exit_jmp 重构 + pe_shim_setup_exit 语义调整）
- 验证: pe-compat.ps1 4 会话全绿——HELLO64.EXE 打印 "Hello from PE32+!" 后 `[PE] ExitProcess code=` → `[cmd] PE run end` 干净返回；Deaicup GUI 长时间运行后 Esc→WM_CLOSE→ExitProcess(0) 退出链回归确认。

### 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` 才能定位。
- 修复方案: DSK 交接时装配最小诊断 IDT（idt_install_dsk_diag）：#PF/#GP/#DF 等 0-31 号异常走 idt_handler 打印向量+err+RIP+CR2+RAX 后 cli;hlt，门用 DSK 阶段有效 CS 选择子、IST=0。
- 状态: [FIXED]
- 修复提交: a26866e（CODE/UTSM/arch/x86_64/idt.c idt_install_dsk_diag）
- 验证: pe-compat.ps1 fault64 会话 PASS——故意 #PF（mov [0x4000000000],1）串口打出 `[IDT] vector=0xe err=0x2 rip=... cr2=0x4000000000` 完整现场后安全停机，修复前静默三重故障。

### 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 写入成功，里程碑文件正确写入磁盘。
