加速访问(Accelerated Access)

更高效地查询

按名称查找数据对象或函数:在调试过程中,当tracee暂停执行时,调试器经常需要根据符号名称查找对应的数据对象或函数的调试信息。这些信息可能分布在当前或其他编译单元中。调试器有时只知道程序构造(如变量、函数、类型等)的名称,有时则只有地址。如果仅通过DWARF调试信息条目(DIEs)按名称查找,调试器就需要遍历每个编译单元中的所有DIEs,这是非常耗时的。

按名称查找类型:在某些编程语言中(如C++),类型名称必须始终引用相同的具体类型。在这种情况下,编译器可以选择在所有编译单元中消除重复的类型定义。因此,调试器需要一种高效的方法来通过名称快速定位具体的类型定义。与查找全局数据对象类似,这也需要搜索程序中所有编译单元的类型定义相关DIEs。

按地址查找:当需要通过地址查找子例程的调试信息时,调试器可以利用编译单元CIE的上下pc属性来快速缩小搜索范围。但是,这些属性仅覆盖了与编译单元条目关联的代码段地址范围。而要通过地址查找数据对象的调试信息,则需要进行完整的搜索。此外,在大型程序中跨不同编译单元搜索调试信息条目时,可能需要访问大量内存页面,这会显著影响调试器的性能。

为了实现更高效的按名称和按地址查找程序实体(包括数据对象、函数和类型),DWARF信息生成器可以额外生成三种专门的表。这些表包含了特定编译单元条目所拥有的调试信息条目的相关信息,并且采用了更紧凑的数据格式。

按名称查询(Lookup by Name)

为了支持高效地按名称进行查找,DWARF额外维护了两张表:.debug_pubnames.debug_pubtypes。其中,.debug_pubnames描述全局对象和函数的名称,.debug_pubtypes描述全局类型。这两张表本质上是名称到具体DIE(调试信息条目)位置的映射表。

理论上,调试器确实可以通过预先分析 .debug_info 中的所有 DIE 信息,自行构建名称到 DIE 的映射表来实现类似的功能。但是 .debug_pubnames 和 .debug_pubtypes section 仍然具有其独特的价值:

  1. 避免重复工作 - 这些表已经由编译器优化并生成,调试器无需重复这个耗时的过程
  2. 内存效率 - 这些表采用了更紧凑的格式,比完整解析 .debug_info 并在内存中维护映射要节省空间
  3. 按需加载 - 调试器可以根据需要只加载这些表的相关部分,而不必一次性加载并解析所有 .debug_info
  4. 标准化 - 提供了统一的查询接口,使不同的调试器无需各自实现不同的索引机制

因此,这些 section 作为“可选的”优化机制,可以帮助调试器在性能和资源消耗上达到更好的平衡。它们的存在大大提升了调试过程中按名称查找的效率。

.debug_pubnames.debug_pubtypes section的数据组织,程序中每个编译单元在.debug_pubnames都存在一个对应的单元,每个单元包含:

  1. 头部信息(Header)
    • unit_length: 该单元的总长度(不包括length字段本身)
    • version: 版本号(2或3)
    • debug_info_offset: 对应编译单元在.debug_info中的偏移量
    • debug_info_length: 对应编译单元在.debug_info中的长度
  2. 名称条目(Name Entry)列表,每个条目包含:
    • offset: DIE在编译单元内的偏移量
    • name: 以null结尾的字符串,表示全局对象或函数的名称
  3. 结束标记
    • offset为0表示该编译单元的名称条目列表结束

这种组织方式使得调试器可以: 快速定位到名称对应的特定编译单元信息,还不需要从头解析完整的DIEs,实现了按需加载。

按地址查询(Lookup by Address)

为了支持高效的按地址查找,DWARF在.debug_aranges section中维护了一个专门的加速查询表。该表由一系列可变长度的条目组成,每个条目对应一个编译单元,记录了该编译单元在程序地址空间中所占用的地址范围信息。由于不同的编译单元会占用程序地址空间中互不重叠的区域,通过这个表可以快速定位到包含特定地址的编译单元。

虽然调试器也可以通过预先分析所有编译单元DIE并建立自己的地址范围索引来实现类似功能,但.debug_aranges section仍然有其价值:

  1. 它提供了一个标准化的、经过优化的数据结构,避免了每个调试器都要实现自己的索引机制
  2. 对于大型程序,预加载和分析所有编译单元DIE会消耗大量内存和时间,而.debug_aranges可以按需加载
  3. 编译器在生成这个表时可以应用特定的优化,使其更紧凑和高效

因此,.debug_aranges section作为DWARF标准的一部分,为调试器提供了一个可选但有价值的性能优化机制。

.debug_aranges section的数据组织如下,程序中每个编译单元在.debug_aranges都存在一个对应的单元,每个单元包括:

  1. 头部信息(Header)
    • unit_length: 该单元的总长度(不包括length字段本身)
    • version: 版本号(2)
    • debug_info_offset: 对应编译单元在.debug_info中的偏移量
    • address_size: 目标机器的地址大小(字节数)
    • segment_size: 段选择器的大小(字节数)
  2. 地址范围描述符(Address Range Descriptor)列表,每个描述符包含:
    • segment: 段选择器(如果segment_size非0)
    • address: 范围的起始地址
    • length: 范围的长度 描述符按起始地址排序,相邻描述符之间不重叠。使用全0的描述符(address和length都为0)表示列表结束。
  3. 对齐填充
    • 在描述符列表之前添加必要的填充字节,使得第一个描述符的地址按照(2 * address_size)字节对齐

这种组织结构使得调试器可以: 通过二分查找快速定位包含特定地址的编译单元,无需从头解析完整DIEs,实现了按需加载。

本文小结

DWARF v4中提出的上述加速查询的辅助表,是可选的优化方案,意味着编译工具链不一定要生成,调试器也不一定非得读取。DWARF v5中已经将 .debug_pubnames 和 .debug_pubtypes 合并成了 .debug_names。实际情况下,编译工具链不一定生成,即使生成了调试器也不一定使用,大家先了解即可。

results matching ""

    No results matching ""