ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

LLVM codegen 中的调试信息生成:Debug Records、DWARF 标签与 CodeView 翻译全解析

LLVM codegen 中的调试信息生成:Debug Records、DWARF 标签与 CodeView 翻译全解析 LLVM codegen 中的调试信息生成Debug Records、DWARF 标签与 CodeView 翻译全解析【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rustrustc 的调试信息最终都要经由 LLVM 代码生成后端落地为二进制里的调试数据。本文基于 rustc 开发指南rustc-dev-guide中关于 LLVM codegen 调试信息的一节src/doc/rustc-dev-guide/src/debuginfo/llvm-codegen.md系统讲解 rustc 通过DIBuilderAPI 向 LLVM 传递调试信息、LLVM 将其归一化为与格式无关的debug record、并最终在目标平台要求 PDB 时把 DWARF 标签翻译为 CodeView 的完整链路并结合 compiler/rustc_codegen_llvm 与 compiler/rustc_codegen_ssa 的源码给出可落地的原理分析与排查思路。一、调试信息在 rustc 中的整体位置rustc 生成调试信息并非一蹴而就而是一条从 MIR 到调试器界面逐层传递的流水线。根据开发指南的调试信息总览完整链路大致为rustc 检查 MIR把相关的源码位置、符号与类型信息通过 LLVM 的DIBuilderAPI 传达给 LLVMLLVM 在编译期间把这类信息翻译成目标平台特定的调试信息格式DWARF、PDB/CodeView 等调试器读取并解释调试信息把指令地址映射回源码行并依据类型布局定位、读取内存中的变量调试器内置的格式化和样式化能力作用于变量用户脚本如 GDB/LLDB 的 Python pretty-printer、Visual Studio 的 Natvis进一步加工调试器前端把变量呈现给用户。本文聚焦其中的第 1、2 步——即 rustc 调用 LLVMDIBuilder之后LLVM 如何把调试信息表示为 debug record以及目标要求 PDB 时如何完成 DWARF 标签到 CodeView 的翻译。二、核心机制格式无关的 Debug Records2.1 什么是 Debug Record当 Rust 调用 LLVM 的DIBuilder函数时LLVM 会把给定的信息翻译成一种格式无关的debug recordLLVM 源码级调试文档中对其有专门定义。这些记录可以直接在 LLVM-IR 中检视——也就是说无论最终目标是 DWARF 还是 CodeViewLLVM IR 阶段看到的都是同一套抽象描述。这一设计的关键收益在于rustc 侧只需面向 LLVM 的元数据接口编程不需要关心目标平台的调试格式细节格式差异被 LLVM 统一收敛到 codegen 阶段。2.2 rustc 侧如何构造 Debug Record在 rustc 中与 LLVMDIBuilder直接交互的封装位于 compiler/rustc_codegen_llvm/src/debuginfo/di_builder.rs。其中DIBuilderBox持有对 LLVMDIBuilder的所有权指针通过LLVMCreateDIBuilder创建、LLVMDisposeDIBuilder释放pub(crate) fn new(llmod: ll Module) - Self { let raw unsafe { llvm::LLVMCreateDIBuilder(llmod) }; let raw ptr::NonNull::new(raw).unwrap(); Self { raw } }DIBuilderExttrait 则提供了一系列安全封装例如create_static_variable内部调用LLVMDIBuilderCreateGlobalVariableExpression创建全局变量调试元数据再通过LLVMGlobalSetMetadata(val, llvm::MD_dbg, global_var_expr)把该元数据挂到对应的 IR 全局值上——这正是debug record 以元数据形式存在于 LLVM-IR 中的直接体现。rustc 代码生成模块中驱动这些调用的逻辑集中在 compiler/rustc_codegen_llvm/src/debuginfo如metadata.rs、metadata/enums/、metadata/type_map.rs其模块文档 doc.md 明确写道Given the right metadata in the LLVM IR, the LLVM code generator is able to create DWARF debug symbols for the given code. The metadata is structured much like DWARF debugging information entries (DIE)...——即 rustc 的职责就是生成正确的元数据并插入 LLVM IRLLVM 负责把它落地为具体格式。三、关键约束Debug Record 中的标签永远是 DWARF 标签理解 debug record 时有一个必须牢记的要点调试记录里的标签tag始终以 DWARF 标签的形式存储。这意味着在 rustc 生成的 LLVM IR 元数据层面你看到的类型、变量、函数等节点都使用DW_TAG_*家族的标签来描述——例如DW_TAG_structure_type、DW_TAG_member、DW_TAG_subprogram等。这与目标平台最终使用的格式无关即使目标是 Windows/MSVC最终产物是 PDB/CodeViewIR 阶段的标签也仍然是 DWARF 语义的。这一点在 rustc 源码中同样可以印证枚举的调试节点生成compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums中DWARF 路径使用DW_TAG_variant/DW_TAG_variant_part等原生节点来描述可辨识联合discriminated union其层级如下摘自开发指南 rust-codegen 一节DW_TAG_structure_type (顶层类型) DW_TAG_variant_part (变体部分) DW_AT_discr (指向判别成员 DW_TAG_member 的引用) DW_TAG_member (判别成员) DW_TAG_variant (变体 1) DW_TAG_variant (变体 2) DW_TAG_variant (变体 3) DW_TAG_structure_type (变体 1 的类型) DW_TAG_structure_type (变体 2 的类型) DW_TAG_structure_type (变体 3 的类型)而一旦目标要求 PDB同一份 DWARF 标签描述会在 codegen 期间被送入一个专门的翻译模块转换为 CodeView 对应的表示。四、PDB 目标下的标签翻译DWARF → CodeView4.1 翻译发生的层级文档明确指出当目标调用 PDB 调试信息时codegen 阶段会把 debug record 中的 DWARF 标签交给一个模块翻译成 CodeView 对应物。对应的 LLVM 实现位于 LLVM 源码树中的llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp这是 LLVM 项目内部的路径rustc 仓库本身只消费 LLVM 的产物。需要区分两个层级rustc 层rustc 通过cpp_like_debuginfo判断见 compiler/rustc_codegen_ssa/src/debuginfo/type_names.rspub fn cpp_like_debuginfo(tcx: TyCtxt_) - bool { tcx.sess.target.is_like_msvc }即只要目标是 MSVC 风格的平台rustc 就会生成 C 风格cpp_like的调试信息与类型名从源头适配 CodeView/Natvis 的语义与限制详见 rust-codegen 一节中的命名变换表例如str→ref$str$、[T; N]→array$T, N、RustEnum→enum2$RustEnum等。LLVM 层LLVM 的AsmPrinter含CodeViewDebug.cpp在发射目标汇编/目标文件时读取 IR 元数据debug record并按照目标格式输出。PDB 目标下即由 CodeViewDebug 完成 DWARF 标签到 CodeView 记录的翻译。4.2 一个具体的翻译示例枚举类型rustc 为 PDB 生成类 C 的可辨识联合结构在 compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums/cpp_like.rs 中实现代码注释中给出了完整的 C 伪代码其中NAME、DISCR_EXACT、DISCR_BEGIN、DISCR_END等关联常量名需要与intrinsic.natvis保持一致union enum2$RUST_ENUM_NAME { enum VariantNames { First, Second }; struct Variant0 { struct First { /* fields */ }; static const enum2$RUST_ENUM_NAME::VariantNames NAME; static const unsigned long DISCR_EXACT; enum2$RUST_ENUM_NAME::Variant0::First value; }; struct Variant1 { struct Second { /* fields */ }; static enum2$RUST_ENUM_NAME::VariantNames NAME; static unsigned long DISCR_EXACT; enum2$RUST_ENUM_NAME::Variant1::Second value; }; enum2$RUST_ENUM_NAME::Variant0 variant0; enum2$RUST_ENUM_NAME::Variant1 variant1; unsigned long tag; }这些enum2$...编码在 IR 中时仍然是 DWARF 标签结构DW_TAG_structure_typeDW_TAG_variant_part等只有到 LLVM codegen 的 CodeView 翻译阶段才真正变成 PDB 里的 CodeView 对象。rustc 源码中同样注意了 128 位判别值的特殊情况tag128_lo/tag128_hi因为 Natvis、Visual Studio 与 WinDbg 不支持 128 位整数这也是rustc 生成 C 风格调试信息以适配 CodeView 限制的典型例证。4.3 MSVC 特有的函数签名修正除了标签翻译MSVC 目标还有一类独立的兼容性修正。在 compiler/rustc_codegen_llvm/src/debuginfo/mod.rs 中可以看到当cx.sess().target.is_like_msvc为真时函数签名里的定长数组如[u8; 4]或 ZST 数组如[(); 7]会被替换为指针类型以规避 MSDIA 在遇到函数类型中定长u8数组或零大小类型时的崩溃对应 FIXME(#42800) / issue #40477。这类修正发生在 rustc 侧与 LLVM 侧的 DWARF→CodeView 翻译互补共同保证 PDB 调试信息的可用性。五、如何检视 Debug Record实操路径5.1 在 LLVM-IR 层面查看调试元数据由于 debug record 以元数据形式存在最直接的检视方式是在编译时保留中间产物rustc --emitllvm-ir -C debuginfo2 your_program.rs然后在生成的.ll文件中查找!dbg标注、llvm.dbg.declare/llvm.dbg.value内建调用以及!DI*元数据节点。文档特别提醒函数 prologue 期间默认不发射源码位置llvm.dbg.declare例外——参数声明指令即使在 prologue 中间也会被链接到正确的源码位置见 compiler/rustc_codegen_llvm/src/debuginfo/doc.md 中关于start_emitting_source_locations()与 prologue 检测的说明。5.2 在目标文件层面查看最终格式DWARF可使用llvm-dwarfdumpllvm-dwarfdump --debug-info your_binary或readelf --debug-dumpinfo检视*-gnu目标产物。PDB/CodeViewLLVM 的llvm-pdbutil dump可转储 PDB 内容配合微软的pdb-rs等工具可进一步分析相关资源汇总见开发指南的 intro.md 一节。5.3 结合测试验证行为rustc 的调试信息测试位于 tests/debuginfo由compiletest执行。开发指南的 testing 一节 介绍了$DEBUGGER-repr指令与--bless机制--bless会更新内存中的期望数据并在无错误时写回目标文件例如./x test tests/debuginfo/basic-types/main.rs --bless。当你怀疑调试信息在 IR/目标文件层面的输出不符合预期时这是最直接的回归验证入口。六、总结一条链路、两层翻译把整条链路浓缩一下rustcrustc_codegen_llvm/src/debuginfo rustc_codegen_ssa/src/debuginfo检查 MIR通过DIBuilderAPI 生成元数据LLVM 把元数据归一化为格式无关的 debug record可在 LLVM-IR 中检视其中的标签恒为 DWARF 标签若目标要求 PDBLLVM 的AsmPrinter/CodeViewDebug在 codegen 阶段把 DWARF 标签翻译为 CodeView 对应物rustc 侧的cpp_like_debuginfo即is_like_msvc分支提前生成 C 风格的类型名与结构以适配 CodeView/Natvis 的能力边界。理解debug record 是格式无关的中间表示、标签永远是 DWARF 标签、PDB 翻译发生在 LLVM codegen这三点是排查 rustc 调试信息问题尤其是 Windows/MSVC 目标的钥匙先看 IR 元数据是否符合预期再看目标文件里 DWARF/CodeView 输出最后回到tests/debuginfo用--bless固化期望行为。【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表