ELF

ELF

ELF(Executable and Linkable Format)是一种对象文件的格式,用于定义不同类型的对象文件(Object files)中都放了什么东西、以及都以什么样的格式去放这些东西。

简介

分类

  • 可重定位文件(Relocatable File) .o)包含适合于与其他目标文件链接来创建可执行文件或者共享目标文件的代码和数据。

  • 可执行文件(Executable File) .exe) 包含适合于执行的一个程序,此文件规定了exec() 如何创建一个程序的进程映像。

  • 共享目标文件(Shared Object File) .so) 包含可在两种上下文中链接的代码和数据。

    • 首先链接编辑器可以将它和其它可重定位文件和共享目标文件一起处理, 生成另外一个目标文件。
    • 其次动态链接器(Dynamic Linker)可能将它与某 个可执行文件以及其它共享目标一起组合,创建进程映像。

作用

ELF文件参与程序的连接(建立一个程序)和程序的执行(运行一个程序),所以可以从不同的角度来看待elf格式的文件:

  • 如果用于编译和链接(可重定位文件),则编译器和链接器将把elf文件看作是节头表描述的节的集合,程序头表可选。

  • 如果用于加载执行(可执行文件),则加载器则将把elf文件看作是程序头表描述的段的集合,一个段可能包含多个节,节头表可选。

  • 如果是共享文件,则两者都含有。

格式分析

ELF Header

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400750
  Start of program headers:          64 (bytes into file)
  Start of section headers:          65016 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         37
  Section header string table index: 34

文件的最开始几个字节给出如何解释文件的提示信息。这些信息独立于处理器,也独立于文件中的其余内容。ELF Header 部分可以用以下的数据结构表示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
typedef struct elfhdr {
    unsigned char    e_ident[EI_NIDENT]; /* ELF Identification */
    Elf32_Half    e_type;        /* object file type */
    Elf32_Half    e_machine;    /* machine */
    Elf32_Word    e_version;    /* object file version */
    Elf32_Addr    e_entry;      /* virtual entry point */
    Elf32_Off     e_phoff;      /* program header table offset */
    Elf32_Off     e_shoff;      /* section header table offset */
    Elf32_Word    e_flags;      /* processor-specific flags */
    Elf32_Half    e_ehsize;     /* ELF header size */
    Elf32_Half    e_phentsize;    /* program header entry size */
    Elf32_Half    e_phnum;      /* number of program header entries */
    Elf32_Half    e_shentsize;    /* section header entry size */
    Elf32_Half    e_shnum;      /* number of section header entries */
    Elf32_Half    e_shstrndx;    /* section header table's "section 
                       header string table" entry offset */
} Elf32_Ehdr;

typedef struct {
    unsigned char    e_ident[EI_NIDENT];    /* Id bytes */
    Elf64_Quarter    e_type;            /* file type */
    Elf64_Quarter    e_machine;        /* machine type */
    Elf64_Half       e_version;        /* version number */
    Elf64_Addr       e_entry;         /* entry point */
    Elf64_Off        e_phoff;         /* Program hdr offset */
    Elf64_Off        e_shoff;         /* Section hdr offset */
    Elf64_Half       e_flags;         /* Processor flags */
    Elf64_Quarter    e_ehsize;        /* sizeof ehdr */
    Elf64_Quarter    e_phentsize;     /* Program header entry size */
    Elf64_Quarter    e_phnum;         /* Number of program headers */
    Elf64_Quarter    e_shentsize;     /* Section header entry size */
    Elf64_Quarter    e_shnum;         /* Number of section headers */
    Elf64_Quarter    e_shstrndx;      /* String table index */
} Elf64_Ehdr;

  • e_shoff 成员给出从文件头到节区头部表格的偏移字节数
  • e_shnum 给节区头部表格条目数目
  • e_shentsize 节区头部表格每个项目的字节数

Program Header

通过ELF Header中的e_phoff e_phentsize e_phnum可以找到Program Header

