iOS开发札记iOS 精讲iOS开发经验集

DWARF, 说不定你也需要它哦

2018-04-23  本文已影响733人  MasterKang

DWARF 是一种调试信息格式,通常用于源码级别调试
相关资料比较琐碎, 整理给大家, 希望大家可以用得上

如没有特殊说明, 命令执行环境为 OS X

什么是 DWARF ?

目前 DWARF 已经在类UNIX系统中逐步替换 stabs(symbol table strings) 成为主流的调试信息格式。
使用GCC或者LLVM系列编译器都可以很方便生成DWARF调试信息。

下面是其发展历史:

DWARF版本 年份 组织 详情
DWARF1 1992 Unix SVR4, PLSIG, UnixInternational 贝尔实验室 1988 年开发用于SVR4, 用于C compiler & SDB, PLSIG 和 Unix International在1992年为其编写了文档, 缺点: 结构不紧凑
DWARF2 1993 PLSIG PLSIG 做了一些优化(主要是减少调试信息大小),于1993年发布了DWARF 2 草案。但是由于摩托罗拉的 88000 处理器出现了一些严重故障,导致了一家名为 Open88 的公司破产, 而 Open88 公司是 PLSIG 和 UnixInternational 的金主, 随后这两个组织随即解散, DWARF 2 正式标准就从未发布。
DWARF3 2005 Free Standards Group 因为在 HP/Intel IA­64 architecture 架构及 C++ ABI 方面有更好的表现, Free Standards Group 在 2003 年完成了草案并在2005年发布了正式标准
DWARF4 2010 DWARF Debugging Format Committee Free Standards Group 和 Open Source Development Labs 在 2007 年合并为了著名的 Linux Foundation, DWARF委员会随后以独立方式存在并创建了网站 dwarfstd.org, 进行了一系列的更新后(支持LVIM, 对 Type Description存储的优化等),于2010 年发布 DWARF4
DWARF5 2017 DWARF Debugging Format Committee 在经过六年的开发后, DWARF委员会于 2017 年发布了 DWARF5 标准, 主要特性有: 更优的数据压缩, 调试信息从可执行文件的分离, 对宏定义有更好支持, 更快的搜索符号(symbol)

下面列出DWARF的一些竞品,方便大家更了解调试格式的发展

格式 年份 典型使用环境
stabs (symbol table strings) 1981 广泛使用于 Unix 环境, 目前已经逐步被 DWARF 替换
COFF (Common Object File Format) 1983 UNIX System V(AT&T), AIX(IBM, XCOFF), DEC、SGI(ECOFF)
IEEE­695 1990 虽是 IEEE 标准, 但是支持少数几种处理器架构, 目前已经基本消亡
PE­COFF (PE/COFF) 1995(?) Windows PE, Windows NT, COFF 最流行的变种
OMF (Object Module Format) 1995(?) CP/M, DOS, OS/2, embedded systems
PDB(Program database) ? Windows, Microsoft Visual Studio 的默认调试格式

我们为什么需要 DWARF ?

上面我们提到DWARF主要为调试器(debugger)服务,我们通过实现一个调试器Demo,帮助大家了解整个技术链。

首先,我们看下一般调试器的样子:


image.png

下面的例子源于 How debuggers work

这个简单的调试器只完成一个功能: 打印被调试程序运行时执行的所有指令

下面的例子使用了 Linux ptrace系统调用, 虽然我很想用 Mac 做演示, 但是 ptrace(OS X) 在Mac上已经被阉割了, 缺少 GETREGS 这种关键的 ptrace 功能,
而使用 thread_get_state() 又过于复杂, 所以 ...

体验编写一个调试器-Demo

docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined \
    -it harisekhon/ubuntu-dev /bin/bash
mkdir ptrace-example-mac; cd ptrace-example-mac

cat > hello.c <<EOF
#include <stdio.h>

