Files
Viper_Wiki/17-traceback.md
T

7.3 KiB
Raw Blame History

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 风格定位,例如:

Traceback (most recent call last, compile-time error):
  File "D:\...\App\main.vp", line 238

实现位于 TransPyV/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 元数据(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)。

相关文件