可执行文件或者共享目标文件的程序头部是一个结构数组,每个结构描述了一个段 或者系统准备程序执行所必需的其它信息。目标文件的“段”包含一个或者多个“节区”, 也就是“段内容(Segment Contents)”。程序头部仅对于可执行文件和共享目标文件 有意义。 可执行目标文件在 ELF 头部的 e_phentsize和e_phnum 成员中给出其自身程序头部 的大小。程序头部的数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* Program Header */
typedef struct {
    Elf32_Word    p_type;        /* segment type */
    Elf32_Off    p_offset;    /* segment offset */
    Elf32_Addr    p_vaddr;    /* virtual address of segment */
    Elf32_Addr    p_paddr;    /* physical address - ignored? */
    Elf32_Word    p_filesz;    /* number of bytes in file for seg. */
    Elf32_Word    p_memsz;    /* number of bytes in mem. for seg. */
    Elf32_Word    p_flags;    /* flags */
    Elf32_Word    p_align;    /* memory alignment */
} Elf32_Phdr;

typedef struct {
    Elf64_Half    p_type;        /* entry type */
    Elf64_Half    p_flags;    /* flags */
    Elf64_Off    p_offset;    /* offset */
    Elf64_Addr    p_vaddr;    /* virtual address */
    Elf64_Addr    p_paddr;    /* physical address */
    Elf64_Xword    p_filesz;    /* file size */
    Elf64_Xword    p_memsz;    /* memory size */
    Elf64_Xword    p_align;    /* memory & file alignment */
} Elf64_Phdr;

其中各个字段说明:

  • p_type 此数组元素描述的段的类型,或者如何解释此数组元素的信息。具体如下图。
  • p_offset 此成员给出从文件头到该段第一个字节的偏移。
  • p_vaddr 此成员给出段的第一个字节将被放到内存中的虚拟地址。
  • p_paddr 此成员仅用于与物理地址相关的系统中。因为 System V 忽略所有应用程序的物理地址信息,此字段对与可执行文件和共享目标文件而言具体内容是指定的。
  • p_filesz 此成员给出段在文件映像中所占的字节数。可以为 0。
  • p_memsz 此成员给出段在内存映像中占用的字节数。可以为 0。
  • p_flags 此成员给出与段相关的标志。
  • p_align 可加载的进程段的 p_vaddr 和 p_offset 取值必须合适,相对于对页面大小的取模而言。此成员给出段在文件中和内存中如何 对齐。数值 0 -和 1 表示不需要对齐。否则 p_align 应该是个正整数,并且是 2 的幂次数,p_vaddr 和 p_offset 对 p_align 取模后应该相等。

Section Header

通过ELF Header中的e_shoff e_shentsize e_shnum可以找到Section Header

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* Section Header */
typedef struct {
    Elf32_Word    sh_name;    /* name - index into section header
                       string table section */
    Elf32_Word    sh_type;    /* type */
    Elf32_Word    sh_flags;    /* flags */
    Elf32_Addr    sh_addr;    /* address */
    Elf32_Off     sh_offset;    /* file offset */
    Elf32_Word    sh_size;    /* section size */
    Elf32_Word    sh_link;    /* section header table index link */
    Elf32_Word    sh_info;    /* extra information */
    Elf32_Word    sh_addralign;    /* address alignment */
    Elf32_Word    sh_entsize;    /* section entry size */
} Elf32_Shdr;

