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

时间:2018-01-20 20:29:01

标签: c debugging static-analysis debug-symbols dwarf

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

请查看以下代码:

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

   {
      struct B *b;

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

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

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

如果有办法强制编译器在调试信息中包含更多信息,则可以解决问题。例如,将bDW_AT_high_pc添加到DW_AT_low_pc类型的DIE的调试信息中会有所帮助。

2 个答案:

答案 0 :(得分:1)

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

首先,文件名/行号与程序计数器之间的关系编码为.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行显示在行号表中:

  [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优化,它打开内联;您将在DW_TAG_subprogram的父draw_vertices()中看到这一点,从中获取示例代码。该错误的解决方法是将-fno-inline添加到编译器选项中;这似乎并没有抑制所有内联,所以你可能希望完全禁用优化。

答案 1 :(得分:0)

以下是使用objdump --dwarf=info mplayer选项编译的MPlayer-1.3.0的{​​{1}}输出。

-gdwarf-2

正如您在偏移<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 block。还有更多类似的lexical_block实例。

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

1)源代码分析:

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

在范围树中查找范围的级别(显示与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]); } 中的内容类似的父/子关系的树。

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

2)DebugInfo分析:

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

范围深于访问范围的声明无效。与目标变量具有相同范围的声明仅在其行号位于目标范围的起始行和变量访问的行号之间时才有效。