ELF 文件
本部分内容来源于 ELF 1.2 标准,内容经过一定的修改与整理,主要参考文献如下
- ELF 文件格式分析,北京大学,滕启明
- ELF-摧毁圣诞
简介
ELF (Executable and Linkable Format)文件,也就是在 Linux 中的目标文件,主要有以下三种类型
- 可重定位文件(Relocatable File),包含由编译器生成的代码以及数据。链接器会将它与其它目标文件链接起来从而创建可执行文件或者共享目标文件。在 Linux 系统中,这种文件的后缀一般为
.o
。 -
可执行文件(Executable File),就是我们通常在 Linux 中执行的程序。
-
共享目标文件(Shared Object File),包含代码和数据,这种文件是我们所称的库文件,一般以
.so
结尾。一般情况下,它有以下两种使用情景 - 链接器(Link eDitor, ld)可能会处理它和其它可重定位文件以及共享目标文件,生成另外一个目标文件。
- 动态链接器(Dynamic Linker)将它与可执行文件以及其它共享目标组合在一起生成进程镜像。
关于Link eDitor的命名,https://en.wikipedia.org/wiki/GNU_linker
目标文件由汇编器和链接器创建,是文本程序的二进制形式,可以直接在处理器上运行。那些需要虚拟机才能够执行的程序(Java)不属于这一范围。
这里我们主要关注于 ELF 的文件格式。
文件格式
目标文件既会参与程序链接又会参与程序执行。出于方便性和效率考虑,根据过程的不同,目标文件格式提供了其内容的两种并行视图,如下
首先,我们来关注一下链接视图。
文件开始处是 ELF 头部( ELF Header),它给出了整个文件的组织情况。
如果程序头部表(Program Header Table)存在的话,它会告诉系统如何创建进程。用于生成进程的目标文件必须具有程序头部表,但是重定位文件不需要这个表。
节区部分包含在链接视图中要使用的大部分信息:指令、数据、符号表、重定位信息等等。
节区头部表(Section Header Table)包含了描述文件节区的信息,每个节区在表中都有一个表项,会给出节区名称、节区大小等信息。用于链接的目标文件必须有节区头部表,其它目标文件则无所谓,可以有,也可以没有。
这里给出一个关于链接视图比较形象的展示
对于执行视图来说,其主要的不同点在于没有了section,而有了多个segment。其实这里的 segment 大都是来源于链接视图中的 section。
注意:
尽管图中是按照 ELF 头,程序头部表,节区,节区头部表的顺序排列的。但实际上除了 ELF 头部表以外,其它部分都没有严格的的顺序。
数据形式
ELF 文件格式支持 8 位/32 位体系结构。当然,这种格式是可以扩展的,也可以支持更小的或者更大位数的处理器架构。因此,目标文件会包含一些控制数据,这部分数据表明了目标文件所使用的架构,这也使得它可以被通用的方式来识别和解释。目标文件中的其它数据采用目的处理器的格式进行编码,与在何种机器上创建没有关系。这里其实想表明的意思目标文件可以进行交叉编译,我们可以在 x86 平台生成 arm 平台的可执行代码。
目标文件中的所有数据结构都遵从“自然”大小和对齐规则。如下
名称 | 长度 | 对齐方式 | 用途 |
---|---|---|---|
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 字节对齐,强制数据结构的大小是 4 的整数倍等等。数据同样适用是对齐的。因此,包含一个 Elf32_Addr 类型成员的结构体会在文件中的 4 字节边界处对齐。
为了具有可移植性,ELF 文件不使用位域。
字符表示
待。
注:在下面的介绍中,我们以 32 位为主进行介绍。
ELF Header
ELF Header 描述了 ELF 文件的概要信息,利用这个数据结构可以索引到 ELF 文件的全部信息,数据结构如下:
#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 开头的,它们应该都是 ELF 的缩写。每个成员具体的说明如下。
e_ident
正如之前所说,ELF 提供了一个目标文件框架,以便于支持多种处理器,多种编码格式的机器。该变量给出了用于解码和解释文件中与机器无关的数据的方式。这个数组对于不同的下标的含义如下
宏名称 | 下标 | 目的 |
---|---|---|
EI_MAG0 | 0 | 文件标识 |
EI_MAG1 | 1 | 文件标识 |
EI_MAG2 | 2 | 文件标识 |
EI_MAG3 | 3 | 文件标识 |
EI_CLASS | 4 | 文件类 |
EI_DATA | 5 | 数据编码 |
EI_VERSION | 6 | 文件版本 |
EI_PAD | 7 | 补齐字节开始处 |
其中,
e_ident[EI_MAG0]
到 e_ident[EI_MAG3]
,即文件的头4个字节,被称作“魔数”,标识该文件是一个ELF目标文件。至于开头为什么是0x7f,并没有仔细去查过。
名称 | 值 | 位置 |
---|---|---|
ELFMAG0 | 0x7f | e_ident[EI_MAG0] |
ELFMAG1 | ‘E’ | e_ident[EI_MAG1] |
ELFMAG2 | ‘L’ | e_ident[EI_MAG2] |
ELFMAG3 | ‘F’ | e_ident[EI_MAG3] |
e_ident[EI_CLASS]
为 e_ident[EI_MAG3]
的下一个字节,标识文件的类型或容量。
名称 | 值 | 意义 |
---|---|---|
ELFCLASSNONE | 0 | 无效类型 |
ELFCLASS32 | 1 | 32位文件 |
ELFCLASS64 | 2 | 64位文件 |
ELF 文件的设计使得它可以在多种字节长度的机器之间移植,而不需要强制规定机器的最长字节长度和最短字节长度。ELFCLASS32
类型支持文件大小和虚拟地址空间上限为 4GB 的机器;它使用上述定义中的基本类型。
ELFCLASS64
类型用于 64 位架构。
e_ident[EI_DATA]
字节给出了目标文件中的特定处理器数据的编码方式。下面是目前已定义的编码:
名称 | 值 | 意义 |
---|---|---|
ELFDATANONE | 0 | 无效数据编码 |
ELFDATA2LSB | 1 | 小端 |
ELFDATA2MSB | 2 | 大端 |
其它值被保留,在未来必要时将被赋予新的编码。
文件数据编码方式表明了文件内容的解析方式。正如之前所述,ELFCLASS32
类型文件使用了具有1,2 和 4 字节的变量类型。对于已定义的不同的编码方式,其表示如下所示,其中字节号在左上角。
ELFDATA2LSB
编码使用补码,最低有效位(Least Significant Byte)占用最低地址。
ELFDATA2MSB
编码使用补码,最高有效位(Most Significant Byte)占用最低地址。
e_ident[EI_DATA]
给出了 ELF 头的版本号。目前这个值必须是EV_CURRENT
,即之前已经给出的e_version
。
e_ident[EI_PAD]
给出了 e_ident
中未使用字节的开始地址。这些字节被保留并置为0;处理目标文件的程序应该忽略它们。如果之后这些字节被使用,EI_PAD的值就会改变。
e_type
e_type
标识目标文件类型。
名称 | 值 | 意义 |
---|---|---|
ET_NONE | 0 | 无文件类型 |
ET_REL | 1 | 可重定位文件 |
ET_EXEC | 2 | 可执行文件 |
ET_DYN | 3 | 共享目标文件 |
ET_CORE | 4 | 核心转储文件 |
ET_LOPROC | 0xff00 | 处理器指定下限 |
ET_HIPROC | 0xffff | 处理器指定上限 |
虽然核心转储文件的内容没有被详细说明,但 ET_CORE
还是被保留用于标志此类文件。从 ET_LOPROC
到 ET_HIPROC
(包括边界)被保留用于处理器指定的场景。其它值在未来必要时可被赋予新的目标文件类型。
e_machine
这一项指定了当前文件可以运行的机器架构。
名称 | 值 | 意义 |
---|---|---|
EM_NONE | 0 | 无机器类型 |
EM_M32 | 1 | AT&T WE 32100 |
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 |
其中 EM 应该是 ELF Machine
的简写。
其它值被在未来必要时用于新的机器。 此外,特定处理器的ELF名称使用机器名称来进行区分,一般标志会有个前缀EF_
(ELF Flag)。例如,在EM_XYZ
机器上名叫 WIDGET
的标志将被称为 EF_XYZ_WIDGET
。
e_version
标识目标文件的版本。
名称 | 值 | 意义 |
---|---|---|
EV_NONE | 0 | 无效版本 |
EV_CURRENT | 1 | 当前版本 |