typedef struct {
    Elf64_Half    sh_name;    /* section name */
    Elf64_Half    sh_type;    /* section type */
    Elf64_Xword   sh_flags;    /* section flags */
    Elf64_Addr    sh_addr;    /* virtual address */
    Elf64_Off     sh_offset;    /* file offset */
    Elf64_Xword   sh_size;    /* section size */
    Elf64_Half    sh_link;    /* link to another */
    Elf64_Half    sh_info;    /* misc info */
    Elf64_Xword   sh_addralign;    /* memory alignment */
    Elf64_Xword   sh_entsize;    /* table entry size */
} Elf64_Shdr;
  • sh_name 给出节区名称。是节区头部字符串表节区( Section Header StringTable Section)的索引。名字是一个 NULL 结尾的字符串。
  • sh_type 为节区的内容和语义进行分类。
  • sh_flags 节区支持 1 位形式的标志,这些标志描述了多种属性。此字段定义了一个节区中包含的内容是否是可以修改、是否可以执行等信息。如果一个标志位被设置,则该值取值为1.未定义的各位都设置为0.
  • sh_addr 如果节区将出现在进程的内存映像中, 此成员给出节区的第一个字节应处的位置。否则,此字段为 0。
  • sh_offset 此成员的取值给出节区的第一个字节与文件头之间的偏移。不过,SHT_NOBITS 类型的节区不占用文件的空间, 因此其 sh_offset 成员给出的是其概念性的偏移。
  • sh_size 此成员给出节区的长度(字节数)。除非节区的型是SHT_NOBITS,否则节区占用文件中的 sh_size 字节。类型为SHT_NOBITS 的节区长度可能非零, 不过却不占用文件中的空间。
  • sh_link 此成员给出节区头部表索引链接。其具体的解释依赖于节区类型。
  • sh_info 此成员给出附加信息,其解释依赖于节区类型。
  • sh_addralign 某些节区带有地址对齐约束。例如,如果一个节区保存一个doubleword, 那么系统必须保证整个节区能够按双字对齐。 sh_addr对 sh_addralign 取模,结果必须为 0。目前仅允许取值为 0 和 2的幂次数。数值 0 和 1 表示节区没有对齐约束。
  • sh_entsize 某些节区中包含固定大小的项目, 如符号表。 对于这类节区, 此成员给出每个表项的长度字节数。 如果节区中并不包含固定长度表项的表格,此成员取值为 0。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400238  00000238
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             0000000000400254  00000254
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             0000000000400274  00000274
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400298  00000298
       0000000000000030  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000004002c8  000002c8
       0000000000000138  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           0000000000400400  00000400
       0000000000000168  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           0000000000400568  00000568
       000000000000001a  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000400588  00000588
       0000000000000040  0000000000000000   A       6     2     8
  [ 9] .rela.dyn         RELA             00000000004005c8  000005c8
       0000000000000030  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             00000000004005f8  000005f8
       00000000000000a8  0000000000000018  AI       5    24     8
  [11] .init             PROGBITS         00000000004006a0  000006a0
       000000000000001a  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         00000000004006c0  000006c0
       0000000000000080  0000000000000010  AX       0     0     16
  [13] .plt.got          PROGBITS         0000000000400740  00000740
       0000000000000008  0000000000000000  AX       0     0     8
  [14] .text             PROGBITS         0000000000400750  00000750
       00000000000001e2  0000000000000000  AX       0     0     16
  [15] .fini             PROGBITS         0000000000400934  00000934
       0000000000000009  0000000000000000  AX       0     0     4
  [16] .rodata           PROGBITS         0000000000400940  00000940
       0000000000000016  0000000000000000   A       0     0     4
  [17] .eh_frame_hdr     PROGBITS         0000000000400958  00000958
       0000000000000044  0000000000000000   A       0     0     4
  [18] .eh_frame         PROGBITS         00000000004009a0  000009a0
       0000000000000134  0000000000000000   A       0     0     8
  [19] .init_array       INIT_ARRAY       0000000000600df8  00000df8
       0000000000000010  0000000000000000  WA       0     0     8
  [20] .fini_array       FINI_ARRAY       0000000000600e08  00000e08
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .jcr              PROGBITS         0000000000600e10  00000e10
       0000000000000008  0000000000000000  WA       0     0     8
  [22] .dynamic          DYNAMIC          0000000000600e18  00000e18
       00000000000001e0  0000000000000010  WA       6     0     8
  [23] .got              PROGBITS         0000000000600ff8  00000ff8
       0000000000000008  0000000000000008  WA       0     0     8
  [24] .got.plt          PROGBITS         0000000000601000  00001000
       0000000000000050  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         0000000000601050  00001050
       0000000000000010  0000000000000000  WA       0     0     8
  [26] .bss              NOBITS           0000000000601060  00001060
       0000000000000118  0000000000000000  WA       0     0     32
  [27] .comment          PROGBITS         0000000000000000  00001060
       0000000000000034  0000000000000001  MS       0     0     1
  [28] .debug_aranges    PROGBITS         0000000000000000  00001094
       0000000000000030  0000000000000000           0     0     1
  [29] .debug_info       PROGBITS         0000000000000000  000010c4
       00000000000015ca  0000000000000000           0     0     1
  [30] .debug_abbrev     PROGBITS         0000000000000000  0000268e
       00000000000003bf  0000000000000000           0     0     1
  [31] .debug_line       PROGBITS         0000000000000000  00002a4d
       000000000000075e  0000000000000000           0     0     1
  [32] .debug_str        PROGBITS         0000000000000000  000031ab
       0000000000009aad  0000000000000001  MS       0     0     1
  [33] .debug_macro      PROGBITS         0000000000000000  0000cc58
       0000000000002512  0000000000000000           0     0     1
  [34] .shstrtab         STRTAB           0000000000000000  0000fc98
       0000000000000159  0000000000000000           0     0     1
  [35] .symtab           SYMTAB           0000000000000000  0000f170
       00000000000007b0  0000000000000018          36    56     8
  [36] .strtab           STRTAB           0000000000000000  0000f920
       0000000000000378  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)