int main() {
    printf("Hello, world!?\n");
    return 0;
}
EOF
cat > main.c <<EOF
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <unistd.h>


void procmsg(const char* format, ...);
void run_target(const char* programname);
void run_debugger(pid_t child_pid);

int main(int argc, char** argv)
{
    pid_t child_pid;

    if (argc < 2) {
        fprintf(stderr, "Expected a program name as argument\n");
        return -1;
    }

    child_pid = fork();
    if (child_pid == 0)
        run_target(argv[1]);
    else if (child_pid > 0)
        run_debugger(child_pid);
    else {
        perror("fork");
        return -1;
    }

    return 0;
}

/* Print a message to stdout, prefixed by the process ID
*/
void procmsg(const char* format, ...)
{
    va_list ap;
    fprintf(stdout, "[%d] ", getpid());
    va_start(ap, format);
    vfprintf(stdout, format, ap);
    va_end(ap);
}


void run_target(const char* programname)
{
    procmsg("target started. will run '%s'\n", programname);

    /* Allow tracing of this process */
    if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0) {
        perror("ptrace");
        return;
    }

    /* Replace this process's image with the given program */
    execl(programname, programname, (char *)0);
}


void run_debugger(pid_t child_pid)
{
    int wait_status;
    unsigned icounter = 0;
    procmsg("debugger started\n");

    /* Wait for child to stop on its first instruction */
    wait(&wait_status);

    while (WIFSTOPPED(wait_status)) {
        icounter++;
        struct user_regs_struct regs;
        ptrace(PTRACE_GETREGS, child_pid, 0, &regs);
        unsigned instr = ptrace(PTRACE_PEEKTEXT, child_pid, regs.rip, 0);

        procmsg("icounter = %u.  IP = 0x%08x.  instr = 0x%08x\n",
                icounter, regs.rip, instr);

        /* Make the child execute another instruction */
        if (ptrace(PTRACE_SINGLESTEP, child_pid, 0, 0) < 0) {
            perror("ptrace");
            return;
        }

        /* Wait for child to stop on its next instruction */
        wait(&wait_status);
    }

    procmsg("the child executed %u instructions\n", icounter);
}
EOF
gcc -O0 hello.c -o hello    # 编译被调试程序
gcc main.c -o ptrace-example-linux    # 编译调试器 demo

编译完成后工作目录如下:

root@9e0370a08026:/ptrace-example-mac# ll
total 40
drwxr-xr-x 2 root root 4096 Apr 21 05:19 ./
drwxr-xr-x 1 root root 4096 Apr 21 05:14 ../
-rwxr-xr-x 1 root root 8600 Apr 21 05:18 hello*
-rw-r--r-- 1 root root   81 Apr 21 05:17 hello.c
-rw-r--r-- 1 root root 1888 Apr 21 05:17 main.c
-rwxr-xr-x 1 root root 9248 Apr 21 05:19 ptrace-example-linux*
./ptrace-example-linux hello    # 执行 Hello 并打印Hello运行过程中执行的指令数

运行结果如下:

...
[60] icounter = 141731.  IP = 0x3818271a.  instr = 0x00e7b841
[60] icounter = 141732.  IP = 0x38182720.  instr = 0x00003cbe
[60] icounter = 141733.  IP = 0x38182725.  instr = 0x0f6619eb
[60] icounter = 141734.  IP = 0x38182740.  instr = 0x44d78948
[60] icounter = 141735.  IP = 0x38182743.  instr = 0x0fc08944
[60] icounter = 141736.  IP = 0x38182746.  instr = 0x3d48050f
[60] the child executed 141736 instructions

调试器总结

从上面的调试器Demo执行的结果及源码看, 调试器从 ptrace 系统调用拿到的信息有

但从我们平时使用的调试器提供给我们更多的信息:

调试器如何从一些十分基础的信息,例如 IP(指令地址), 呈现给我们如此丰富的调试信息呢?

