Files
Viper_Wiki/17-traceback.md
T

108 lines
7.3 KiB
Markdown
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.
# 17 - 栈回溯与调试信息
TransPyV 里的 "traceback" 有**两套完全不同的东西**,务必区分:
| | 编译期 traceback | 运行期栈回溯 |
|---|---|---|
| 时机 | 编译源码出错时 | 编译出的**目标程序运行**时 |
| 触发 | 解析失败 / 类型错误 | 用户按键(Ctrl+C / Ctrl+Break)或进程崩溃 |
| 输出 | `Traceback (most recent call last, compile-time error):` + `File "...", line N` | `Traceback (most recent call last):` + `File "路径", line N, in 函数名` |
| 依赖 | 无 | `--traceback` 编出的 `<exe>.stackmap` 旁文件 |
## 编译期 traceback
编译期错误(AST 解析失败、类型错误等)由编译器打印 Python 风格定位,例如:
```text
Traceback (most recent call last, compile-time error):
File "D:\...\App\main.vp", line 238
```
实现位于 [TransPyV/App/lib/core/Gen/Type/TypeGenErr.vp](../TransPyV/App/lib/core/Gen/Type/TypeGenErr.vp)(`fatal_error`),入口 `GenApi.dump_parse_traceback`,在 Phase1 / Phase2 失败路径上调用。它与运行期无关。
## 运行期栈回溯(`--traceback`)
### 它做什么
运行期模块 [includes/traceback.vp](../includes/traceback.vp) 通过 `SetConsoleCtrlHandler` + `SetUnhandledExceptionFilter` 捕获「按键」或「崩溃」,转储主线程 / 出错线程的调用栈,并按 `<exe>.stackmap` 旁文件符号化成 `File "路径", line N, in 函数名`。
触发条件:
- `Ctrl+C`(`CTRL_C_EVENT`):打印调用栈后返回 `FALSE`(交还系统默认处理 ⇒ 进程终止);
- `Ctrl+Break`(`CTRL_BREAK_EVENT`):打印调用栈后返回 `TRUE`(已处理 ⇒ 进程继续);
- 其它控制事件(关窗 / 注销 / 关机):一律返回 `FALSE`,不转储、不干预;
- **崩溃**(未处理异常,经 `SetUnhandledExceptionFilter` 捕获):在**出错线程**上打印调用栈后返回 `EXCEPTION_CONTINUE_SEARCH`(异常交还系统,退出码 / WER / ASan 报告行为原样保留)。
**总开关 = 旁文件是否存在**:`Install()` 先求 `<当前 exe 完整路径>.stackmap`;该文件不存在则立刻返回、零副作用(不分配、不注册 handler);存在才注册 handler。因此在编译期显式加 `--traceback` 才会生效。
### 输出格式
```text
Traceback (most recent call last):
File "D:\...\App\main.vp", line 238, in main
w_upper: str = w_str.upper()
File "<0x7ff6... +0x1a2b no source line mapping>", line 0, in <unknown>
File "<0x7ffb... external>", line 0, in <unknown> [inferred]
```
- 帧行格式 `File "路径", line N, in 函数名`,其下再打一行 4 空格缩进的源码行;最外层在前、最内层在后(与 Python 一致)。
- 未命中映射的帧退化为纯地址形式(`no source line mapping` / `external`)。
- 栈扫描得到的帧标注 `[inferred]`(可能含误报)。
### 符号化:`<exe>.stackmap` 旁文件
运行期**不读 DWARF**,而是读链接后由编译器生成的 `<exe>.stackmap`。`BuildPipeline.WriteStackMap` 在链接成功后调用 `llvm-dwarfdump --debug-line --debug-info <exe>` 解析最终 PE 的 `.debug_line` / `.debug_info`,把每条地址归一化为运行期 RVA(`addr - ImageBase`),写出「地址 → 源行 / 函数名」映射。
文件布局:header 20B(`"TPCS"` + `u32 version=1` + `u32 entry_count` + `u32 str_count` + `u32 str_bytes`),记录区每条 20B(`u64 rva | u32 file_off | u32 line | u32 func_off`,按 `rva` 严格升序),字符串池偏移 0 为空串哨兵。运行期加载后按 `rva` 做 floor 二分查找。
### DWARF 元数据由编译器自己发射
DWARF 不是靠 llc 的 `-g`,而是编译器在 LLVM IR 里自己发 DI 元数据([TransPyV/App/lib/core/Gen/DebugInfoGen.vp](../TransPyV/App/lib/core/Gen/DebugInfoGen.vp)):
- 节点层次:`!DIFile → !{ null } → !DISubroutineType → distinct !DICompileUnit → distinct !DISubprogram(每个 define)→ !DILocation(每条语句)`;
- 模块尾部追加 `!llvm.dbg.cu` 与 `!llvm.module.flags`(Debug Info Version 3);
- **`DIFile` 用绝对路径**(正斜杠归一)——否则 `llvm-dwarfdump --debug-line` 拿不到 comp_dir,行记录会退化成裸文件名 `main.vp`,运行期既显示不全也无法按路径重读源码行;
- 生效开关 = `--traceback`(`DebugInfoGen.SetEnabled` 镜像 `BuildPipeline._traceback_mode`);关闭时 IR 与未开 DWARF 前逐字节一致。
### 编译 / 链接参数
| 环节 | 参数 | 说明 |
|------|------|------|
| llc | `-frame-pointer=all` | 保留帧指针,保 RBP 链 |
| 链接 | `-ldbghelp` | `StackWalk64` 等符号 |
| 链接后 | `llvm-dwarfdump --debug-line --debug-info <exe>` | 生成 `<exe>.stackmap` |
| IR | `!llvm.dbg.cu` + `!llvm.module.flags(Debug Info Version 3)` | DWARF 元数据 |
| 启动期 | `@llvm.global_ctors` 构造器调用 `Install()` | 自动安装 handler |
`--traceback` 还会把运行期模块 `traceback` 加入可达集强制编入产物(在 includes 目录找不到 `traceback.vp` 会报错)。
### 取栈路径
- **主路径** = 纯 RBP 链(`WalkRbpChain`,零 dbghelp 依赖);
- **第二兜底** = 栈扫描(`ScanStack`,零 dbghelp、不依赖帧指针,结果为推断帧);
- **可选加强** = dbghelp `StackWalk64`(仅在 RBP 链拿不到 >1 帧且探针判定可用时启用,能穿过免帧指针的系统帧)。
ASan 运行时也会注册 DbgHelp 符号化器,但 DbgHelp 状态是**进程级共享**而非冲突;ASan 的 VEH 优先于未处理异常过滤器,故 ASan 正常报告时崩溃转储不会被调用。
## ASan 与 MemBuddy 检测
- **`--asan`**:在 `.ll → obj` 之前先跑 `opt -passes=forceattrs,asan -force-attribute=sanitize_address -asan-globals=false` 插桩。ASan 运行库**不由 TPV 注入**,必须由 `project.vpj` 的 `linker.static_libs` / `dynamic_libs` / `flags` 显式声明(出现 "asan" 即视为已声明),否则 fail-fast。
- **`--detection` / `--detection-deep`**:`MemBuddy` 内存池的分配检测(用户区前后各 2 字节 `0xCDCD` 标记 + 记录表探针),与 traceback 无关,是独立的内存诊断开关,作用于 `mb` 与 `perm_pool` 两个池。
## 已知限制
- **仅支持 Windows PE 产物**:读到非 PE 或 `SizeOfImage <= 0` 直接终止编译。
- **依赖 `llvm-dwarfdump` 在 PATH**:调用失败或解析不到任何行记录都会终止编译。
- **RBP 链可能断**:`-frame-pointer=all` 对系统 / CRT 免帧指针代码无效,被中断的系统帧可能 `RBP=0x0` 断链,需靠 dbghelp `StackWalk64` 还原。
- 栈扫描帧是推断帧,可能含误报;RBP 链 + 栈扫描上限 64 帧。
- DWARF 生成有深度上限(函数嵌套 `DBG_STACK_MAX = 256`);发射了 define 但 IR 无 DI 的模块,其帧只显示地址。
- `--repl` 与 `--traceback` / `--asan` 互斥(片段 DLL 无 `<exe>.stackmap`)。
## 相关文件
- [includes/traceback.vp](../includes/traceback.vp)(运行期栈回溯模块)
- [TransPyV/App/lib/core/Gen/DebugInfoGen.vp](../TransPyV/App/lib/core/Gen/DebugInfoGen.vp)(DWARF IR 发射)
- [TransPyV/App/lib/Phase/BuildPipeline.vp](../TransPyV/App/lib/Phase/BuildPipeline.vp)(llc / 链接 / stackmap 旁文件)
- [TransPyV/App/lib/core/Gen/Type/TypeGenErr.vp](../TransPyV/App/lib/core/Gen/Type/TypeGenErr.vp)(编译期 traceback)
- [includes/memhub.vp](../includes/memhub.vp)(`--detection` 语义)