使用Dwarf调试信息和源代码将变量映射到声明

3

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

请看以下代码:

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

   {
      struct B *b;

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

假设我有这个源代码,并且它是使用DWARF格式的调试信息编译的。如何使用源代码和调试信息确定变量b的类型为struct B *
我的意思是如何离线自动化它?问题在于,在DWARF.info部分中,没有源代码(例如行号)和作用域信息之间的映射。在上面的示例中,使用调试信息,我们可以确定存在一个foo()的子级别为struct A *类型的变量,还有一个foo()的其他子级别为struct B *类型的变量。解析源代码可以帮助确定发生访问的嵌套级别,但无法将访问的变量映射到其类型。因为在访问b的相同级别上有两种类型。
如果有一种方法可以强制编译器在调试信息中包含更多信息,则可以解决该问题。例如,向DW_TAG_lexical_block类型的DIEs的调试信息添加DW_AT_high_pcDW_AT_low_pc将有所帮助。
2个回答

5

您已经几乎回答了所有的问题,只有两个问题还没有解决。

首先,文件名/行号和程序计数器之间的关系编码在 .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”这个名称必须引用DIE在0xc4处的子级。该变量的类型由其给出。

 <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

编辑:

在你自己的回答中,你提供了一个反例,其中存在没有对应地址范围的词法块。这种DWARF不符合标准:DWARF 2的§3.4规定词法块条目具有DW_AT_low_pc和DW_AT_high_pc属性,并未暗示这些属性是可选的。如果你使用的是gcc,可能是"内联词法块缺少范围的DWARF调试信息"引起了这个问题。默认的mplayer配置包括-O2优化,它会打开内联;你将在父级DW_TAG_subprogram中看到这一点,该代码段是从中获取的draw_vertices()。解决此问题的方法是将-fno-inline添加到编译器选项中;这似乎并不能完全抑制所有内联,因此您可能希望完全禁用优化。


谢谢!据我所知,.aranges包括编译单元的范围,而词法块仅是编译单元的一部分。 - undefined
如果你从源代码的角度开始,比如 foo.c:14 这样的范围,那么你首先需要找到 foo.c 的 CU。.debug_aranges 无法帮助你:你需要解析每个 CU 的头部,找到一个匹配的名称。一旦你找到了正确的 CU,你必须自己检查它的后代,将找到的地址范围与所关心的 PC 进行比较。我的观点是,可以将词法块的地址范围编码为 DW_AT_ranges,它引用 .debug_ranges 中的一个条目(而不是 .debug_aranges)。 - undefined
问题在于,并不是所有的词法块都包含地址信息,无论是以 DW_AT_low_pc 的形式还是以 DW_AT_ranges 的形式。我已经找到了一种解决方案,即使没有这些信息也可以完成。但是提供这些地址信息会更容易一些。 - undefined
你有一个可重现的测试案例来生成一个表示可执行代码但缺少地址范围的词法块吗?听起来像是一个错误。你有什么解决方案? - undefined

1
这是使用-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)源代码分析:

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

查找作用域在作用域树中的级别(一棵显示父/子关系的树,类似于可以在.info中找到的内容)。

此时,我们已经拥有了对应于变量访问的作用域的起始行和作用域树中的级别(例如,在原始问题中所示的代码中的第12行和第2级)。

2)DebugInfo分析:

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接