名称 类型 属性 含义
.bss SHT_NOBITS SHF_ALLOC +SHF_WRITE 包含将出现在程序的内存映像中的为初始化数据。根据定义,当程序开始执行,系统将把这些数据初始化为 0。此节区不占用文件空间
.comment SHT_PROGBITS (无) 包含版本控制信息
.data SHT_PROGBITS SHF_ALLOC + SHF_WRITE 这些节区包含初始化了的数据,将出现在程序的内存映像中
.data1 SHT_PROGBITS SHF_ALLOC + SHF_WRITE 这些节区包含初始化了的数据,将出现在程序的内存映像中
.debug SHT_PROGBITS (无) 此节区包含用于符号调试的信息
.dynamic SHT_DYNAMIC   此节区包含动态链接信息。节区的属性将包含 SHF_ALLOC 位。是否 SHF_WRITE 位被设置取决于处理器
.dynstr SHT_STRTAB SHF_ALLOC 此节区包含用于动态链接的字符串,大多数情况下这些字符串代表了与符号表项相关的名称
.dynsym SHT_DYNSYM SHF_ALLOC 此节区包含了动态链接符号表
.fini SHT_PROGBITS SHF_ALLOC + SHF_EXECINSTR 此节区包含了可执行的指令,是进程终止代码的一部分。程序正常退出时,系统将安排执行这里的代码
.got SHT_PROGBITS   此节区包含全局偏移表
.hash SHT_HASH SHF_ALLOC 此节区包含了一个符号哈希表
.init SHT_PROGBITS SHF_ALLOC +SHF_EXECINSTR 此节区包含了可执行指令,是进程初始化代码的一部分。当程序开始执行时,系统要在开始调用主程序入口之前(通常指 C 语言的 main 函数)执行这些代码
.interp SHT_PROGBITS   此节区包含程序解释器的路径名。如果程序包含一个可加载的段,段中包含此节区,那么节区的属性将包含 SHF_ALLOC 位,否则该位为 0
.line SHT_PROGBITS (无) 此节区包含符号调试的行号信息,其中描述了源程序与机器指令之间的对应关系。其内容是未定义的
.note SHT_NOTE (无) 此节区中包含注释信息,有独立的格式。
.plt SHT_PROGBITS   此节区包含过程链接表(procedure linkage table)
.relname
.relaname
SHT_REL
SHT_RELA
  这些节区中包含了重定位信息。如果文件中包含可加载的段,段中有重定位内容,节区的属性将包含 SHF_ALLOC 位,否则该位置 0。传统上 name 根据重定位所适用的节区给定。例如 .text 节区的重定位节区名字将是:.rel.text 或者 .rela.text
.rodata
.rodata1
SHT_PROGBITS SHF_ALLOC 这些节区包含只读数据,这些数据通常参与进程映像的不可写段
.shstrtab SHT_STRTAB   此节区包含节区名称
.strtab SHT_STRTAB   此节区包含字符串,通常是代表与符号表项相关的名称。如果文件拥有一个可加载的段,段中包含符号串表,节区的属性将包含SHF_ALLOC 位,否则该位为 0
.symtab SHT_SYMTAB   此节区包含一个符号表。如果文件中包含一个可加载的段,并且该段中包含符号表,那么节区的属性中包含SHF_ALLOC 位,否则该位置为 0
.text SHT_PROGBITS SHF_ALLOC + SHF_EXECINSTR 此节区包含程序的可执行指令

Session

