208 lines
24 KiB
Markdown
208 lines
24 KiB
Markdown
# VKERNELERR — VKernel LLVM IR 与源码对照:TPV 编译器 codegen 缺陷清单
|
||
|
||
> 背景:ViperOS 内核(VKernel)用**旧版 TPC** 编译可完全正常运行;用**新版 TPV/TPC** 编译后出现概率性崩溃、内存破坏(.rodata 格式串被逐步覆写)、desktop 启动约 11s 后挂死。怀疑编译器的 LLVM IR 生成存在回归,本文件记录对照结果。
|
||
>
|
||
> 对照对象:`ViperOS\VKernel\Kernel\*.vp`(源码)↔ `.tpv_build\temp\<hash>\*.ll / *.text.ll / *.vpi`(IR)。模块哈希映射见 `_sha1_map.txt`。
|
||
>
|
||
> 验证状态标记:✅ 已亲自复核 IR 行号;📎 子代理报告(建议复核)。
|
||
|
||
---
|
||
|
||
## 一、确凿的 codegen 缺陷(✅ 已复核)
|
||
|
||
### A1. 全局变量初始化常量 -1 被静默改为 0(zeroinitializer)
|
||
|
||
> ✅ **已修复(TransPyV commit 4db68ff,待重编验证)**:`TranslatorGenMixin.handle_module_level_var` 的初始值解析(AnnAssign/Assign 分支)新增 `UnaryOp` 处理(`extract_cdefine_int_value`),负数字面量 `-1` 现在正确提取。覆盖 main.vp `kbd_tid/mse_tid`、desktop 7 个、uhci `_irq`、usb_mouse `mse_slot_id` 等模块级 -1 全局。
|
||
|
||
- **现象**:`desktop.vp` 中 7 个 `= -1` 的模块级全局全部编译成 `global i32 zeroinitializer`(值 0)。
|
||
- **IR**:`temp\1e\1eb7b5be276f0a8f.ll` L2250/2251/2268/2269/2277/2280/2291
|
||
- **源码**:`services\desktop.vp` L113 `_dirty_y_max=-1`、L114 `_vblank_ok=-1`、L276 `_last_hover_win=-1`、L277 `_focused_win=-1`、L550 `_drag_idx=-1`、L561 `_rsz_drag_idx=-1`、L1282 `_cached_top_win=-1`
|
||
- **影响**:
|
||
- `_drag_idx=0` → `_update_mouse` 中 `if _drag_idx >= 0` 恒真 → **窗口 0 被无条件拖拽**(改写 aw2.x/y)
|
||
- `_rsz_drag_idx=0` → 无条件进入窗口缩放分支
|
||
- `_focused_win=0` → 键盘事件错误路由到窗口 0
|
||
- `_last_hover_win=0` / `_cached_top_win=0` → 错误 hover/active 判定
|
||
- `_vblank_ok=0` → vblank 同步被跳过
|
||
- **性质**:vpi 层初始值确为 -1,是 **vpi → LLVM IR 阶段负常量丢失**(同一类病灶见 A2)。
|
||
- **严重性**:高(行为改变,最可疑的"新版崩溃"诱因之一)
|
||
|
||
### A2. 字符数组全局初始化数据全部丢失(128×2 字节全 0)
|
||
|
||
> ✅ **已修复(TransPyV commit 4db68ff,验证中)**:`_build_array_initializer_text` 的数组元素提取新增 `CONST_CHAR` 分支(int_val 即字符码)。
|
||
>
|
||
> 🔧 **补充修复(includes commit 49445ea)**:验证发现 `'\x1b'` 仍为 0 —— 根因是 lexer 不支持 `\xNN` 十六进制转义([lexer.vp](file:///d:/Users/TermiNexus/Desktop/TransPyC/includes/ast/lexer.vp) 把 `\x1b` 当未知转义保留为 `\`+`x` 2 字节 → esc_len≠1 → 被判为 CONST_STR 而非 CONST_CHAR)。已加 `_hex_val` + `\xNN` 转义分支(对齐 Python)。**需重编 TPV + ViperOS 后验证 `_ps2_set1[1]` = 27**。
|
||
>
|
||
> **验证记录(✅ 全部通过 2026-08-30)**:重编后 `_ps2_set1[1]` = 27(ESC,`\xNN` 修复生效),`'1'`=49、`'2'`=50、`'q'`=113、`'['`=91、`'\n'`=10、`'\b'`=8、`'\t'`=9 全部与源码一致;`_ps2_set1_shift` 同理(`'!'`=33、`'@'`=64、`'Q'`=81、`'{'`=123)。A1/A2 均验证通过。
|
||
|
||
- **现象**:`_ps2_set1` / `_ps2_set1_shift` 两张 128 字节 PS/2 扫描码表编译成全部 `i8 0`。
|
||
- **IR**:`temp\1e\1eb7b5be276f0a8f.ll` L2275-2276(`global [128 x i8] [i8 0, ...]`)
|
||
- **源码**:`services\desktop.vp` L296-306、L308-318(`'\x1b','1','2',...` 真实数据)
|
||
- **影响**:`_ps2_scancode_to_ascii` 恒返回 0,PS/2 键盘输入完全失效(USB 键盘走 `_hid_to_ascii` 不受影响)。
|
||
- **性质**:与 A1 同类 —— **全局初始化器数据在 IR 生成阶段被丢弃**。数值字面量数组(`_CURSOR_*`)正常,唯独字符字面量数组全丢。
|
||
- **严重性**:高(确凿的初始化数据丢失,TPC 前端/IR 生成回归)
|
||
|
||
### A3. (源码误用,非编译器 bug)`str | t.CPtr` 联合类型按 i8** 处理下标/算术
|
||
|
||
> ⚠️ 2026-08-30 用户确认:**`str | t.CPtr` 预期语义就是 i8\*\***(指针的指针)。编译器把 `last_slash`(`str | t.CPtr` 注解)按下标写 8 字节、`+1` 偏移 8 —— **符合类型语义**。问题是内核源码把 `strrchr` 的 **`str`(i8\*)返回值**赋给 i8\*\* 变量却按 i8\* 使用。
|
||
>
|
||
> ✅ **已修复(ViperOS commit 333adb0,验证通过 2026-08-30)**:devfs.vp 4 处(L218/224 create_file、L253/259 open)`last_slash`/`file_name` 注解 `str | t.CPtr` → 裸 `str`。重编后 IR(`temp\81\815a07186c8962c8.text.ll` L1302-1313)`last_slash[0]='\0'` 为 1 字节 `store i8 0, i8*`、`+1` 为 1 字节 `getelementptr i8`。
|
||
>
|
||
> **修复方向(内核侧)**:devfs.vp L218/224(create_file)、L253/259(open)改用裸 `str`(i8\*):
|
||
> ```python
|
||
> last_slash: str = string.strrchr(path_copy, '/')
|
||
> file_name: str = last_slash + 1
|
||
> ```
|
||
>
|
||
> **编译器加固点**:`i8* → i8**` 赋值当前做隐式 bitcast —— 按隐式转换规则"**不同级指针不允许隐式转换**"应 fail-fast 报错(避免同类误用被静默编译)。
|
||
|
||
- **现象**:devfs `create_file`/`open` 中 `last_slash: str | t.CPtr`(i8\*\*):
|
||
- `last_slash[0] = '\0'` → 编译成 `getelementptr i8*, i8** %24, i32 0` + `store i8* null, i8** %25` —— **8 字节写**
|
||
- `file_name = last_slash + 1` → `getelementptr i8*, i8** %29, i64 1` —— **偏移 8 字节**
|
||
- **IR**:`temp\0f\0f22b8ba53f4d613.text.ll` L1296-1307(create_file)、L1431-1442(open)
|
||
- **源码**:`drivers\devfs\devfs.vp` L218-224、L253-259(`strrchr` 返回 `str`=i8\*,赋给 i8\*\* 变量)
|
||
- **后果链**:文件名开头 8 字节被清 0 → 所有设备注册成空名 → 第 2 个 create_file 的 `find_in_dir("")` 命中第一个空名返回 -1 → **devfs 只能注册 1 个空名设备**;`open()` 恒命中空名文件。
|
||
- **严重性**:高(devfs 功能整体崩坏;但非编译器行为错误,是源码类型误用)
|
||
|
||
### A4. (源码误用,非编译器 bug)`c.Set(裸指针形参, 值)` 只改指针变量本身,不是写所指地址
|
||
|
||
> ⚠️ 2026-08-30 用户澄清 `c.Set` 语义:`c.Set(a, b)` = **a = b**(含把指针变量本身改成 b);`c.Set(c.Deref(a), b)` = ***a = b**。因此 paging `save_pt_range` 的写法是**内核源码误用**,编译器行为符合语义。
|
||
>
|
||
> ✅ **已修复(ViperOS commit 463a200,验证通过 2026-08-30)**:paging.vp L426/L444 `c.Set(save_count, ...)` → `c.Set(c.Deref(save_count), ...)`。重编后 IR(`temp\7a\7ad9eacd898aa9e8.text.ll` L2241-2243)`store i32 0, i32* %21`(写所指地址),不再 `inttoptr` 改指针变量。
|
||
>
|
||
> 保留记录供排查参考:paging `save_pt_range` 的 out 参数 `save_count: t.CUInt32T | t.CPtr`:
|
||
> - `c.Set(save_count, t.CUInt32T(0))` 编译成 `inttoptr i32 0 to i32*` + `store i32* %20, i32** %3` —— 把**指针变量**置成 NULL
|
||
> - 后续 `c.DerefAs(save_count)` 解引用 NULL → **#PF**
|
||
> - **修复方向(内核侧)**:应写 `c.Set(c.Deref(save_count), t.CUInt32T(0))`、`c.Set(c.Deref(save_count), t.CUInt32T(saved + 1))`
|
||
> - **IR**:`temp\20\20f811d509884de2.text.ll` L2241-2242、L2411-2414
|
||
> - **源码**:`paging\paging.vp` L425、L443(写),L439(读用 DerefAs)
|
||
> - **严重性**:高(函数一旦被调用必然 NULL 解引用崩溃;非编译器问题)
|
||
|
||
### A5. `t.CUInt64T` 无符号循环比较被编译成有符号 `icmp slt`
|
||
|
||
> ✅ **已修复(TransPyV commit a0d6dce,验证通过 2026-08-30)**:`ExprGenOps.translate_compare` 比较 predicate 按 `_node_type_unsigned`(Name→变量 AnnotClassName / Call→转换目标 / Attribute→类型名 / 递归)判定操作数无符号性,无符号则选 `ULT/ULE/UGT/UGE`。重编后 paging MakeRangeRO `while va < vaddr_end` 生成 `icmp ult i64`(`temp\7a\7ad9eacd898aa9e8.text.ll` L1267)。
|
||
|
||
- **IR**:`temp\20\20f811d509884de2.text.ll` L2252(`while va < vaddr_end` → `icmp slt i64`)、L1267(MakeRangeRO 同样)
|
||
- **源码**:`paging\paging.vp` L260、L427(va/vaddr_end 均为 t.CUInt64T)
|
||
- **影响**:地址跨 2^63 边界时语义错误(扫错范围/立即退出)。当前 ro_start/ro_end 同为高地址(同号)碰巧正确。
|
||
- **严重性**:中(类型保真缺陷,当前未触发)
|
||
|
||
### A6. `%d/%i` 非 long 分支缺 32 位截断+符号扩展(与 %u/%x 不对称)
|
||
|
||
> ✅ **已修复(TransPyV commit a0d6dce,验证通过 2026-08-30)**:`CallGenVa._translate_va_arg_sugar`/`_translate_arg_sugar` 读 8 字节变参槽后 `build_trunc` 回目标宽度(<64 位整数),调用方 `t.CInt64T(i32)` 再 sext 正确符号扩展。重编后 `%d` else 分支出现 `trunc i64 to i32` + `sext i32 to i64`(`temp\bb\bba983c61a970f7c.text.ll` L993-994)。
|
||
|
||
- **IR**:`temp\bb\bba983c61a970f7c.text.ll` L985-994 —— long 分支(if.then.47)与 else 分支(if.else.47)**逐指令相同** `va_arg i64`,else 分支缺 `trunc to i32` + `sext to i64`(对比 is_char/is_short 分支 L1000-1013 有 trunc+sext)
|
||
- **源码**:`vpsdk\stdio.vp`(`else: num_l = t.CInt64T(va_arg(ap, t.CInt))` 语义要求读 32 位再符号扩展)
|
||
- **对照**:`%u`/`%x`/`%X`/`%o`/`%b` 的 else 分支均有 `trunc i64 to i32` + `zext i32 to i64`,唯独 `%d/%i` 缺失
|
||
- **影响**:调用方 int 参数高 32 位含垃圾时,`%d` 把负数当大正数输出(desktop `dirty=%d vsync=%d`、elf/mm 大量 `%d`)。
|
||
- **严重性**:中高(明确的 codegen 语义错误;x86 写 32 位寄存器清高 32 位所以当前碰巧多数正确)
|
||
|
||
### A7. `AllocPage` 内联汇编丢 `~{al}` clobber
|
||
|
||
> ✅ **已修复(TransPyV commit d14415a,验证通过 2026-08-30)**:`CallGenCBuiltin._resolve_register` 约束映射表补全 8 位寄存器 clobber(AL/AH/BL/BH/CL/CH/DL/DH,对齐 t.py 标准常量集)。重编后 paging AllocPage 约束为 `"r,~{al},~{ecx},~{rdi}"`(`temp\7a\7ad9eacd898aa9e8.text.ll` L110)。
|
||
|
||
- **IR**:`temp\20\20f811d509884de2.text.ll` L110(`asm sideeffect "mov al,0...rep stosb", "r,~{ecx},~{rdi}"` 缺 al/rax)
|
||
- **源码**:`paging\paging.vp` L68-72(op 列表含 CLOBBER_AL)
|
||
- **影响**:LLVM 不知道 AL 被改,可能把页地址 `$0` 分配进 RAX 被 `mov al,0` 破坏。当前因 pt_pool 4K 对齐低字节恰为 0 而未炸。
|
||
- **严重性**:中(约束错误,应修)
|
||
|
||
---
|
||
|
||
## 二、高嫌疑(📎 子代理报告,建议复核/对照旧产物)
|
||
|
||
### B1. `for x in ptr/数组` 缺"解引用为空终止"语义 → devfs find 全量遍历崩溃
|
||
|
||
> ✅ **已修复(TransPyV commit 9b46e1d,待重编验证)**:用户澄清语言语义 —— `for x in ptr` 遍历到**解引用为空**即终止,空值宽度按元素类型(整数→0、指针→null、浮点→0.0)。编译器实现:
|
||
> - `_handle_ptr_iter`(指针路径):元素类型放宽为整数/指针/浮点(此前指针元素 i8\*\* 场景 fatal_error)
|
||
> - `_handle_array_iter`(数组路径):cond 拆块,`idx < count` 长度上限 + 解引用为空提前终止(devfs `result` 是 CArray[str,256],split 只填 token + NULL 哨兵 → 遍历到哨兵停)
|
||
>
|
||
> **ViperOS commit c6a4063**:devfs find 恢复 `for i in result`(不再 range(token)),靠编译器 NULL 终止语义工作。**B1 是 A3 修复后"跑到 [STK] 就崩溃"的直接嫌疑**(`open("/dev/serial0")` → `find("/dev", root)` → 旧语义全量 256 槽 → strcmp(NULL) → #PF)。
|
||
|
||
- `devfs_directory.find`(`drivers\devfs\devfs.vp` L35-47)中 `for i in result` 编译为全量 256 槽迭代(`temp\81\815a07186c8962c8.text.ll` L477 `icmp slt i32 %31, 256`),`result` 为未清零栈数组(L470-472 load+store 整个 [256 x i8*]),split 只填 token 并设尾哨兵,其余槽位为栈垃圾;`samestr→strcmp` 无 NULL 防护(`includes\string.vp` L84-85)→ 遍历到哨兵/垃圾槽时 #PF。
|
||
- 旧编译器若清零数组或按 count 迭代,行为完全不同 —— **最像"新版崩溃"的直接触发点**。
|
||
|
||
### B2. 对象型全局 `X: T = T()` 构造语义不保真(双 storage + 栈临时 self + c.Addr 张冠李戴)
|
||
|
||
> ✅ **已修复(TransPyV 待提交,待重编验证)**:
|
||
> **根因**(devfs devfs_init IR `temp\92\920070a06bc630a1.text.ll` L57-70 实锤):
|
||
> 1. **类型比较是地址比较**:AssignGenMixin 三处 storage 绑定分支用 `t.CUInt64T(ty1) == t.CUInt64T(ty2)`(或 `ty1 == ty2`)判类型相等,而 llvmlite 类型**非 canonical**(`includes\llvmlite\__types.vp` `Ptr/Struct` 每次 `pool.alloc` 新实例)。`X = T()` 构造调用的 rhs 类型实例(CallGenCtor 的 struct_ty)与 storage 类型实例(handle_module_level_var 的 resolved_stg_ty)不同 → 绑定失败 → `@X = &栈临时`(悬垂),`@X.stg` 恒全 0(IR L61-67);
|
||
> 2. **c.Addr 误伤绑定**:`DevFSObject = c.Addr(_DevFSObject_instance)` 的 rhs 类型 pointee 恰是 resolved_stg_ty 同实例 → 误命中绑定 → `@DevFSObject = &@DevFSObject.stg`(应为 `&@_DevFSObject_instance.stg`)+ 值复制零对象(IR L58-60)→ DevFSObject 与 _DevFSObject_instance 脱钩。
|
||
> **修复**(`AssignGenMixin.vp`):
|
||
> - 新增 `_stg_ty_equal`:同实例快路径 + `TypePrint` 内容比较(命名结构体比名、匿名比布局),替换三处地址比较(L1242/L1320/L1956);
|
||
> - 新增 `_is_addrlike_call`:识别 `c.Addr/c.Deref/c.Cast/c.Ptr` 显式指针调用,绑定分支排除之(L1231/L1309/L1945)——`X = c.Addr(Y)` 改走普通 store(`@X = &Y.stg`,X 与 Y 指向同一对象)。
|
||
> **效果**:`X = T()` 绑定 storage(构造结果落 BSS,全局不悬垂);`X = c.Addr(Y)` 直接存指针(不再张冠李戴)。
|
||
|
||
- `sched.vp:231` `_sched_storage = Scheduler()` / `_sched_ptr = c.Addr(_sched_storage)`:
|
||
- IR(`temp\77\775840c75419f22c.ll` L4612-4615)把 `Scheduler.__init__` 的 self 编成**主模块栈上的 920 字节 alloca**;
|
||
- IR(`temp\3d\3d4afd9cdffeba6d.ll` L748-751)生成**两个独立 storage**:`_sched_storage.stg` 与 `_sched_ptr.stg`,`__init__` 用一次"全结构体值拷贝"从前者拷到后者 —— `_sched_ptr != &_sched_storage`,语义不保真;`_sched_storage.stg` 恒全 0;
|
||
- 若 `__init__` 写 `self.xxx`(未走全局指针重定向),数据落在栈临时对象上 → 全局 storage 全 0 → 崩溃。
|
||
- devfs 同构(`temp\0f\...text.ll` L55-72):`DevFSObject = c.Addr(_DevFSObject_instance)` 的 store 目标是 `@DevFSObject.stg`(**应为 `@_DevFSObject_instance.stg`**);`serial0_file = devfs_file()` 等指针全局指向已失效的栈帧(L277-371)。
|
||
- **性质**:模块级值对象构造的通用 codegen 模式,极脆弱。当前因 `__init__` 恰好不写 self / 引用点恰好在栈帧存活期而"侥幸工作"。
|
||
|
||
### B3. `memset` 跨模块声明签名不一致(i32 vs i64 长度参数)
|
||
|
||
> ✅ **已修复(TransPyV 待提交,待重编验证)**:
|
||
> **根因**(新产物实锤:process `temp\ed\edb4366475576ebf.ll` L487 `declare @memset(i8*,i32,i32)`、L1811 `call ... i32 32`;定义 `temp\b7\b758eeb73e54002a.ll` L938 `define @memset(i8*,i32,i64)`):
|
||
> `CallGenBase._ensure_method_declare`(L494)建跨模块 declare 时默认用**实参类型**,仅当 call_name 带 sha1 前缀(L575 `call_name[16]=='.'`)才查 vpi 定义侧参数注解修正宽度。裸名 CExport 函数(`string.memset` → `@memset`,string.vp `def memset(...) -> ... | t.CExport`)**无前缀** → 提取定义侧模块失败 → 第三参字面量 32=i32 固化窄。sched 未调用 memset → 无调用侧 declare → vpi stub 注入 i64(正确)。
|
||
> **当前碰巧安全**:x86-64 写 32 位寄存器(mov edx)自动清高 32 位 → 定义读 rdx=32;但 IR 层签名不一致是未定义行为隐患(优化路径读高位垃圾写穿内存)。
|
||
> **修复**:
|
||
> - `CallGenBase._ensure_method_declare`:裸名(无 `.`)且是已知 C 库函数 → 直接走 `CallGenLib._ensure_c_lib_declare` 标准签名(memset 第三参 i64);
|
||
> - `CallGenLib._promote_clib_call_args`:跨模块调用点把第三参窄整数实参提升 i64(常量 → i64 常量,SSA → zext),重建实参链;
|
||
> - `CallGenMixin` 跨模块调用出口(L1364-1370)先提升实参再建 declare。
|
||
> **预期**:process memset declare/call 均 `(i8*, i32, i64)`,与定义一致。
|
||
|
||
- process 模块声明 `declare i8* @memset(i8*, i32, i32)`(`temp\ed\edb4366475576ebf.ll` L487),调用 `create_process` 中 `string.memset(p.name, 0, 32)`(L1811)传 i32 长度;
|
||
- 实际定义在 `temp\b7\b758eeb73e54002a.ll` L938 为 `@memset(i8*, i32, i64)`(内部按 i64 读长度);sched 模块声明却是正确的 i64。
|
||
- 当前调用点碰巧生成 `mov ecx, 32`(清高位)未炸;一旦优化产生非清零高位路径,`rep stosb` 写穿内存。
|
||
|
||
### B4. `sched.vp:208` create_thread 栈分配失败不恢复 `thread_count`(源码 bug)
|
||
|
||
> ✅ **已修复(ViperOS 待提交)**:`sched.vp:208` `if not stack_ptr:` 前 `s.thread_count -= 1` 回退槽位(对齐 process.vp create_process 失败路径)。线程槽位不再泄漏,调度器不会扫描未初始化槽位。
|
||
|
||
- 对比 `process.vp:200` 失败路径有回退。线程槽位泄漏 → thread_count 漂移 → 调度器扫描未初始化槽位。
|
||
|
||
### B5. `t.attr` 系列属性丢失(packed/aligned/section 等)
|
||
|
||
> ✅ **已修复(TransPyV + includes 待提交)**:
|
||
> **根因**:`@c.Attribute(t.attr.packed)` 等装饰器被编译器忽略——
|
||
> 1. **packed**:LLVMType/NamedTypeNode 无 packed 概念,类型定义恒输出 `{...}`(自然对齐)。`idt_entry` 碰巧 16 字节无差异(`temp\58\58700b4401f1b4eb.ll` L5),但 gdt/elf/fat32 等二进制格式结构体若字段变化即插填充 → 磁盘/硬件布局错乱;
|
||
> 2. **aligned**:`fpu_context`(FXSAVE 区域需 16 对齐)无全局/alloca 对齐 → fxsave/fxrstor #GP 风险;
|
||
> 3. **section**:`main.vp boot` 的 `t.attr.section(".text.startup")` 被 `_append_llvm_attr` 拒绝 → 函数放错段。
|
||
> **修复**:
|
||
> - **includes/llvmlite**:`NamedTypeNode.IsPacked`(输出 `%"name" = type <{...}>`,LLVM packed struct 无填充)+ `module_set_named_type_packed`;`GlobalVariable.Align`(输出 `, align N`);`PrintStructDefinition/_print_struct_body` 加 packed 参数;
|
||
> - **TPV 编译器**:`StructEntry.IsPacked/Align`;`ClassGenBase` 新增 `_class_has_tattr/_tattr_name/_class_aligned_value/_tattr_aligned_int` 解析 `@c.Attribute(t.attr.*)`;`ClassGenDef` 注册 packed 类型 + 记录 aligned;`TranslatorGenMixin` 全局/storage 施加对齐;`FuncGenBase.extract_func_section` + `llvmlite.function_set_attr_group`:section 走**属性组 #N**(`define f() ... #1` + `attributes #1 = { "section"=".text.startup" }`)——LLVM 拒绝内联 `section="..."`(`llc: expected string constant`,boot 函数首编实测)。
|
||
> - **stub packed 最终根因(2026-08-30 二次验证补)**:定义模块 packed 已生效(`<{...}>`),但**跨模块 stub 注入仍 `{...}`**——`StructTable` 每 Phase 重建,Phase B existing 是新建 entry,`module_set_named_type_packed` 只设节点不设 `entry.IsPacked`;VpiReader 注入的 `entry_c.IsPacked` 同样缺失 → 后续跨模块 `ensure_struct_def_in_module` 按 `entry.IsPacked` 判定读到 0 → stub 非 packed(GEP 布局错位)。已补:`ClassGenDef` Phase B existing 路径 `existing.IsPacked = 1`(L131)+ `VpiReader._inject_class_struct_declare` `entry_c.IsPacked = 1`(L1762)。
|
||
> - **stub packed 终局根因(2026-08-30 静态定位+验证)**:上述 IsPacked 补设后,per_cpu_data/gdt_entry 在**值类型注入路径**仍非 packed——`VpiReader._ensure_full_value_types`(vpi 全局变量 `@_per_cpu = global %per_cpu_data`、`@gdt = global [7 x %gdt_entry]` 及值返回类型注入完整定义)**只 `module_add_named_type` 不 `module_set_named_type_packed`** → 引用模块节点 IsPacked=0 → 输出 `{…}`。而 _tss/idt_entry(字段/属性引用触发 ensure 路径)packed、17/fpu.vp(代码引用触发 ensure)packed——同模块两种结果根因。已修:`_ensure_full_value_types` Struct 分支 add 后按 `full_se_h.IsPacked` 同步 set_packed。重编验证:全部引用模块 per_cpu_data/gdt_entry/idt_entry 输出 `<{...}>`,非 packed 残留 0。
|
||
> **说明**:`naked`/`aligned`(函数)LLVM IR 无对应属性(GCC 扩展),当前无实际危害(naked 函数体是纯 c.Asm+ret,llc 无局部变量不生成序言);`stackrealign`/`noinline`/`nounwind`/`always_inline` 等已有映射。
|
||
|
||
- `idt_entry` 的 `@c.Attribute(t.attr.packed)` 未体现在 IR type(`temp\58\58700b4401f1b4eb.ll` L5)。当前字段自然对齐碰巧与 packed 相同(16 字节),改动字段即插填充。
|
||
|
||
### B6. 非 entry 块 alloca → 动态 alloca → 运行时大 `sub rsp` → 概率性栈越界 #PF(2026-08-30 定位)
|
||
|
||
> ✅ **已修复(TransPyV 待提交)**:`StmtGenCifMixin._handle_array_iter`(数组 for-in)与 `_handle_list_iter`(列表字面量 for-in)的临时数组 alloca 原用 `llvmlite.build_alloca` 在 **for 语句所在块**(非 entry)创建。旧版 LLVM 将非 entry alloca 降为 **DynamicAlloca** → 运行时 `sub rsp, sizeof([N x elem])` → 调用栈深时 rsp 骤降踩未映射栈页 → **概率性 #PF**。已改 `VarGen._alloca_at_entry`(entry 静态 alloca,进帧由 LLVM 统一布局)。
|
||
>
|
||
> **实测崩溃样本**(serial.log):`[0E][PF] rip=0x103002 err=0x2 rsp=0x7bf10`,ret 链 `0x104948 → 0x10003e`,`[main] sched started` 之后,devfs 路径解析链:
|
||
> - 反汇编 kernel.bin 偏移 **0x2150 = `devfs_directory.find`**(prologue `sub rsp 0x1038`;`strchr(path,'/')`;`split(path,"/",result@rbp-0x1008)`),调用者 0x48b0 = devfs open 路径解析(`strchr('/')` + 63 项 vtable 表 + `callq *0x10(%rax)`);
|
||
> - 崩溃指令 0x3002 `movq %rcx, -0x668(%rax)` 位于 **%30 动态 alloca(`sub rsp 0x800`)的整数组初始化拷贝**(`%29=load [256 x i8*] %3; %30=alloca; store` 展开的逐条 movq);
|
||
> - IR(`temp/92/920070a06bc630a1.text.ll` L427-476):`result: t.CArray[str, 256]` → `%3=alloca [256 x i8*]` → `for i in result:` → L474-476 **整数组 load + 非 entry `alloca` + 整数组 store**;
|
||
> - llc 22.1.8(msys2 clang64)同 IR 会把该非 entry alloca **静态化**(rbp 基址拷贝)——故 TPV 配套旧 LLVM 是直接触发点。
|
||
> **概率性机制**:调用深度/路径分段数不同 → rsp 位置不同 → 动态 alloca 后是否越过栈映射页界决定是否 #PF。
|
||
|
||
---
|
||
|
||
## 三、与运行时崩溃现象(serial.log)的关联分析
|
||
|
||
| 现象 | 最相关发现 | 推理 |
|
||
|---|---|---|
|
||
| .rodata 格式串逐步被覆写(win=' / work=,) | A3 类(联合指针 8 字节写错位) | 越界写源在 .rodata 附近或指针算术错位;.rodata 因 2MB 大页未真正只读而静默吞掉写(ProtectKernelRO 已修复分裂,下轮可 #PF 定位写源) |
|
||
| desktop 启动 ~11s 挂死 | A1(_drag_idx/_rsz_drag_idx=0) | 窗口 0 被无条件拖拽/缩放,读写 `_wins[0]` 未初始化字段,可能放大为越界写 |
|
||
| 早期 sched started 后立即崩溃(devfs find rax=0) | B1(find 遍历未初始化栈数组) | `open("/dev/...")` 多级路径 strcmp NULL → #PF |
|
||
| `eyboard` 测试打印错乱(a[4] 应打印 o) | 编译器联合/str 索引问题 | `c.Addr(a[4])` 对 `t.CConst|str` 的地址计算错误(同类 A3 联合解析) |
|
||
| 设备文件/键盘输入异常 | A2(PS/2 表全 0)、A3(devfs 空名) | 初始化数据丢失 + 文件名 8 字节写 |
|
||
|
||
## 四、建议修复方向(TPV 编译器侧,按优先级)
|
||
|
||
1. **全局初始化器生成**(A1/A2):修复 vpi → LLVM IR 阶段负常量与字符数组初始化数据的丢失 —— 最高优先,直接影响所有 `= -1` 与字符表。
|
||
2. **联合指针类型解析**(A3/A4):`T | t.CPtr`(含 `str | t.CPtr`)联合类型应保留"裸指针"语义(i8* 元素步长 1),而非统一解析为 i8**;`c.Set(联合指针形参, v)` 应写所指地址而非改指针变量。
|
||
3. **对象型全局构造**(B2):`X: T = T()` 的 `__init__` self 应指向全局值 storage(`&X.stg`);`P = c.Addr(X)` 应生成 `store @X.stg → @P`,而非独立 `.ptr.stg` + 结构体值拷贝。
|
||
4. **类型保真**(A5/A6):无符号比较用 `icmp ult`;`%d/%i` 非 long 分支补 `trunc+sext`;统一 `memset` 声明;补 AllocPage 的 `~{al}` clobber。
|