ELF文件结构

ELF全称为Executable and Linkable Format,是 Linux 中的目标文件。

其内容视图主要有两种

以链接视图来分析ELF的文件格式

  • ELF Header -> 存放ELF文件的整体信息
  • Program Header Table -> 存放程序执行时所需要的信息。其实就是段信息
  • Section -> 存放程序大部分信息:指令、数据、符号表等等
  • Section Header Table -> 存放描述节区的信息

ELF Header

用于索引ELF全部的信息,其数据结构如下(数据结构可以在/usr/include/elf.h查看)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* The ELF file header.  This appears at the start of every ELF file.  */

typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;

只了解其中部分含义

e_ident

其下标含义如下

宏名称 下标 目的
EI_MAG0 0 文件标识
EI_MAG0 1 文件标识
EI_MAG0 2 文件标识
EI_MAG0 3 文件标识
EI_CLASS 4 文件类
EI_DATA 5 数据编码
EI_VERSION 6 文件版本
EI_PAD 7 补齐字节开始处

下标0-3称为魔数标识ELF文件
下标4EI_CLASS定义ELF类型,有以下取值

名称 意义
ELFCLASSNONE 0 无效类型
ELFCLASS32 1 32 位文件
ELFCLASS64 2 64 位文件

下标5 EI_DATA 定义大小端模式,有以下取值

名称 意义
ELFDATANONE 0 无效数据编码
ELFDATA2LSB 1 小端
ELFDATA2MSB 2 大端

下标6 EI_DATA 定义ELF头的版本号,与e_version相同
下标7 EI_PAD 给出了e_ident中未使用字节的开始地址。这些字节被保留并置为 0;处理目标文件的程序应该忽略它们。如果之后这些字节被使用,EI_PAD 的值就会改变。

e_phoff

给出程序头部表在文件中的字节偏移(Program Header table OFFset)。如果文件中没有程序头部表,则为 0。

e_shoff

给出节头表在文件中的字节偏移( Section Header table OFFset )。如果文件中没有节头表,则为 0。

Program Header Table

其类型为Elf32_Phdr结构体数组,ELF Header中的 e_phentsize 和 e_phnum 指定了该数组每个元素的大小以及元素个数。数据结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
/* Program segment header.  */
typedef struct
{
Elf32_Word p_type; /* Segment type */
Elf32_Off p_offset; /* Segment file offset */
Elf32_Addr p_vaddr; /* Segment virtual address */
Elf32_Addr p_paddr; /* Segment physical address */
Elf32_Word p_filesz; /* Segment size in file */
Elf32_Word p_memsz; /* Segment size in memory */
Elf32_Word p_flags; /* Segment flags */
Elf32_Word p_align; /* Segment alignment */
} Elf32_Phdr;

前面说过其实这个表就是存放段信息的,暂且先了解p_type,p_offset字段

p_type

描述段类型,具体取值如下

名字 取值 说明
PT_NULL 0 表明段未使用,其结构中其他成员都是未定义的。
PT_LOAD 1 此类型段为一个可加载的段,大小由 p_filesz 和 p_memsz 描述。文件中的字节被映射到相应内存段开始处。如果 p_memsz 大于 p_filesz,“剩余” 的字节都要被置为 0。p_filesz 不能大于 p_memsz。可加载的段在程序头部中按照 p_vaddr 的升序排列。
PT_DYNAMIC 2 此类型段给出动态链接信息。
PT_INTERP 3 此类型段给出了一个以 NULL 结尾的字符串的位置和长度,该字符串将被当作解释器调用。这种段类型仅对可执行文件有意义(也可能出现在共享目标文件中)。此外,这种段在一个文件中最多出现一次。而且这种类型的段存在的话,它必须在所有可加载段项的前面。
PT_NOTE 4 此类型段给出附加信息的位置和大小。
PT_SHLIB 5 该段类型被保留,不过语义未指定。而且,包含这种类型的段的程序不符合 ABI 标准。
PT_PHDR 6 该段类型的数组元素如果存在的话,则给出了程序头部表自身的大小和位置,既包括在文件中也包括在内存中的信息。此类型的段在文件中最多出现一次。此外,只有程序头部表是程序的内存映像的一部分时,它才会出现。如果此类型段存在,则必须在所有可加载段项目的前面。
PT_LOPROC~PT_HIPROC 0x70000000 ~0x7fffffff 此范围的类型保留给处理器专用语义。

p_offset

存放从文件开始到该段开头的第一个字节的偏移

关于段和节的关系