那便我们为什么需要 DWARF, 其提供了程序运行时信息(Runtime)到源码信息的映射(Source File)

IP|regs|address      >>>  Source File 
(Runtime)           DWARF    

认识 DWARF

使用 GCC 生成 DWARF 调试信息

认识 DWARF 的第一步便是如何生成 DWARF 信息, 所幸这个过程非常简单

我们使用一个名为 foo.c 的示例程序来演示 生成并探索 DWARF 内容

mkdir show-dwarf; cd show-dwarf;

cat > foo.c <<EOF
int foo(int a, int b) {
    int c;
    static double d = 5.0;
    c = a + b;
    return c;
}

int main() {
    int r;
    r = foo(2, 3);
    return 0;
}
EOF
gcc -O0 -gdwarf-4 foo.c -o foo

编译完成后, 会发现多了 foo.dSYM 的目录, 当前工作目录文件列表如下:

masterkang: ~/Documents/show-dwarf > ll
total 32
-rwxr-xr-x  1 masterkang  staff  8728  4 21 13:58 foo
-rw-r--r--  1 masterkang  staff   153  4 21 13:57 foo.c
drwxr-xr-x  3 masterkang  staff    96  4 21 13:58 foo.dSYM
masterkang: ~/Documents/show-dwarf > PATH=/usr/bin lldb foo
(lldb) target create "foo"
Current executable set to 'foo' (x86_64).
(lldb) b foo
Breakpoint 1: where = foo`foo + 10 at foo.c:4, address = 0x0000000100000f6a
(lldb) run
Process 96385 launched: '/Users/masterkang/Documents/show-dwarf/foo' (x86_64)
Process 96385 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100000f6a foo`foo(a=2, b=3) at foo.c:4
   1    int foo(int a, int b) {
   2        int c;
   3        static double d = 5.0;
-> 4        c = a + b;
   5        return c;
   6    }
   7
Target 0: (foo) stopped.
(lldb)
cd foo.dSYM/Contents/Resources/DWARF

masterkang: ~/Documents/show-dwarf/foo.dSYM/Contents/Resources/DWARF > ll
total 24
-rw-r--r--  1 masterkang  staff  9166  4 21 13:58 foo
masterkang: ~/Documents/show-dwarf/foo.dSYM/Contents/Resources/DWARF > file foo
foo: Mach-O 64-bit dSYM companion file x86_64

从描述看, 可以看到这是一个 Mach-O 文件

