# ELF 原理一

什么是ELF: Executable and Linking Format 可执行、可链接格式

可执行和链接格式最初是由UNIX系统实验室(USL)开发并发布的,是应用程序二进制接口 (ABI:二进制程序表示的所有工具集所遵循的一组运行时约定,包括:编译器、汇编器、连接器和语言运行时支持。有些ABI是正式的,有书面的规范,它们可能是由多个相关组织一起定义并设计的,而其他的一些ABI,只是由一组特定的工具来指定相关的一些约束) 的一部分。工具接口标准委员会(TIS)选择了不断发展的ELF标准作为可移植的目标文件格式,以使基于32位/64位的Intel 架构上运行的应用程序能够在不同操作系统之间运行。

ELF标准为开发人员提供统一的二进制接口定义以简化程序员开发应用程序时的复杂性,只要操作系统支持该格式,那么就不要对不同操作系统的目标文件格式来设计对应的代码和接口(当然,这只在类Unix系统中使用,windows是个奇葩,它有它的想法),这将减少不同接口实现的数量,从而使应用的源程序代码不需要根据特定的格式来重新编码和重新编译。

ELF系列文章

ELF系列的文章将介绍:在32位类Unix系统上的ELF文件格式的原理,开发人员可以通过本系列文章深入理解ELF的执行原理。该系列文章按以下三个部分来完成编写:

  1. “对象文件(Object Files)” 描述了三种主要 ELF 对象目标文件格式
  2. ”程序加载和动态链接(Program Loading and Dynamic Linking)“ 描述各个对象文件的内部信息和根据ELF对象文件创建进程的过程
  3. ”C函数库(‘C Library)“ 列出系统库中包含的符号信息,标准ANSI C 和 libc 中的函数信息和全局符号信息

目标对象文件

什么是目标对象文件

ELF 目标对象文件格式分为如下三种格式:

  1. 可重定向文件格式(relocatable file)也称之为静态链接库,包含 部分 可执行文件格式或者动态链接库格式的数据或者代码,需要与其他两种格式之一进行整合
  2. 可执行文件格式(executable file) 包含程序执行所需的代码和数据信息,被操作系统用于创建进程
  3. 共享库(shared object file)也称之为 动态链接库,件包含适合在以上两种文件格式中需要链接的代码和数据信息。首先,链接器(ld)可以将它与其他可重定位和共享对象文件一起处理,以创建另一个新的对象文件。其次,动态连接器将共享库与一个可执行文件和其他共享对象结合起来创建一个进程

目标对象文件由汇编器和链接器创建,是程序的二进制表示形式,以表示直接在处理器上执行的代码和数据。与他其他抽象机器的程序(如:shell脚本)不一样,它本身包含了代执行的目标ISA指令集的数据和二进制代码(不在混沌学堂的同学可以看看混沌学堂的视频加深理解)。

目标对象文件格式

目标对象文件将参与程序链接(用于构建可执行程序)和程序执行(用于构建进程)。为了方便和高效的表示object文件格式,ELF 提供了表示文件内容的两种不同视图,反映了 ELF 文件 在链接和执行时的不同,如图1-1所示。

img

图 1-1 ELF 目标文件格式:链接视图与执行视图(尽管图中将的程序头部表放置在ELF 头信息之后,section头部表紧跟在sections头之后,但实际的文件可能会有所不同。此外,节和段没有指定的顺序。只有ELF头部在文件中有固定的位置)

ELF 头部信息位于ELF 文件最开始之处,保存着描述文件组织的信息,用于指明ELF文件的元数据信息,比如:节头部表偏移量(Section header table)、程序头部表偏移量(Program header table)、目标文件类型(静态链接库、可执行文件、动态链接库)。节(section)保存着链接视图中的大量对象文件信息:指令、数据、符号表、重定位信息等等。

程序头部表用于告知操作系统如何创建进程。用于构建进程(也即可执行文件)的文件必须有一个程序头部表,而对于可重定位的文件则不需要程序头部表。节头部表(Section header table)包含了描述文件各节的信息,每个节在表中都有一个条目,每个条目给出了诸如节名、节大小等信息。链接过程中使用的文件(静态链接库)必须有一个节头部表,其他目标文件可能存在节头部表,也可能没有。

ELF 数据表示

ELF 对象文件格式支持具有8位字节和32位架构的各种处理器(本系列文章以32位为例,读者可自己反推64位的ELF,详细参考Linux内核源码)。因为ELF 目的是可扩展到更大(或更小)的体系结构,也即保持数据类型平台无关性,因此,对象文件以一种独立于机器的格式表示一些控制数据,这使得以一种常见的方式识别对象文件信息并解释其内容成为可能(参考下C语言的宏定义的出现的原因)。对象文件中的数据使用特定的目标处理器进行编码,而不管ELF文件在哪个类型处理器上运行。如下图 1-2 描述了 在32位处理器上的类型表示。