节区中包含目标文件中的所有信息,除了:ELF 头部、程序头部表格、节区头部表格。节区满足以下条件:

  • 目标文件中的每个节区都有对应的节区头部描述它,反过来,有节区头部不意 味着有节区。
  • 每个节区占用文件中一个连续字节区域(这个区域可能长度为 0)。
  • 文件中的节区不能重叠,不允许一个字节存在于两个节区中的情况发生。
  • 目标文件中可能包含非活动空间(INACTIVE SPACE)。这些区域不属于任何头部和节区,其内容指定。

.symtab

目标文件的符号表中包含用来定位、重定位程序中符号定义和引用的信息。符号表 索引是对此数组的索引。索引 0 表示表中的第一表项,同时也作为 定义符号的索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* Symbol Table Entry */
typedef struct elf32_sym {
    Elf32_Word    st_name;    /* name - index into string table */
    Elf32_Addr    st_value;    /* symbol value */
    Elf32_Word    st_size;    /* symbol size */
    unsigned char    st_info;    /* type and binding */
    unsigned char    st_other;    /* 0 - no defined meaning */
    Elf32_Half    st_shndx;    /* section header index */
} Elf32_Sym;

typedef struct {
    Elf64_Half    st_name;    /* Symbol name index in str table */
    Elf_Byte    st_info;    /* type / binding attrs */
    Elf_Byte    st_other;    /* unused */
    Elf64_Quarter    st_shndx;    /* section index of symbol */
    Elf64_Xword    st_value;    /* value of symbol */
    Elf64_Xword    st_size;    /* size of symbol */
} Elf64_Sym;

.strtab

字符串表节区包含以 NULL(ASCII 码 0)结尾的字符序列,通常称为字符串。ELF 目标文件通常使用字符串来表示符号和节区名称。对字符串的引用通常以字符串在字符 串表中的下标给出。

一般,第一个字节(索引为 0)定义为一个空字符串。类似的,字符串表的最后一 个字节也定义为 NULL,以确保所有的字符串都以 NULL 结尾。索引为 0 的字符串在 不同的上下文中可以表示无名或者名字为 NULL 的字符串。

允许存在空的字符串表节区,其节区头部的 sh_size 成员应该为 0。对空的字符串 表而言,非 0 的索引值是非法的。

在使用、分析字符串表时,要注意以下几点:

  • 字符串表索引可以引用节区中任意字节。
  • 字符串可以出现多次
  • 可以存在对子字符串的引用
  • 同一个字符串可以被引用多次。
  • 字符串表中也可以存在未引用的字符串。

.strtab vs .dynsym

静态链接中有一个专门的段叫符号表 – “.symtab”(Symbol Table), 里面保存了所有关于该目标文件的符号的定义和引用

动态链接中同样有一个段叫 动态符号表 – “.dynsym”(Dynamic Symbol) , 但.dynsym 相对于 .symtab 只保存了与动态链接相关的导入导出符号。

so中同样有.symtab,其中保存着所有的符号

.symtab 和 .dynsym 都有相对应的辅助表

比如 :

.symtab -> .strtab(String Table)符号字符串表

.dynsym -> .dynstr(Dynamic String Table)动态符号字符串表

前面提到.symtab和.dynsym两个不同的symbol table, 它们有什么区别? .dynsym是.symtab的一个子集, 大家都有疑问, 为什么要两个信息重合的结构? 需要先了解allocable/non-allocable ELF section, ELF文件包含一些sections(如code和data)是在运行时需要的, 这些sections被称为allocable; 而其他一些sections仅仅是linker,debugger等工具需要, 在运行时并不需要, 这些sections被称为non-allocable的. 当linker构建ELF文件时, 它把allocable的数据放到一个地方, 将non-allocable的数据放到其他地方. 当OS加载ELF文件时, 仅仅allocable的数据被映射到内存, non-allocable的数据仍静静地呆在文件里不被处理. strip就是用来移除某些non-allocable sections的. .symtab包含大量linker,debugger需要的数据, 但并不为runtime必需, 它是non-allocable的; .dynsym包含.symtab的一个子集, 比如共享库所需要在runtime加载的函数对应的symbols, 它世allocable的.

因此, 得到答案:

  1. strip移除的应是.symtab.
  2. nm读取的应是.symtab: 上面发现的libattr等nm结果为空, libpthread nm结果非空应是正常的. 3. 共享库包含的.dynsym是runtime必需的, 是allocable的.