masterkang: ~/Documents/show-dwarf/foo.dSYM/Contents/Resources/DWARF > size -x -m -l foo
Segment __PAGEZERO: 0x100000000 (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0x1000 (vmaddr 0x100000000 fileoff 0)
    Section __text: 0x4b (addr 0x100000f60 offset 0)
    Section __unwind_info: 0x48 (addr 0x100000fac offset 0)
    total 0x93
Segment __DATA: 0x1000 (vmaddr 0x100001000 fileoff 0)
    Section __data: 0x8 (addr 0x100001000 offset 0)
    total 0x8
Segment __LINKEDIT: 0x1000 (vmaddr 0x100002000 fileoff 4096)
Segment __DWARF: 0x1000 (vmaddr 0x100003000 fileoff 8192)
    Section __debug_line: 0x68 (addr 0x100003000 offset 8192)
    Section __debug_pubnames: 0x29 (addr 0x100003068 offset 8296)
    Section __debug_pubtypes: 0x25 (addr 0x100003091 offset 8337)
    Section __debug_aranges: 0x40 (addr 0x1000030b6 offset 8374)
    Section __debug_info: 0xba (addr 0x1000030f6 offset 8438)
    Section __debug_abbrev: 0x78 (addr 0x1000031b0 offset 8624)
    Section __debug_str: 0x78 (addr 0x100003228 offset 8744)
    Section __apple_names: 0x74 (addr 0x1000032a0 offset 8864)
    Section __apple_namespac: 0x24 (addr 0x100003314 offset 8980)
    Section __apple_types: 0x72 (addr 0x100003338 offset 9016)
    Section __apple_objc: 0x24 (addr 0x1000033aa offset 9130)
    total 0x3ce
total 0x100004000

可以看到有一个名为 __DWARF 的 Segment, 下面包含 __debug_line, __debug_pubnames, __debug_pubtypes ... 等很多歌Section。
这些 Section 便是 DWARF 在 .dSYM 中的存储方式,如何观察这些 Section 的内容呢?

输入命令 dwarfdump foo --debug-info 可展示 __debug_line Section 下的内容(非原始内容, 已经经过格式化处理方便查看)
下一小节我们会详细说明 DWARF info 段

下面的这段内容我们会反复引用,下文称之为 DWARF info 示例

masterkang: ~/Documents/show-dwarf/foo.dSYM/Contents/Resources/DWARF > dwarfdump foo --debug-info
----------------------------------------------------------------------
 File: foo (x86_64)
----------------------------------------------------------------------
.debug_info contents:

0x00000000: Compile Unit: length = 0x000000b6  version = 0x0004  abbr_offset = 0x00000000  addr_size = 0x08  (next CU at 0x000000ba)

0x0000000b: TAG_compile_unit [1] *
             AT_producer( "Apple LLVM version 9.1.0 (clang-902.0.39.1)" )
             AT_language( DW_LANG_C99 )
             AT_name( "foo.c" )
             AT_stmt_list( 0x00000000 )
             AT_comp_dir( "/Users/masterkang/Documents/show-dwarf" )
             AT_low_pc( 0x0000000100000f60 )
             AT_high_pc( 0x0000004b )

0x0000002a:     TAG_subprogram [2] *
                 AT_low_pc( 0x0000000100000f60 )
                 AT_high_pc( 0x00000018 )
                 AT_frame_base( rbp )
                 AT_name( "foo" )
                 AT_decl_file( "foo.c" )
                 AT_decl_line( 1 )
                 AT_prototyped( true )
                 AT_type( {0x000000b2} ( int ) )
                 AT_external( true )

0x00000043:         TAG_variable [3]
                     AT_name( "d" )
                     AT_type( {0x00000083} ( double ) )
                     AT_decl_file( "foo.c" )
                     AT_decl_line( 3 )
                     AT_location( [0x0000000100001000] )

0x00000058:         TAG_formal_parameter [4]
                     AT_location( fbreg -4 )
                     AT_name( "a" )
                     AT_decl_file( "foo.c" )
                     AT_decl_line( 1 )
                     AT_type( {0x000000b2} ( int ) )

0x00000066:         TAG_formal_parameter [4]
                     AT_location( fbreg -8 )
                     AT_name( "b" )
                     AT_decl_file( "foo.c" )
                     AT_decl_line( 1 )
                     AT_type( {0x000000b2} ( int ) )

0x00000074:         TAG_variable [5]
                     AT_location( fbreg -12 )
                     AT_name( "c" )
                     AT_decl_file( "foo.c" )
                     AT_decl_line( 2 )
                     AT_type( {0x000000b2} ( int ) )

0x00000082:         NULL

0x00000083:     TAG_base_type [6]
                 AT_name( "double" )
                 AT_encoding( DW_ATE_float )
                 AT_byte_size( 0x08 )

0x0000008a:     TAG_subprogram [7] *
                 AT_low_pc( 0x0000000100000f80 )
                 AT_high_pc( 0x0000002b )
                 AT_frame_base( rbp )
                 AT_name( "main" )
                 AT_decl_file( "foo.c" )
                 AT_decl_line( 8 )
                 AT_type( {0x000000b2} ( int ) )
                 AT_external( true )

0x000000a3:         TAG_variable [5]
                     AT_location( fbreg -8 )
                     AT_name( "r" )
                     AT_decl_file( "foo.c" )
                     AT_decl_line( 9 )
                     AT_type( {0x000000b2} ( int ) )

0x000000b1:         NULL

0x000000b2:     TAG_base_type [6]
                 AT_name( "int" )
                 AT_encoding( DW_ATE_signed )
                 AT_byte_size( 0x04 )

0x000000b9:     NULL

DWARF 核心: info section

info section 是DWARF的核心,其用来描述程序结构

如果让你去描述上文我们使用的 foo.c 你会怎么表达呢 ?

是不是感觉整个描述很繁琐并且有很多种类型的东西需要描述?
包括函数、形式参数、局部变量、全局变量甚至还有每个函数位于代码的什么位置。

为此 DWARF 提出了 The Debugging Information Entry (DIE) 来以统一的形式描述这些信息,详见: 「DWARF4 2.1」
每个DIE包含:

值得注意的是:

组合使用 DIE 就可以描述整个程序,就像搭乐高积木一样(可能需要更多的技巧)

事实上, 类似于编译器语法树,DWARF 使用 Tree 作为组合 DIE 的方式

一个 DIE 可以包含几个 子DIE, 正如一个文件可以有 N 个函数, 一个函数可以包含 X 个形式参数和 Y 个局部变量
DWARF info 示例 也可以看出, DIE 间用空行分割,并且为缩进分明的树形结构。

DWARF4 标准阅读指引

如果你觉得 DWARF 只是用来描述下你的程序有哪些函数和变量,那就 Too Young, Too Naive ~

DWARF4 标准从这里可以获取 DWARF4.pdf, 建议下载到本地慢慢读
DWARF5 也已经开始得到编译器支持, 详情见 DWART Stand

DWARF4 标准全文 300页左右, 其中正文到 189页,剩余为附录

重要的内容前面有 【重要】标签提示

1. INTRODUCTION...........................................1
   介绍 DWARF 目标适用范围,版本变更细节

2. GENERAL DESCRIPTION ...................................7
   DWARF 基础概念的描述
    
    2.1 THE DEBUGGING INFORMATION ENTRY (DIE) ............7
        【重要】描述了 DIE 的概念
    
    2.2 ATTRIBUTE TYPES ..................................7
        【重要】描述了 Atrribute 的概念, 列出了所有的 Attibute, 以及 Attribute 的值可以拥有哪些数据类型 
        「DWARF4 Figure 3. Classes of attribute value」
    
    2.3 RELATIONSHIP OF DEBUGGING INFORMATION ENTRIES ...16
        DIE 间的关系, 为树形结构
        
    2.4 TARGET ADDRESSES.................................16
        关于目标机器地址为32或者是64位的一些描述
        
    2.5 DWARF EXPRESSIONS ...............................17
        【重要】DWARF 定义出了一种专用的表达式(Expressions), 由操作码和操作数序列组成一个表达式, 最终可计算出想要表达的值。
         主要为了下一节 LOCATION DESCRIPTIONS 服务
        
    2.6 LOCATION DESCRIPTIONS............................25
        【重要】描述如何计算函数 `stack frame` | `return address` 的值
        利用DWARF Expression 来描述 Location, 甚至可以满足下面的情况: 生命周期中,对象的地址甚至是可以改变的
        包括: Single location descriptions 和 Location lists 两种
        
    2.7 TYPES OF PROGRAM ENTITIES .......................32
        DW_AT_type 属性的描述
    
    2.8 ACCESSIBILITY OF DECLARATIONS....................32
        DW_AT_accessibility 属性的描述
        
    2.9 VISIBILITY OF DECLARATIONS.......................33
        DW_AT_visibility 属性的描述
        
    2.10 VIRTUALITY OF DECLARATIONS .....................33
        DW_AT_virtuality 属性的描述(目前看来仅适用于 C++虚函数)
        
    2.11 ARTIFICIAL ENTRIES..............................34
        DW_AT_artificial 属性的描述, 为一个 flag, 当一个对象|类型是由编译器而不是源代码生成时, 这个flag为 True
        
    2.12 SEGMENTED ADDRESSES ............................34
         【重要】DW_AT_segment, 一些系统的内存地址可能不是一个平整的地址空间,而是 Segment基址加上一个Offset
         当 DW_AT_segment 出现时, 任何计算结果为地址的属性,例如 DW_AT_low_pc, DW_AT_high_pc, DW_AT_ranges or DW_AT_entry_pc 
         计算出来的值都需要加上 DW_AT_segment 计算所得到的值
        
    2.13 NON-DEFINING DECLARATIONS AND COMPLETIONS ......35
         DWARF 中大多是DIE都是用来描述一个对象的定义(Define),但是C语言允许使用 extern 关键字描述这个变量在其他地方定义,
         这里仅仅声明(NON-DEFINING DECLARATIONS)一下,这时候 DW_AT_declaration 属性作为 flag 说明这是否为一个非定义声明
    
    2.14 DECLARATION COORDINATES ........................36
         【重要】描述对象在源码中的声明位置,包括属性: DW_AT_decl_file, DW_AT_decl_line, DW_AT_decl_column
    
    2.15 IDENTIFIER NAMES ...............................36
         【重要】DW_AT_name 的描述, 关于变量的名字, 函数的名字之类的
         
    2.16 DATA LOCATIONS AND DWARF PROCEDURES.............37
         【重要】DW_AT_location 属性的描述, 其值为一个 DWARF 表达式,通常用来描述变量或者形式参数的Location
         
    2.17 CODE ADDRESSES AND RANGES ......................37
         【重要】很多实体(entry), 如函数, 编译单元,代码块,Try/Catch 等都可以映射到机器码地址(地址范围), 
         DW_AT_low_pc, DW_AT_high_pc, DW_AT_ranges, DW_AT_start_scope 属性用来描述这些信息
         
    2.18 ENTRY ADDRESS ..................................40
         对于有一个地址范围的实体,DW_AT_entry_pc 属性描述执行入口地址在哪里, 如果没有声明, 那么 DW_AT_low_pc 便是入口地址
    
    2.19 STATIC AND DYNAMIC VALUES OF ATTRIBUTES ........40
         描述了一些属性的值可能是静态,也可能是动态的情况
         
    2.20 ENTITY DESCRIPTIONS.............................41
         DW_AT_description 属性的描述, 用来对实体的注解性描述
         
    2.21 BYTE AND BIT SIZES..............................41
         【重要】用来描述实体所需的存储大小, DW_AT_byte_size|DW_AT_bit_size 分别以字节和比特作为单位, 
         DW_AT_byte_stride|DW_AT_bit_stride 用于描述Array一个元素占用存储大小
    
    2.22 LINKAGE NAMES ..................................41
         【重要】因为一些语言,例如C++,允许多个实体使用相同的名字(如参数个数不同的话,函数名字可以一样),但在链接(Link)期间,必须保证没有相同的名字,
         所以这些语言的编译器会使用一种成为符号重整(mangled names)的方式来对名字做一些格式化来满足不重名的要求。
         DW_AT_linkage_name 用来记录重整过后的名字
         
3. PROGRAM SCOPE ENTRIES.................................43
   程序不同层级实体的描述(编译、模块、函数),但是不包括类型。简单来说,就是存在于二进制程序中 TEXT 段的那些实体
   
   3.1 UNIT ENTRIES......................................43
       【重要】Unit Entries 是处在食物链顶端的 DIE (/偷笑),一个可执行文件通常包含一个或多个编译单元(compilation unit),后面会细说
   
   3.2 MODULE, NAMESPACE AND IMPORTING ENTRIES ..........48
       DW_TAG_module 为编程语言 module 实体的TAG(如Python), DW_TAG_namespace 是命名空间的TAG(C++)
       DW_TAG_imported_declaration | DW_TAG_imported_module 说明此实体被其他地方引用
       
   3.3 SUBROUTINE AND ENTRY POINT ENTRIES ...............53
       【重要】描述了大多数语言都会支持的子程序/函数(Subroutine)实体,不同语言差异性比较大... 这部分描述用了整整 11 页
   
   3.4 LEXICAL BLOCK ENTRIES.............................65
       语句块(lexical block) 实体相关描述, 语句块可能会有关联的地址范围属性,如 DW_AT_low_pc、DW_AT_high_pc 或者是 DW_AT_ranges
   
   3.5 LABEL ENTRIES ....................................65
       LABEL 语句相关描述,TAG 为 DW_TAG_label, label 一般和 goto 配合使用。
       
   3.6 WITH STATEMENT ENTRIES............................66
       WITH 语句相关描述,TAG 为 DW_TAG_with_stmt,Pascal / Modula-2 支持 with 语句, 但是这个 with 和 Python 中的 with 是完全不一样的哦
       
   3.7 TRY AND CATCH BLOCK ENTRIES ......................66
       try ... catch ... 语句相关描述, TAG 为 DW_TAG_try_block

4. DATA OBJECT AND OBJECT LIST ENTRIES ..................69
   程序不可分割的数据实体描述,例如变量、形式参数、常量
   
   4.1 DATA OBJECT ENTRIES...............................69
       【重要】对 DW_TAG_variable、DW_TAG_formal_parameter、DW_TAG_constant 相关属性的描述
   
   4.2 COMMON BLOCK ENTRIES..............................73
       看起来是 Fortran 的独有特性, fortran 允许一些变量不经形式参数传递到另一个编译单元的函数中, 
       使用 COMMON 语句标识出这些变量即可,类似于 C 语言定义在函数外边的全局变量
   
   4.3 NAMELIST ENTRIES .................................73
       看起来是 Fortran 的独有特性, 类似于 C 语言 struct,组合多个变量方便使用
       
5. TYPE ENTRIES .........................................75
   描述了程序中经常用到的数据类型
   
   5.1 BASE TYPE ENTRIES ................................75
       【重要】编程语言定义的基础数据类型, TAG 为 DW_TAG_base_type,描述了可用于描述 Type 的各个属性
       
   5.2 UNSPECIFIED TYPE ENTRIES..........................80
       也没看懂到底啥意思,举的例子关于如何描述C语言的变量修饰符。
       
   5.3 TYPEDEF ENTRIES ..................................82
       C/C++ 中的 typedef 语句, TAG 为 DW_TAG_typedef
       
   5.4 ARRAY TYPE ENTRIES ...............................83
   ...
   5.15 TEMPLATE ALIAS ENTRIES ..........................103
       各种数据类型的描述,如 ARRAY、STRUCT、CLASS、INTERFACE、SUBROUTINE ...
       
6. OTHER DEBUGGING INFORMATION...........................105
   未表示为DIE的调试信息, 即不存储于  `info` 和 `types` section
   
   6.1 ACCELERATED ACCESS ...............................105
       【重要】快捷查找符号/Address的能力,包括:
       - Lookup by Name: 快速查找全局对象/变量对应的编译单元, 存储于 `pubnames` & `pubtypes` section
       - Lookup by Address: 快速定位地址对应的编译单元, 存储于 `aranges` section
   
   6.2 LINE NUMBER INFORMATION ..........................108
       【重要】这可谓是相当重要啊,源码级调试就靠它了,存储在 `info` section
       DWARF为了节省需要的存储,使用了特殊定义的状态机(Line Number Program)
       来生成LINE TABLE,完成 Address 到文件名、行号的映射
       
   6.3 MACRO INFORMATION.................................123
       C/C++ 宏定义相关的描述
       
   6.4 CALL FRAME INFORMATION ...........................126
       【重要】调用栈相关信息,存储在 `frame` section,典型应用场景为 异常发生时堆栈回溯(stack unwinding)
       
7. DATA REPRESENTATION ..................................139
   这一节描述如何将调试信息序列化为二进制文件
   
APPENDIX A -- ATTRIBUTES BY TAG VALUE (INFORMATIVE)......191
    【重要】描述了不同 DIE 一般会拥有的属性
    
APPENDIX B -- DEBUG SECTION RELATIONSHIPS (INFORMATIVE)..213
    【重要】描述了不同DWARF section 之间的关系
    
APPENDIX C -- VARIABLE LENGTH DATA: ENCODING/DECODING (INFORMATIVE)...217
    LEB128 编码算法

APPENDIX D -- EXAMPLES (INFORMATIVE) ....................219
    各种示例
    
    重要的有下面几个
    
    D.1 COMPILATION UNITS AND ABBREVIATIONS TABLE EXAMPLE ...219
    D.5 LINE NUMBER PROGRAM EXAMPLE .........................237
    D.6 CALL FRAME INFORMATION EXAMPLE ......................239
    
APPENDIX E -- DWARF COMPRESSION AND DUPLICATE ELIMINATION (INFORMATIVE)..63
    【重要】DWARF 压缩及减少冗余的措施

DWARF Sections

DWARF 各个 section 可以有独立的版本号, 下面是各个 Section 及其在不同 DWARF 版本中对应的版本号


image.png

Section 间的关系如下:


image.png

info & types 是毫无疑问的宇宙中心,描述了程序结构已经类型声明。
为了支持快速 Name/Type/Address 的查找,pubnames & pubtypes & aranges 可快速定位到编译单元
为了减少 info 段所需存储,abbrev 统一描述了不同 DIE 所包含的属性,info 仅需要引用 abbrev 中的 id 就好
为了减少 info 段所需存储,str 用于复用字符串,避免字符串重复,DW_FORM_strp
loc 存储了 Location 表达式,DW_AT_location
ranges 存储了 地址范围表达式,DW_AT_ranges
macinfo 存储了 宏定义相关的信息,DW_AT_macinfo
line 存储了行号相关信息, DW_AT_stmt_list 属性指向编译单元对应的 Line Table

开发 DWARF 相关工具

如果你不是编译器/调试器开发者,了解 DWARF 格式的重要目的一般是为了开发一些工具,完成程序运行时到源码之间的映射。例如:

另外, 有一些领域可以预见到DWARF信息会提供一些帮助,例如: 静态代码扫描(static code analysis), 二进制代码性能分析(performance analysis of binary)

下面给出了一些开发相关的库:

作为一个Golang 使用 debug/dwarf 的例子, ParseFile 打印了 address 对应的文件名及行号

package dwarfexample

import (
    "debug/macho"
    "debug/dwarf"
    "log"
    "github.com/go-errors/errors"
)

func ParseFile(path string, address int64) (err error) {
    var f *macho.FatFile
    if f, err = macho.OpenFat(path); err != nil {
        return errors.New("open file error: " + err.Error())
    }

    var d *dwarf.Data
    if d, err = f.Arches[1].DWARF(); err != nil {
        return
    }

    r := d.Reader()

    var entry *dwarf.Entry
    if entry, err = r.SeekPC(address); err != nil {
        log.Print("Not Found ...")
        return
    } else {
        log.Print("Found ...")
    }

    log.Printf("tag: %+v, lowpc: %+v", entry.Tag, entry.Val(dwarf.AttrLowpc))

    var lineReader *dwarf.LineReader
    if lineReader, err = d.LineReader(entry); err != nil {
        return
    }

    var line dwarf.LineEntry

    if err = lineReader.SeekPC(0x1005AC550, &line); err != nil {
        return
    }

    log.Printf("line %+v:%+v", line.File.Name, line.Line)

    return
}

DWARF 资源

下面是DWARF相关的资源链接:

上一篇 下一篇

猜你喜欢

热点阅读