img

图 1- 2 ELF 32位处理器上的数据表示(name表示数据名、size表示大小单位为字节、alignment表示对齐信息、purpose表示数据类型的意义)

ELF 文件格式定义的所有数据结构都遵循特定的大小和对齐规则。如有必要,对应的struct结构体需要填充0以确保4字节对齐,相应的,基础数据类型(1字节、2字节、4字节、8字节)均需要对齐。例如,包含一个Elf32_Addr成员的结构体将按4字节边界对齐。出于可移植性的原因,ELF不使用位表示(也即 bit 属性)。

ELF 头部信息

ELF 目标文件对象的控制结构,也即描述结构长度不需要固定,比如:节头部表长度、程序头部表长度,因为在ELF头部中包含它们的大小信息。注意:如果目标文件格式发生变化,程序可能会遇到比预期更大或更小的控制结构,因此程序可能会忽略一些扩展信息,而对这些缺失的扩展信息的处理取决于当前上下文,并将在扩展头部信息时指定。头部结构体定义如下图所示。

img

ELF 结构体定义

  1. e_ident :该值初始字节将文件标记为目标文件,并提供独立于机器的数据,用于解码和解释文件的内容
  2. e_type:该值标识目标对象文件的类型。如下图所示,我们常见的为:REL、EXEC、DYN。虽然 Core 文件内容未指定,但保留类型ET_CORE来标记该类型的文件。从 ET_LOPROC到ET_HIPROC 的值保留用于表示处理器特定的语义。e_type的其他整形值被保留,根据需要赋值给新的对象文件类型。 img
  3. e_machine:该值指定单个文件所需的体系结构信息。如下图所示。同理其他的整型值被保留,并将根据需要分配给新的体系结构。如果我们是Intel 32位架构,那么将被标识为 EM_386img
  4. e_version:该值用于标识ELF文件格式的版本信息,如下图所示。EV_CURRENT的值(虽然图中给出了1)将根据需要更改为当前的版本号img
  5. e_entry:该值用于标识程序执行入口的虚拟地址,从而启动进程。如果目标文件没有入口信息,则该成员保存0
  6. e_phoff:该值用于保存程序头部表的文件偏移量(以字节为单位)。如果文件没有程序头表,则该成员保存为0
  7. e_shoff:该值用于保存节头部表的文件偏移量(以字节为单位)。如果文件没有节头部表,则该成员保存为0
  8. e_flags:该值用于保存与文件相关的特定处理器标志
  9. e_ehsize:该值用于保存ELF头部信息的大小(以字节为单位)。
  10. e_phentsize:该值用于保存程序头表中一个表项的固定字节大小,所有表项的大小相同
  11. e_phnum:该值用于保存程序头部表中的表项数。因此,e_phentsize和e_phnum的乘积以字节为单位给出了表的大小,如果文件没有程序头表,e_phnum的值为0
  12. e_shentsize:该值用于保存节头部表中一个表项的固定字节大小,所有表项的大小相同
  13. e_shnum:该值用于保存节头部表中的表项数。因此,e_shentsize和e_shnum的乘积以字节为单位给出了表的大小,如果文件没有程序头表,e_shnum的值为0
  14. e_shstrndx:该值用于保存着与字符串表相关联的节头表索引。如果文件中没有字符串表,该成员保存SHN_UNDEF值。

节信息

节头部

目标对象的节头部表可以用于定位所有的节。节头表由Elf32_Shdr结构数组表示,如下所示。若上所述,ELF头的e_shoff成员给出了从文件开始到节头部表的字节偏移量,E_shnum表示节头部表包含多少个表项,E_shentsize以字节为单位给出每个表项的大小。我们通过以上信息便可遍历该数组。

