使用 Dwarf DebugInfo 和源代码将 Var 映射到声明

Map Var to Declaration Using Dwarf DebugInfo and Source Code

给定变量访问(不是声明)的行号,我如何确定它的类型(或者它在 .info 树中的声明 DIE)?

看下面的代码:

void foo()
{
   {
      struct A *b;
   }

   {
      struct B *b;

      b = malloc(sizeof(struct B));
   }
}

假设我有这个源代码,它是用 DWARF 格式的调试信息编译的。如何使用源代码和调试信息确定变量 b 的类型为 struct B *

我的意思是如何离线自动化它?问题是在 DWARF.info 部分中,源代码(例如,行号)和范围信息之间没有映射。在上面的示例中,使用调试信息,我们可以确定有一个 struct A * 类型的变量是 foo() 的子变量和一个 struct B * 类型的变量,它是 foo() 的另一个子变量foo()。解析源代码可以帮助确定发生访问的嵌套级别,但是无法将访问的变量映射到它的类型。因为访问b的同一级别有两种类型

如果有办法强制编译器在调试信息中包含更多信息,问题就可以解决。例如DW_TAG_lexical_block类型DIE的调试信息中加入DW_AT_high_pcDW_AT_low_pc会有帮助。

您已经回答了几乎所有您自己的问题;只缺少两件事。

首先,文件name/line编号和程序计数器之间的关系编码为.debug_line,而不是.debug_info

其次,变量不是 foo() 的子变量:每个变量都是词法块的子变量。程序结构的相关部分看起来像

DW_TAG_compile_unit
    DW_TAG_subprogram
        DW_TAG_lexical_block
            DW_TAG_variable
        DW_TAG_lexical_block
            DW_TAG_variable

词法块应与地址范围相关联,但这可能使用 DW_AT_ranges 而不是 DW_AT_low_pc/DW_AT_high_pc 进行编码;如果是这种情况,那么您需要解释 .debug_ranges.

为了说明手头的案例,我用 cc -g(Oracle Linux 上的 gcc 4.8.5)编译了以下内容...

  1 #include <stdlib.h>
  2 
  3 struct A { int a; };
  4 struct B { int b; };
  5 
  6 void foo()
  7 {
  8     {
  9         struct A *b;
 10     }
 11 
 12     {
 13         struct B *b;
 14         b = malloc(sizeof (struct B));
 15     }
 16 }

...并使用 'readelf -w' 解码 DWARF。第 14 行出现在此处的行号 table:

  [0x00000032]  Special opcode 124: advance Address by 8 to 0x8 and Line by 7 to 14

意思是我们对地址 0x8 感兴趣。 DIE 层次结构包括

<0><b>: Abbrev Number: 1 (DW_TAG_compile_unit)

<1><96>: Abbrev Number: 6 (DW_TAG_subprogram)
   <9d>   DW_AT_low_pc      : 0x0
   <a5>   DW_AT_high_pc     : 0x18

<2><b3>: Abbrev Number: 7 (DW_TAG_lexical_block)
   <b4>   DW_AT_low_pc      : 0x8
   <bc>   DW_AT_high_pc     : 0xe

<3><c4>: Abbrev Number: 8 (DW_TAG_variable)
   <c5>   DW_AT_name        : b
   <c7>   DW_AT_decl_file   : 1
   <c8>   DW_AT_decl_line   : 13
   <c9>   DW_AT_type        : <0xd2>

0xb3 处的 DIE 不包含任何进一步的词法块,因此它代表地址 0x8 处的最严格范围。因此,在这一点上,名称 "b" 必须引用位于 0xc4 的 DIE 的子代。此变量的类型由

给出
 <1><d2>: Abbrev Number: 9 (DW_TAG_pointer_type)
    <d3>   DW_AT_byte_size   : 8
    <d4>   DW_AT_type        : <0x81>

 <1><81>: Abbrev Number: 4 (DW_TAG_structure_type)
    <82>   DW_AT_name        : B
    <84>   DW_AT_byte_size   : 4

 <2><8b>: Abbrev Number: 5 (DW_TAG_member)
    <8c>   DW_AT_name        : b
    <90>   DW_AT_type        : <0x34>
    <94>   DW_AT_data_member_location: 0

 <1><34>: Abbrev Number: 3 (DW_TAG_base_type)
    <35>   DW_AT_byte_size   : 4
    <36>   DW_AT_encoding    : 5    (signed)
    <37>   DW_AT_name        : int

编辑:

在您自己的回答中,您给出了 mplayer 的反例,其中存在没有相应地址范围的词法块。这样的 DWARF 不符合标准:DWARF 2 的 §3.4 指出词法块条目具有 DW_AT_low_pc 和 DW_AT_high_pc 属性,并且没有暗示这些是可选的。假设您使用的是 gcc,此错误的可能候选者是“DWARF debug info for inlined lexical blocks missing range”。默认的 mplayer 配置包括 -O2 优化,它打开内联;您会在 draw_vertices() 的父 DW_TAG_subprogram 中看到这一点,示例代码就是从中获取的。该错误的解决方法是将 -fno-inline 添加到编译器选项;这似乎并没有抑制所有内联,因此您可能希望完全禁用优化。

这是使用 -gdwarf-2 选项编译的 MPlayer-1.3.0objdump --dwarf=info mplayer 的输出。

<2><4000e>: Abbrev Number: 43 (DW_TAG_lexical_block)
<3><4000f>: Abbrev Number: 37 (DW_TAG_variable)
<40010>   DW_AT_name        : px
<40013>   DW_AT_decl_file   : 1
<40014>   DW_AT_decl_line   : 2079
<40016>   DW_AT_type        : <0x38aed>
<3><4001a>: Abbrev Number: 37 (DW_TAG_variable)
<4001b>   DW_AT_name        : py
<4001e>   DW_AT_decl_file   : 1
<4001f>   DW_AT_decl_line   : 2080
<40021>   DW_AT_type        : <0x38aed>
<3><40025>: Abbrev Number: 0
<2><40026>: Abbrev Number: 0

正如您在偏移 0x4000e 处看到的,有一个没有属性的词法块。对应源码位于libvo/gl_common.c:2078:

for (i = 0; i < 4; i++) {
int px = 2*i;
int py = 2*i + 1;
mpglTexCoord2f(texcoords[px], texcoords[py]);
if (is_yv12) {
  mpglMultiTexCoord2f(GL_TEXTURE1, texcoords2[px], texcoords2[py]);
  mpglMultiTexCoord2f(GL_TEXTURE2, texcoords2[px], texcoords2[py]);
}
if (use_stipple)
  mpglMultiTexCoord2f(GL_TEXTURE3, texcoords3[px], texcoords3[py]);
mpglVertex2f(vertices[px], vertices[py]);
}

该块是一个for块。还有更多类似的 lexical_block 实例。

我的解决方案由两部分组成:

1)源码分析:

找到访问目标变量的范围(左右大括号)。其实我们只需要存储左大括号的行号即可。

在作用域树中查找作用域级别(显示 parent/child 关系的树类似于 .info

此时我们有对应于变量访问的范围的起始行和范围树中范围的级别(例如,原始问题中描述的代码中的第 12 行和第 2 级)。

2)DebugInfo分析:

现在,我们可以分析适当的 CU 并查找该目标变量的声明。重要的一点是,只有行号小于访问点行号的声明才有效。考虑到这一点,我们可以搜索全局范围,并继续更深层次,按顺序。

范围比访问范围更深的声明无效。与目标变量具有相同范围的声明只有在它们的行号在目标范围的起始行和变量访问的行号之间时才有效。