llvm为自定义语言生成调试信息需在ir构建阶段手动插入dwarf元数据:必须显式创建dicompileunit和disubprogram,配对emitdeclare与dbg_value,并调用dibuilder::finalize();禁用优化且分层验证各环节。

LLVM 为自定义语言生成调试信息,核心不是“加个开关就能有”,而是必须在 IR 构建阶段就主动插入符合 DWARF 语义的元数据结构,并严格维护 dbg_value 记录与指令的绑定关系。跳过这一步,哪怕 IR 看起来正确,llc 或 lldb 也只会显示“no debug info”或变量值始终为 <optimized out></optimized>。
必须手动创建 DICompileUnit 和 DISubprogram
LLVM 不会自动推导源码路径、语言类型或函数作用域。你得在模块初始化时显式构造 DIBuilder,并调用 DIBuilder::createCompileUnit 和 DIBuilder::createFunction 创建顶层调试单元和子程序描述。漏掉 DICompileUnit,整个模块的调试信息链就断了——llvm-dwarfdump 会直接报 error: invalid compile unit。
常见错误现象:
- 编译后
llvm-dwarfdump -debug-info your.bc输出为空或只有.debug_abbrev段 -
lldb ./a.out中bt能看到函数名,但frame variable显示variable not available
关键点:
-
createCompileUnit的File参数必须是真实存在的绝对路径(即使只是模拟),相对路径或空字符串会导致 DWARF 解析失败 -
DISubprogram必须绑定到函数的Function*,且需调用setSubprogram设置到函数元数据中,否则调试器无法将机器指令映射回源函数
变量声明必须配对 emitDeclare + dbg_value
仅在 IR 中生成 %x = alloca i32 不足以让变量出现在调试器里。你必须在变量首次被赋值前,用 DIBuilder::insertDeclare 告诉 LLVM “这个内存地址对应源码里的哪个变量”,再在每次写入后插入 dbg_value 记录(新 IR 格式下是 #dbg_value 注释,不是 call @llvm.dbg.value)。
容易踩的坑:
- 忘记在
alloca后立即调用insertDeclare:变量在调试器中显示为optimized out,即使后续有store和dbg_value - 对同一变量多次
insertDeclare:LLVM 不报错,但 DWARF 生成器可能丢弃早期记录,导致单步时变量值跳变 - 在 PHI 节点或循环头中插入
dbg_value时没处理支配边界:调试器在循环内单步可能读到旧值
示例(伪代码):
Value *Alloca = Builder.CreateAlloca(Type::getInt32Ty(Context)); DIBuilder->insertDeclare(Alloca, DILocalVariable, DebugLoc, Builder.GetInsertBlock()); // ... 后续 Builder.CreateStore(ConstantInt::get(Context, APInt(32, 42)), Alloca); DIBuilder->insertDbgValueIntrinsic(Alloca, DILocalVariable, DebugLoc, Builder);
禁用优化是调试信息可用的前提
LLVM 的调试信息默认不兼容中端优化。一旦启用 -O1 及以上,mem2reg、instcombine 等 pass 会合并指令、消除 alloca、重排计算顺序,而你的 dbg_value 若未通过 MachineFunction::substituteDebugValuesForInst 显式更新引用,就会指向已不存在的指令或操作数。
实操建议:
- 开发调试信息阶段,固定使用
llc -O0 -filetype=obj生成目标文件,避免任何 IR 优化干扰 - 若必须测试优化后效果,需在自定义 Pass 中监听
MachineInstr替换事件,并调用substituteDebugValuesForInst维护 DBG_INSTR_REF 映射 - 不要依赖
clang -g -O2的行为来反推自定义语言的实现逻辑——Clang 有大量专用调试信息补丁,不可复用
验证必须分层检查,不能只看 lldb
从 IR 到最终可执行文件,调试信息经过 opt → llc → ld 多道转换,任一环节出问题都会静默丢失。只靠 lldb 看不到变量,无法定位是前端没生成、中端删掉了、还是链接时 strip 了。
分层验证步骤:
- IR 层:
llvm-dis your.bc | grep -A5 "dbg.*value"确认存在#dbg_value行且指向有效 Value - 汇编层:
llc -O0 -S your.bc查看生成的 .s 文件是否含.loc和.Ldebug_*段 - 对象层:
llvm-dwarfdump -debug-info your.o检查DW_TAG_compile_unit、DW_TAG_variable是否完整 - 可执行层:
readelf -w your.out | head -20确认 .debug_info 段非空
最常被忽略的是 DIBuilder::finalize() 调用时机——它必须在所有 insertDeclare/insertDbgValueIntrinsic 完成后、保存 BC 文件前显式调用,否则元数据不会写入模块。这个调用没有错误提示,但缺失它,前面所有工作全白做。