img

  1. sh_name:该值用于表示节的名称。它的值是一个节头字符串表的索引下标。
  2. sh_type:该成员对节的内容和语义进行分类。节类型及其描述如下:
  3. SHT_NULL:该值将节标记为非活动状态
  4. SHT_PROGBITS:该值保存着由程序定义的节信息,其格式和含义完全由程序决定(程序是什么?考虑下谁生成了目标文件)
  5. SHT_SYMTAB 和 SHT_DYNSYM:表示当前节保存着一个符号表。SHT_SYMTAB 表示提供了用于链接器静态链接的符号,其作为一个完整的符号表,它可能包含许多动态链接中不需要的符号,因此,一个对象文件也可以包含一个 SHT_DYNSYM 的节用于保存动态链接符号的最小集合,以节省空间。所以我们可以通过strip去掉完整符号表,但这会导致不能进行静态链接
  6. SHT_STRTAB:表示当前节为字符串表
  7. SHT_RELA:表示当前节保存着带有显式加数的重定位表项,例如对象文件的32位类的Elf32_Rela类型(后面会说这里的加数是什么)
  8. SHT_HASH:表示当前节保存着一个符号哈希表。所有参与动态链接的对象文件必须包含一个符号哈希表
  9. SHT_DYNAMIC:表示当前节保存动态链接信息
  10. SHT_NOTE:表示当前节保存文件的注释信息
  11. SHT_NOBITS:表示不占任何空间的节信息,虽然该节不包含字节,但是sh_offset成员包含概念上的文件偏移量
  12. SHT_REL:表示该节保存着没有显式加数的重定位表项
  13. SHT_SHLIB:该节类型是保留的,但具有未指定的语义。包含这种类型的部分的程序不符合ABI规范
  14. SHT_LOPROC 与 SHT_HIPROC:这个范围内的值为特定于处理器的语义保留
  15. SHT_LOUSER:该类型的节表示应用程序保留的索引范围的下界
  16. SHT_HIUSER:该类型的节表示应用程序保留的索引范围的上限
  17. sh_flags:节支持描述其他属性的位标志。标志定义如下:
  18. SHF_WRITE:表示该节中包含在进程执行期间可写的数据
  19. SHF_ALLOC:表示该节在进程执行期间占用的内存
  20. SHF_EXECINSTR :表示该节包含可执行的机器指令
  21. SHF_MASKPROC:这个掩码中包含的所有位都为特定处理器语义保留
  22. sh_addr:如果该section将出现在进程的内存虚拟地址中,该成员将给出该section的第一个字节所在的地址。否则,成员包含0。
  23. sh_offset:该值给出了从文件开头到当前节的第一个字节的偏移量
  24. sh_size:该值以字节为单位给出当前节的大小
  25. sh_link:该值表示一个节头表索引链接,其解释取决于当前节的类型
  26. sh_info:该值用于表示额外的信息,其解释取决于当前节类型
  27. sh_addralign :用于指定节对齐值。有些节需要指定地址对齐,例如,如果一个节保存着一个双字(32位),系统必须确保整个节的双字对齐。也即sh_addr的值必须以sh_addralign的值取模值需要为0
  28. sh_entsize:有些节保存着一个固定大小的表项,比如一个符号表。对于这样的节,该成员给出每个表项的字节大小。如果节中没有固定大小的表项,则该成员为0。

特殊的节

特殊的节的分别用于保存程序信息和控制信息。下面列表中的部分由操作系统使用,并具有指定的类型和属性。

img

img

  1. bss:表示该节保存着未初始化的数据,这些数据构成了程序的数据内存排布。根据定义,当程序开始运行时,系统用零初始化这些数据。节类型 SHT_NOBITS 表示该节不占用文件空间。
  2. comment:持有注释信息
  3. data 和 data1:表示该节保存着已经初始化的数据,这些数据构成了程序的数据内存排布
  4. debug:表示该节保存了用于调试的符号信息
  5. dynamic:表示该节保存动态链接信息
  6. dynstr:表示该节保存动态链接所需的字符串,最常见的是表示符号表项相关名称的字符串
  7. dynsym:表示该节保存着动态链接符号表
  8. fini:表示该节保存了进程终止代码的可执行指令。也就是说,当一个程序正常退出时将会执行这一部分的代码
  9. got:表示该节保存了全局偏移量表
  10. hash:表示该节保存了符号hash表
  11. init:表示该节保存了进程初始化代码的可执行指令。也就是说,当一个程序开始运行时,将会在调用主程序入口点(C程序的main函数)之前执行这一部分的代码
  12. interp:表示该节保存程序动态链接器的路径名。如果文件中有一个包含该节的可加载段,该节的属性将包括SHF_ALLOC位
  13. line:表示该节保存用于符号调试的行号信息,它描述了源程序和机器码之间的对应关系
  14. note:表示该节保存了标注信息
  15. plt:表示该节保存了程序连接表信息
  16. rel name 和 rela name:表示该节保存着重定位信息。如果文件中有一个包含重定位的可加载段,则节的属性将包含 SHF_ALLOC 。通常,name 由重定位的节提供。因此.text 的重定位节的名称通常为.rel.text 或 .rela.text
  17. rodata 和 rodata1:表示该节保存只读数据,这些数据通常会在进程中形成一个不可写的数据段
  18. shstrtab:表示该节保存节的名字
  19. strtab:表示该节保存字符串信息,最常见的是表示与符号表项关联的名称的字符串
  20. symtab:表示该节保存符号表信息
  21. text:表示当前节包含程序的可执行的指令序列信息