基于DWARF调试信息与源码,根据变量访问行号确定其类型
这个问题确实很典型——当代码里有嵌套作用域的同名变量时,默认的DWARF信息没法直接把源码中的变量访问和对应的类型绑定。咱们一步步拆解解决方案:
问题场景还原
先看你给出的示例代码:
void foo() { { struct A *b; } { struct B *b; b = malloc(sizeof(struct B)); } }
编译后,DWARF的.debug_info段里会记录foo()下有两个名为b的变量,分别对应struct A*和struct B*类型,但默认情况下,包裹这些变量的DW_TAG_lexical_block(也就是代码里的花括号作用域)没有DW_AT_low_pc和DW_AT_high_pc属性,导致我们没法把源码中b = malloc(...)这行的访问,和对应的struct B*类型的b关联起来。
核心解决方案:强制编译器为 lexical block 添加PC范围属性
解决这个问题的关键,是让编译器在DWARF中为每个DW_TAG_lexical_block生成DW_AT_low_pc(作用域起始PC)和DW_AT_high_pc(作用域结束PC)属性。这样我们就能把源码行→PC地址→lexical block→变量类型的链路打通。
编译时的配置
针对常用编译器,你可以用这些选项强制生成所需的DWARF信息:
- GCC:使用
-g3 -fvar-tracking-assignments编译。-g3会生成更详细的调试信息,-fvar-tracking-assignments会让编译器追踪变量的作用域PC范围,为lexical block添加上下文信息。 - Clang:使用
-gfull编译,这个选项会生成包含lexical block PC范围的完整DWARF信息。
离线自动化分析的步骤
当你拿到带完整DWARF信息的二进制后,可以按以下步骤离线确定变量类型:
映射源码行到PC地址
解析DWARF的.debug_line段(行号程序),找到你要分析的变量访问行(比如示例中b = malloc(sizeof(struct B));所在的行)对应的机器码PC地址范围。遍历DWARF的作用域信息
解析.debug_info段,找到foo()对应的DW_TAG_subprogramDIE(调试信息条目),然后遍历它的子DIE,筛选出所有DW_TAG_lexical_block类型的条目——现在每个条目都有DW_AT_low_pc和DW_AT_high_pc,表示这个作用域覆盖的PC范围。匹配变量访问所在的作用域
检查第一步得到的PC地址,落在哪个DW_TAG_lexical_block的PC区间内,这个block就是变量访问所在的嵌套作用域。确定变量类型
在该lexical block的子DIE中,找到名字为b的DW_TAG_variable条目,读取它的DW_AT_type属性,通过这个属性指向的DIE,就能获取到变量的类型(也就是struct B*)。
替代方案(如果无法修改编译选项)
如果因为某些原因没法让编译器生成lexical block的PC范围,你可以尝试结合源码的作用域嵌套结构和DWARF中变量的声明顺序:
- 先解析源码,确定变量访问所在的嵌套层级和作用域顺序;
- 再遍历
foo()下的DW_TAG_variable条目,按照它们在DWARF中出现的顺序,对应源码中作用域的声明顺序。
不过这种方法可靠性较低,因为编译器可能会对作用域进行优化调整,变量在DWARF中的顺序不一定完全和源码一致,所以优先推荐修改编译选项的方案。
内容的提问来源于stack exchange,提问作者TheAhmad