简单来说段就是节的集合,段主要在程序装载时起作用。

  • 有执行功能的代码指令流和只读属性的数据节组合成只读代码段。
  • 可修改的数据变量,和动态调用的相对地址寻址入口组合成读写数据段。
  • 不同的段来说可能会有所重合,即不同的段包含相同的节

Section Header Table

其类型为ELF32_Shdr结构体数组,存放节信息。其数据结构如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* Section header.  */

typedef struct
{
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;

还是先了解部分字段

sh_type

名称 取值 说明
SHT_NULL 0 该类型节区是非活动的,这种类型的节头中的其它成员取值无意义。
SHT_PROGBITS 1 该类型节区包含程序定义的信息,它的格式和含义都由程序来决定。
SHT_SYMTAB 2 该类型节区包含一个符号表(SYMbol TABle)。目前目标文件对每种类型的
SHT_STRTAB 3 该类型节区包含字符串表( STRing TABle )。
SHT_RELA 4 该类型节区包含显式指定位数的重定位项( RELocation entry with Addends ),例如,32 位目标文件中的 Elf32_Rela 类型。此外,目标文件可能拥有多个重定位节区。
SHT_HASH 5 该类型节区包含符号哈希表( HASH table )。
SHT_DYNAMIC 6 该类型节区包含动态链接的信息( DYNAMIC linking )。
SHT_NOTE 7 该类型节区包含以某种方式标记文件的信息(NOTE)。
SHT_NOBITS 8 该类型节区不占用文件的空间,其它方面和 SHT_PROGBITS 相似。尽管该类型节区不包含任何字节,其对应的节头成员 sh_offset 中还是会包含概念性的文件偏移。
SHT_REL 9 该类型节区包含重定位表项(RELocation entry without Addends),不过并没有指定位数。例如,32 位目标文件中的 Elf32_rel 类型。目标文件中可以拥有多个重定位节区。
SHT_SHLIB 10 该类型此节区被保留,不过其语义尚未被定义。
SHT_DYNSYM 11 作为一个完整的符号表,它可能包含很多对动态链接而言不必 要的符号。因此,目标文件也可以包含一个 SHT_DYNSYM 节区,其中保存动态链接符号的一个最小集合,以节省空间。
SHT_LOPROC 0X70000000 此值指定保留给处理器专用语义的下界( LOw PROCessor-specific semantics )。
SHT_HIPROC 0X7FFFFFFF 此值指定保留给处理器专用语义的上界( HIgh PROCessor-specific semantics )。
SHT_LOUSER 0X80000000 此值指定保留给应用程序的索引下界。
SHT_HIUSER 0X8FFFFFFF 此值指定保留给应用程序的索引上界。

Section

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

  • 每个节区都有对应的节头来描述它。但是反过来,节区头部并不一定会对应着一个节区。
  • 每个节区在目标文件中是连续的,但是大小可能为 0。
  • 任意两个节区不能重叠,即一个字节不能同时存在于两个节区中。
  • 目标文件中可能会有闲置空间(inactive space),各种头和节不一定会覆盖到目标文件中的所有字节,闲置区域的内容未指定。

先了解一些特殊节(前边带.的节是系统保留的)

名称 类型 属性 含义
.comment SHT_PROGBITS 包含版本控制信息。
.debug SHT_PROGBITS 此节区包含用于符号调试的信息。
.dynamic SHT_DYNAMIC SHF_ALLOC SHF_WRITE 此节区包含动态链接信息。SHF_WRITE 位设置与否是否被设置取决于具体的处理器。
.dynstr SHT_STRTAB SHF_ALLOC 此节区包含用于动态链接的字符串,大多数 情况下这些字符串代表了与符号表项相关的名称。
.dynsym SHT_DYNSYM SHF_ALLOC 此节区包含动态链接符号表。
.got SHT_PROGBITS 此节区包含全局偏移表。
.line SHT_PROGBITS 此节区包含符号调试的行号信息,描述了源程序与机器指令之间的对应关系,其内容是未定义的。
.plt SHT_PROGBITS 此节区包含过程链接表(procedure linkage table)。
.relname SHT_REL 这些节区中包含重定位信息。如果文件中包含可加载的段,段中有重定位内容,节区的属性将包含 SHF_ALLOC 位,否则该位置 0。传统上 name 根据重定位所适用的节区给定。例如 .text 节区的重定位节区名字将是:.rel.text 或者 .rela.text。
.relaname SHT_RELA
.shstrtab SHT_STRTAB 此节区包含节区名称。