开发一个 Linux 调试器(四):Elves 和 dwarves

2024年 7月 19日 60.5k 0

开发一个 Linux 调试器(四):Elves 和 dwarves-1

到目前为止,你已经偶尔听到了关于 dwarves、调试信息、一种无需解析就可以理解源码方式。今天我们会详细介绍源码级的调试信息,作为本指南后面部分使用它的准备。

系列文章索引

随着后面文章的发布,这些链接会逐渐生效。

  • 准备环境
  • 断点
  • 寄存器和内存
  • Elves 和 dwarves
  • 源码和信号
  • 源码级逐步执行
  • 源码级断点
  • 调用栈展开
  • 读取变量
  • 下一步
  • ELF 和 DWARF 简介

    ELF 和 DWARF 可能是两个你没有听说过,但可能大部分时间都在使用的组件。ELF(Executable and Linkable Format,可执行和可链接格式)是 Linux 系统中使用最广泛的目标文件格式;它指定了一种存储二进制文件的所有不同部分的方式,例如代码、静态数据、调试信息以及字符串。它还告诉加载器如何加载二进制文件并准备执行,其中包括说明二进制文件不同部分在内存中应该放置的地点,哪些位需要根据其它组件的位置固定(重分配)以及其它。在这些博文中我不会用太多篇幅介绍 ELF,但是如果你感兴趣的话,你可以查看这个很好的信息图或该标准。

    DWARF是通常和 ELF 一起使用的调试信息格式。它不一定要绑定到 ELF,但它们两者是一起发展的,一起工作得很好。这种格式允许编译器告诉调试器最初的源代码如何和被执行的二进制文件相关联。这些信息分散到不同的 ELF 部分,每个部分都衔接有一份它自己的信息。下面不同部分的定义,信息取自这个稍有过时但非常重要的 DWARF 调试格式简介:

    • .debug_abbrev .debug_info 部分使用的缩略语
    • .debug_aranges 内存地址和编译的映射
    • .debug_frame 调用帧信息
    • .debug_info 包括 DWARF 信息条目 ( DWARF Information Entries ) (DIEs)的核心 DWARF 数据
    • .debug_line 行号程序
    • .debug_loc 位置描述
    • .debug_macinfo 宏描述
    • .debug_pubnames 全局对象和函数查找表
    • .debug_pubtypes 全局类型查找表
    • .debug_ranges DIEs 的引用地址范围
    • .debug_str .debug_info 使用的字符串列表
    • .debug_types 类型描述

    我们最关心的是 .debug_line.debug_info 部分,让我们来看一个简单程序的 DWARF 信息。

    int main() {
        long a = 3;
        long b = 2;
        long c = a + b;
        a = 4;
    }
    

    DWARF 行表

    如果你用 -g 选项编译这个程序,然后将结果传递给 dwarfdump 执行,在行号部分你应该可以看到类似这样的东西:

    .debug_line: line number info for a single cu
    Source lines (from CU-DIE at .debug_info offset 0x0000000b):
    
                NS new statement, BB new basic block, ET end of text sequence
                PE prologue end, EB epilogue begin
                IS=val ISA number, DI=val discriminator value
            [lno,col] NS BB ET PE EB IS= DI= uri: "filepath"
    0x00400670  [   1, 0] NS uri: "/home/simon/play/MiniDbg/examples/variable.cpp"
    0x00400676  [   2,10] NS PE
    0x0040067e  [   3,10] NS
    0x00400686  [   4,14] NS
    0x0040068a  [   4,16]
    0x0040068e  [   4,10]
    0x00400692  [   5, 7] NS
    0x0040069a  [   6, 1] NS
    0x0040069c  [   6, 1] NS ET
    

    前面几行是一些如何理解 dump 的信息 - 主要的行号数据从以 0x00400670 开头的行开始。实际上这是一个代码内存地址到文件中行列号的映射。NS 表示地址标记一个新语句的开始,这通常用于设置断点或逐步执行。PE 表示函数序言(LCTT 译注:在汇编语言中,function prologue 是程序开始的几行代码,用于准备函数中用到的栈和寄存器)的结束,这对于设置函数断点非常有帮助。ET 表示转换单元的结束。信息实际上并不像这样编码;真正的编码是一种非常节省空间的排序程序,可以通过执行它来建立这些行信息。

    那么,假设我们想在 variable.cpp 的第 4 行设置断点,我们该怎么做呢?我们查找和该文件对应的条目,然后查找对应的行条目,查找对应的地址,在那里设置一个断点。在我们的例子中,条目是:

    0x00400686  [   4,14] NS
    

    假设我们想在地址 0x00400686 处设置断点。如果你想尝试的话你可以在已经编写好的调试器上手动实现。

    反过来也是如此。如果我们已经有了一个内存地址 - 例如说,一个程序计数器值 - 想找到它在源码中的位置,我们只需要从行表信息中查找最接近的映射地址并从中抓取行号。

    DWARF 调试信息

    .debug_info 部分是 DWARF 的核心。它给我们关于我们程序中存在的类型、函数、变量、希望和梦想的信息。这部分的基本单元是 DWARF 信息条目(DWARF Information Entry),我们亲切地称之为 DIEs。一个 DIE 包括能告诉你正在展现什么样的源码级实体的标签,后面跟着一系列该实体的属性。这是我上面展示的简单事例程序的 .debug_info 部分:

    .debug_info
    
    COMPILE_UNIT:
      DW_TAG_compile_unit
                        DW_AT_producer              clang version 3.9.1 (tags/RELEASE_391/final)
                        DW_AT_language              DW_LANG_C_plus_plus
                        DW_AT_name                  /super/secret/path/MiniDbg/examples/variable.cpp
                        DW_AT_stmt_list             0x00000000
                        DW_AT_comp_dir              /super/secret/path/MiniDbg/build
                        DW_AT_low_pc                0x00400670
                        DW_AT_high_pc               0x0040069c
    
    LOCAL_SYMBOLS:
        DW_TAG_subprogram
                          DW_AT_low_pc                0x00400670
                          DW_AT_high_pc               0x0040069c
                          DW_AT_frame_base            DW_OP_reg6
                          DW_AT_name                  main
                          DW_AT_decl_file             0x00000001 /super/secret/path/MiniDbg/examples/variable.cpp
                          DW_AT_decl_line             0x00000001
                          DW_AT_type                  
                          DW_AT_external              yes(1)
          DW_TAG_variable
                            DW_AT_location              DW_OP_fbreg -8
                            DW_AT_name                  a
                            DW_AT_decl_file             0x00000001 /super/secret/path/MiniDbg/examples/variable.cpp
                            DW_AT_decl_line             0x00000002
                            DW_AT_type                  
          DW_TAG_variable
                            DW_AT_location              DW_OP_fbreg -16
                            DW_AT_name                  b
                            DW_AT_decl_file             0x00000001 /super/secret/path/MiniDbg/examples/variable.cpp
                            DW_AT_decl_line             0x00000003
                            DW_AT_type                  
          DW_TAG_variable
                            DW_AT_location              DW_OP_fbreg -24
                            DW_AT_name                  c
                            DW_AT_decl_file             0x00000001 /super/secret/path/MiniDbg/examples/variable.cpp
                            DW_AT_decl_line             0x00000004
                            DW_AT_type                  
        DW_TAG_base_type
                          DW_AT_name                  int
                          DW_AT_encoding              DW_ATE_signed
                          DW_AT_byte_size             0x00000004
        DW_TAG_base_type
                          DW_AT_name                  long int
                          DW_AT_encoding              DW_ATE_signed
                          DW_AT_byte_size             0x00000008
    

    第一个 DIE 表示一个编译单元(CU),实际上是一个包括了所有 #includes 和类似语句的源文件。下面是带含义注释的属性:

    DW_AT_producer   clang version 3.9.1 (tags/RELEASE_391/final)    DW_OP_breg7+8
    

    位置列表取决于程序计数器所处的位置给出不同的位置。这个例子告诉我们如果程序计数器是在 DW_AT_low_pc 偏移量为 0x0 的位置,那么帧基就在和寄存器 7 中保存的值偏移量为 8 的位置,如果它是在 0x10x4 之间,那么帧基就在和相同位置偏移量为 16 的位置,以此类推。

    休息一会

    这里有很多的信息需要你的大脑消化,但好消息是在后面的几篇文章中我们会用一个库替我们完成这些艰难的工作。理解概念仍然很有帮助,尤其是当出现错误或者你想支持一些你使用的 DWARF 库所没有实现的 DWARF 概念时。

    如果你想了解更多关于 DWARF 的内容,那么你可以从这里获取其标准。在写这篇博客时,刚刚发布了 DWARF 5,但更普遍支持 DWARF 4。

    via: https://blog.tartanllama.xyz/c++/2017/04/05/writing-a-linux-debugger-elf-dwarf/

    作者:Simon Brand 译者:ictlyh 校对:wxy

    本文由 LCTT 原创编译,Linux中国 荣誉推出

    相关文章

    Linux 命令行的聊天工具 CenterIM
    Linux 桌面年仍未到来 但 Linux 移动之年已到来
    12 个在线学习 Linux 技能网站
    Linux Mint : 会是另一个新的 Ubuntu 吗?
    W3Conf 开发者大会将于下周召开
    Ubuntu 10.04 ARM 处理器上网本版本结束服务期

    发布评论