7.3 KiB
16 - 栈回溯与调试信息
ViperC 里的 "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 风格定位,例如:
Traceback (most recent call last, compile-time error):
File "D:\...\App\main.vp", line 238
实现位于 ViperC/App/lib/core/Gen/Type/TypeGenErr.vp(fatal_error),入口 GenApi.dump_parse_traceback,在 Phase1 / Phase2 失败路径上调用。它与运行期无关。
运行期栈回溯(--traceback)
它做什么
运行期模块 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 才会生效。
输出格式
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 元数据(ViperC/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 运行库不由 VPC 注入,必须由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断链,需靠 dbghelpStackWalk64还原。 - 栈扫描帧是推断帧,可能含误报;RBP 链 + 栈扫描上限 64 帧。
- DWARF 生成有深度上限(函数嵌套
DBG_STACK_MAX = 256);发射了 define 但 IR 无 DI 的模块,其帧只显示地址。 --repl与--traceback/--asan互斥(片段 DLL 无<exe>.stackmap)。
相关文件
- includes/traceback.vp(运行期栈回溯模块)
- ViperC/App/lib/core/Gen/DebugInfoGen.vp(DWARF IR 发射)
- ViperC/App/lib/Phase/BuildPipeline.vp(llc / 链接 / stackmap 旁文件)
- ViperC/App/lib/core/Gen/Type/TypeGenErr.vp(编译期 traceback)
- includes/memhub.vp(
--detection语义)