1 简介
可执行链接格式(Executable and Linking Format)最初是由UNIX 系统实验室(UNIX System Laboratories,USL)开发并发布的,作为应用程序二进制接口(Application Binary Interface,ABI )的一部分。工具接口标准(Tool Interface Standards,TIS)委员会将还在发展的ELF 标准选作为一种可移植的目标文件格式,可以在32 位Intel 体系结构上的很多操作系统中使用[1, 2] 。
ELF 标准的目的是为软件开发人员提供一组二进制接口定义,这些接口可以延伸到多种操作环境,从而减少重新编码、重新编译程序的需要。接口的内容包括目标模块格式、可执行文件格式以及调试记录信息与格式等。
TIS 给出的Portable Formats Specification 1.1 版本中主要针对三种不同类型的目标文件作了规定,并规定了程序加载与动态链接相关过程细节,给出了标准ANSI C 和libc例程必须提供的符号[1] 。在该组织随后发布的 Executable and Linking Format(ELF) Specification 1.2 版本中则分如下三个部分,主要的不同点是对与操作系统相关的部分进行了重新组织:
2 相关标准
2.1 System V ABI
System V Application Binary Interface(ABI)为已编译的应用程序定义了系统接口,同时也为安装脚本支持提供了一个最小环境。其目的是为应用程序提供一个标准的二进制接口,使得这些程序能够运行在符合 X/Open Commen Application Environment Specification, Issue 4.2 和System V Interf ace Defin ition, Fourth Edition 的操作系统上。二进制要包含与不同处理器体系结构相关的信息,所以 ABI并不是只有一个规范,而是一个规范体系。System V ABI 由两个部分组成:一个通用的部分,描述System V 在所有硬件平台上都一致的接口;一个处理器相关的部分,描述特定于某个处理器体系结构的具体实现。
2.2 LSB
由于我们所关心的主要是 Linux 平台上目标文件的格式,所以 Linux 标准 LSB(Linux Standard Base)也是重要的参考资料。LSB 的目标是增强不同的Linux 发布版本之间的兼容性,与ABI 类似,也由两个部分组成:
gLSB (Generic LSB )适用于所有体系结构
archLSB (Architecture Specific LSB )特定某种体系结构的LSB
目前,LSB 由SourceForge 开放源码项目社区提供支持。
3 ELF 文件格式
3.1 简介
目标文件有三种类型:
可重定位文件(Relocatable File ) 包含适合于与其他目标文件链接来创建可执行文件或者共享目标文件的代码和数据。
可执行文件(Executable File) 包含适合于执行的一个程序,此文件规定了 exec() 如何创建一个程序的进程映像。
共享目标文件 (Shared Object File) 包含可在两种上下文中链接的代码和数据。首先链接编辑器可以将它和其它可重定位文件和共享目标文件一起处理,生成另外一个目标文件。其次,动态链接器(Dynamic Linker)可能将它与某个可执行文件以及其它共享目标一起组合,创建进程映像。
目标文件全部是程序的二进制表示,目的是直接在某种处理器上直接执行。
3.1.1 目标文件中的数据表示
目标文件格式支持 8 位字节/32 位体系结构。不过这种格式是可以扩展的,目标文件因此以某些机器独立的格式表达某些控制数据,使得能够以一种公共的方式来识别和解释其内容。目标文件中的其它数据使用目标处理器的编码结构,而不管文件在何种机器上创建。
表 1 ELF 中常用数据格式
名称 大小 对齐 目的
Elf32_Addr 4 4 无符号程序地址
Elf32_Half 2 2 无符号中等整数
Elf32_Off 4 4 无符号文件偏移
Elf32_SWord 4 4 有符号大整数
Elf32_Word 4 4 无符号大整数
unsigned char 1 1 无符号小整数
目标文件中的所有数据结构都遵从“ 自然”大小和对齐规则。如果必要,数据结构可以包含显式的补齐,例如为了确保4 字节对象按4 字节边界对齐。数据对齐同样适用于文件内部。
3.2 目标文件格式
目标文件既要参与程序链接又要参与程序执行。出于方便性和效率考虑,目标文件格式提供了两种并行视图,分别反映了这些活动的不同需求。
图 1 目标文件格式
链接视图 执行视图
ELF 头部 ELF 头部
程序头部表(可选) 程序头部表
节区 1
段 1
...
节区 n
段 2
...
... ...
节区头部表 节区头部表(可选)
文件开始处是一个 ELF 头部(ELF Header ),用来描述整个文件的组织。节区部分包含链接视图的大量信息:指令、数据、符号表、重定位信息等等。
程序头部表 (Program Header Table ),如果存在的话,告诉系统如何创建进程映像。用来构造进程映像的目标文件必须具有程序头部表,可重定位文件不需要这个表。
节区头部表(Section Heade Table )包含了描述文件节区的信息,每个节区在表中都有一项,每一项给出诸如节区名称、节区大小这类信息。用于链接的目标文件必须包含节区头部表,其他目标文件可以有,也可以没有这个表。
注意:尽管图中显示的各个组成部分是有顺序的,实际上除了 ELF 头部表以外,其他节区和段都没有规定的顺序
3.3 ELF Header 部分
文件的最开始几个字节给出如何解释文件的提示信息。这些信息独立于处理器,也独立于文件中的其余内容。ELF Header 部分可以用下图中的数据结构表示:
图 2 ELF Header 数据结构
#define EI_NIDENT 16
typedef struct{
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
}Elf32_Ehdr;
其中,e_ident 数组给出了ELF 的一些标识信息,这个数组中不同下标的含义如表 2 所示:
表 2 e_ident[] 标识索引
名称 取值 目的
EI_MAG0 0 文件标识
EI_MAG1 1 文件标识
EI_MAG2 2 文件标识
EI_MAG3 3 文件标识
EI_CLASS 4 文件类
EI_DATA 5 数据编码
EI_VERSION 6 文件版本
EI_PAD 7 补齐字节开始处
EI_NIDENT 16 e_ident[]大小
这些索引访问包含以下数值的字节:
表 3 e_ident[]的内容说明
索引 说明
魔数(Magic Number),标志此文件是一个 ELF 目标文件。
名称 取值 位置
EI_MAG0 到 EI_MAG0 0x7f e_ident[EI_MAG0]
EI_MAG3 EI_MAG1 'E' e_ident[EI_MAG1]
EI_MAG2 'L' e_ident[EI_MAG2]
EI_MAG3 'F' e_ident[EI_MAG3]
标识文件的类别,或者说,容量。
名称 取值 位置
ELFCLASSNONE 0 非法类别
EI_CLASS ELFCLASS32 1 32 位目标
ELFCLASS64 2 64 位目标
ELFCLASS32 支持虚存范围 4 GB。ELFCLASS64 是为 64 位预
留的,不过文件中的其他内容都没有针对 64 位定义。
EI_DATA 字节 e_ident[EI_DATA] 给出处理器特定数据的数据编码方式。
名称 取值 位置
ELFDATANONE 0 非法数据编码
ELFDATA2LSB 1 高位在前
ELFDATA2MSB 2 低位在前
EI_VERSION ELF 头部的版本号码,不前此值必须是 EV_CURRENT。
EI_PAD 标记 e_ident 中未使用字节的开始。初始化为 0 。
某些目标文件控制结构可以增长,因为 ELF 头部能够给出它们的实际大小。如果目标文件格式确实发生改变,可能会发生控制结构比预期的大或者小的情况。在这种情况下,忽略这些信息是允许的。至于对 “缺失”信息的处理方式则要依赖于上下文,如果定义了扩展的话,将会对这些做出规定。
注意:在 32 位 Intel 体系结构上要求:
1、标志
位置 取值
e_ident[EI_CLASS] ELFCLASS32
e_ident[EI_DATA] ELFDATA2LSB
2、处理器标识(e_machine)成员必须是 EM_386。
ELF Header 中各个字段的说明如表 4 :
表 4 ELF Header 中各个字段的含义
成员 说明
e_ident 目标文件标识
目标文件类型:
名称 取值 含义
ET_NONE 0 未知目标文件格式
ET_REL 1 可重定位文件
ET_EXEC 2 可执行文件
e_type ET_DYN 3 共享目标文件
ET_CORE 4 Core 文件(转储格式)
ET_LOPROC 0xff00 特定处理器文件
ET_HIPROC 0xffff 特定处理器文件
ET_LOPROC 和 ET_HIPROC 之间的取值用来标识与处理器相关的文件
格式。
给出文件的目标体系结构类型
名称 取值 含义
EM_NONE 0 未指定
EM_M32 1 AT&T WE 32100
e_machine
EM_SPARC 2 SPARC
EM_386 3 Intel 80386
EM_68K 4 Motorola 68000
EM_88K 5 Motorola 88000
EM_860 7 Intel 80860
EM_MIPS 8 MIPS RS3000
其它值都是保留的。特定处理器的 ELF 名称会使用机器名来进行区分。
目标文件版本
名称 取值 含义
e_version
EV_NONE 0 非法版本
EV_CURRENT 1 当前版本
e_entry 程序入口的虚拟地址。如果目标文件没有程序入口,可以为 0 。
程序头部表格(Program Header Table)的偏移量(按字节计算)。如果文
e_phoff
件没有程序头部表格,可以为 0 。
节区头部表格 (Section Header Table )的偏移量 (按字节计算)。如果文件
e_shoff
没有节区头部表格,可以为 0 。
保存与文件相关的,特定于处理器的标志。标志名称采用 EF_machine_flag
e_flags
的格式。
e_ehsize ELF 头部的大小(以字节计算)。
e_phentsize 程序头部表格的表项大小(按字节计算)。
e_phnum 程序头部表格的表项数目。可以为 0 。
e_shentsize 节区头部表格的表项大小(按字节计算)。
e_shnum 节区头部表格的表项数目。可以为 0 。
节区头部表格中与节区名称字符串表相关的表项的索引。如果文件没有节
e_shstrndx
区名称字符串表,此参数可以为 SHN_UNDEF 。
3.4 节区(Sections)
节区中包含目标文件中的所有信息,除了:ELF 头部、程序头部表格、节区头部表格。节区满足以下条件:
(1). 目标文件中的每个节区都有对应的节区头部描述它,反过来,有节区头部不意味着有节区。
(2). 每个节区占用文件中一个连续字节区域(这个区域可能长度为 0 )。
(3). 文件中的节区不能重叠,不允许一个字节存在于两个节区中的情况发生。
(4). 目标文件中可能包含非活动空间(INACTIVE SPACE )。这些区域不属于任何头部和节区,其内容未指定。
3.4.1 节区头部表格
ELF 头部中,e_shoff 成员给出从文件头到节区头部表格的偏移字节数;e_shnum给出表格中条目数目;e_shentsize给出每个项目的字节数。从这些信息中可以确切地定位节区的具体位置、长度。
节区头部表格中比较特殊的几个下标如下:
表 5 节区头部表格中的特殊下标
名称 取值 说明
SHN_UNDEF 0 标记未定义的、缺失的、不相关的,或者没有含义的
节区引用
SHN_LORESERVE OXFF00 保留索引的下界
SHN_LOPROC 0XFF00
保留给处理器特殊的语义
SHN_HIPROC 0XFF1F
包含对应引用量的绝对取值。这些值不会被重定位所
SHN_ABS OXFFF1
影响
相对于此节区定义的符号是公共符号。如 FORTRAN
SHN_COMMON OXFFF2
中 COMMON 或者未分配的 C 外部变量。
SHN_HIRESERVE 0XFFFF 保留索引的上界
介于 SHN_LORESERVE 和 SHN_HIRESERVE 之间的表项不会出现在节区头部表中。
3.4.2 节区头部
每个节区头部可以用如下数据结构描述:
图 3 节区头部数据结构
typedef struct{
Elf32_Word sh_name;
Elf32_Word sh_type;
Elf32_Word sh_flags;
Elf32_Addr sh_addr;
Elf32_Off sh_offset;
Elf32_Word sh_size;
Elf32_Word sh_link;
Elf32_Word sh_info;
Elf32_Word sh_addralign;
Elf32_Word sh_entsize;
}Elf32_Shdr;
对其中各个字段的解释如下:
表 6 节区头部字段说明
成员 说明
给出节区名称。是节区头部字符串表节区(Section Header String sh_name Table Section )的索引。名字是一个 NULL 结尾的字符串。
sh_type 为节区的内容和语义进行分类。参见节区类型。
sh_flags 节区支持 1 位形式的标志,这些标志描述了多种属性。如果节区将出现在进程的内存映像中,此成员给出节区的第一个字 sh_addr 节应处的位置。否则,此字段为 0 。
此成员的取值给出节区的第一个字节与文件头之间的偏移。不过,sh_offset SHT_NOBITS 类型的节区不占用文件的空间,因此其 sh_offset 成员给出的是其概念性的偏移。
此成员给出节区的长度(字节数)。除非节区的类型是 sh_size
SHT_NOBITS ,否则节区占用文件中的 sh_size 字节。类型为SHT_NOBITS 的节区长度可能非零,不过却不占用文件中的空间。
sh_link 此成员给出节区头部表索引链接。其具体的解释依赖于节区类型。
sh_info 此成员给出附加信息,其解释依赖于节区类型。
某些节区带有地址对齐约束。例如,如果一个节区保存一个doubleword,那么系统必须保证整个节区能够按双字对齐。sh_addr
sh_addralign 对 sh_addralign 取模,结果必须为 0 。目前仅允许取值为 0 和 2 的幂次数。数值 0 和 1 表示节区没有对齐约束。
某些节区中包含固定大小的项目,如符号表。对于这类节区,此成 sh_entsize 员给出每个表项的长度字节数。 如果节区中并不包含固定长度表项的表格,此成员取值为 0 。
索引为零 (SHN_UNDEF )的节区头部也是存在的,尽管此索引标记的是未定义的节区引用。这个节区的内容固定如下:
表 7 SHN_UNDEF(0)节区的内容
字段名称 取值 说明
sh_name 0 无名称
sh_type SHT_NULL 非活动
sh_flags 0 无标志
sh_addr 0 无地址
sh_offset 0 无文件偏移
sh_size 0 无尺寸大小
sh_link SHN_UNDEF 无链接信息
sh_info 0 无辅助信息
sh_addralign 0 无对齐要求
sh_entsize 0 无表项
3.4.2.1 节区类型—sh_type 字段
节区类型定义如表 8 :
表 8 节区类型定义
名称 取值 说明
此值标志节区头部是非活动的,没有对应的节区。此节区头部
SHT_NULL 0
中的其他成员取值无意义。
SHT_PROGBITS 1 此节区包含程序定义的信息,其格式和含义都由程序来解释。
此节区包含一个符号表。目前目标文件对每种类型的节区都只
能包含一个,不过这个限制将来可能发生变化。
SHT_SYMTAB 2
一般,SHT_SYMTAB 节区提供用于链接编辑(指 ld 而言)
的符号,尽管也可用来实现动态链接。
SHT_STRTAB 3 此节区包含字符串表。目标文件可能包含多个字符串表节区。
此节区包含重定位表项,其中可能会有补齐内容 (addend ),例
SHT_RELA 4 如 32 位目标文件中的 Elf32_Rela 类型。目标文件可能拥有多
个重定位节区。
此节区包含符号哈希表。所有参与动态链接的目标都必须包含
SHT_HASH 5 一个符号哈希表。目前,一个目标文件只能包含一个哈希表,
不过此限制将来可能会解除。
此节区包含动态链接的信息。目前一个目标文件中只能包含一
SHT_DYNAMIC 6
个动态节区,将来可能会取消这一限制。
SHT_NOTE 7 此节区包含以某种方式来标记文件的信息。
这种类型 的节区不 占用文件中的空间,其他方面和
SHT_NOBITS 8 SHT_PROGBITS 相似。尽管此节区不包含任何字节,成员
sh_offset 中还是会包含概念性的文件偏移
此节区包含重定位表项,其中没有补齐 (addends),例如 32 位
SHT_REL 9 目标文件中的 Elf32_rel 类型。目标文件中可以拥有多个重定
位节区。
此节区被保留,不过其语义是未规定的。包含此类型节区的程
SHT_SHLIB 10
序与 ABI 不兼容。
作为一个完整的符号表,它可能包含很多对动态链接而言不必
SHT_DYNSYM 11 要的符号。因此,目标文件也可以包含一个 SHT_DYNSYM 节
区,其中保存动态链接符号的一个最小集合,以节省空间。
SHT_LOPROC 0X70000000
这一段(包括两个边界),是保留给处理器专用语义的。
SHT_HIPROC OX7FFFFFFF
SHT_LOUSER 0X80000000 此值给出保留给应用程序的索引下界。
SHT_HIUSER 0X8FFFFFFF 此值给出保留给应用程序的索引上界。
其它的节区类型是保留的。
3.4.2.2 sh_flags 字段
sh_flags 字段定义了一个节区中包含的内容是否可以修改、是否可以执行等信息。如果一个标志位被设置,则该位取值为 1。未定义的各位都设置为0。
表 9 节区头部的 sh_flags 字段取值
名称 取值
SHF_WRITE 0x1
SHF_ALLOC 0x2
SHF_EXECINSTR 0x4
SHF_MASKPROC 0xF0000000
其中已经定义了的各位含义如下:
SHF_WRITE: 节区包含进程执行过程中将可写的数据。
SHF_ALLOC: 此节区在进程执行过程中占用内存。某些控制节区并不出现于目标
文件的内存映像中,对于那些节区,此位应设置为 0。
SHF_EXECINSTR: 节区包含可执行的机器指令。
SHF_MASKPROC: 所有包含于此掩码中的四位都用于处理器专用的语义。
3.4.2.3 sh_link 和 sh_info 字段
根据节区类型的不同,sh_link 和 sh_info 的具体含义也有所不同:
表 10 sh_link 和 sh_info 字段解释
sh_type sh_link sh_info
此节区中条目所用到的字符串表格
SHT_DYNAMIC 0
的节区头部索引
此哈希表所适用的符号表的节区头
SHT_HASH 0
部索引
SHT_REL 重定位所适用的节区的
相关符号表的节区头部索引
SHT_RELA 节区头部索引
最后一个局部符号(绑
SHT_SYMTAB
相关联的字符串表的节区头部索引 定 STB_LOCAL )的符
SHT_DYNSYM
号表索引值加一
其它 SHN_UNDEF 0
3.4.3 特殊节区
很多节区中包含了程序和控制信息。下面的表中给出了系统使用的节区,以及它们的类型和属性。
表 11 常见特殊节区
名称 类型 属性 含义
包含将出现在程序的内存映像中的为初始
SHF_ALLOC + 化数据。根据定义,当程序开始执行,系统
.bss SHT_NOBITS
SHF_WRITE 将把这些数据初始化为 0 。此节区不占用文
件空间。
.comment SHT_PROGBITS (无) 包含版本控制信息。
SHF_ALLOC +
.data SHT_PROGBITS
SHF_WRITE 这些节区包含初始化了的数据,将出现在程
SHF_ALLOC + 序的内存映像中。
.data1 SHT_PROGBITS
SHF_WRITE
.debug SHT_PROGBITS (无) 此节区包含用于符号调试的信息。
此节区包含动态链接信息。节区的属性将包
.dynamic SHT_DYNAMIC 含 SHF_ALLOC 位。是否 SHF_WRITE 位
被设置取决于处理器。
此节区包含用于动态链接的字符串,大多数
.dynstr SHT_STRTAB SHF_ALLOC 情况下这些字符串代表了与符号表项相关
的名称。
.dynsym SHT_DYNSYM SHF_ALLOC 此节区包含了动态链接符号表。
此节区包含了可执行的指令,是进程终止代
SHF_ALLOC +
.fini SHT_PROGBITS 码的一部分。程序正常退出时,系统将安排
SHF_EXECINSTR
执行这里的代码。
.got SHT_PROGBITS 此节区包含全局偏移表。
.hash SHT_HASH SHF_ALLOC 此节区包含了一个符号哈希表。
此节区包含了可执行指令,是进程初始化代
SHF_ALLOC + 码的一部分。当程序开始执行时,系统要在
.init SHT_PROGBITS
SHF_EXECINSTR 开始调用主程序入口之前(通常指 C 语言
的 main 函数)执行这些代码。
此节区包含程序解释器的路径名。如果程序
包含一个可加载的段,段中包含此节区,那
.interp SHT_PROGBITS
么节区的属性将包含 SHF_ALLOC 位,否
则该位为 0 。
此节区包含符号调试的行号信息,其中描述
.line SHT_PROGBITS (无) 了源程序与机器指令之间的对应关系。其内
容是未定义的。