1436 字
7 分钟
Bk-3 ELF 文件结构基础
本节介绍 ELF 文件的基本结构,只会涉及一些最基础的知识。
ELF (Executable and Linkable Format)文件,也就是在 Linux 中的目标文件,主要有以下三种类型
- 可重定位文件(Relocatable File),包含由编译器生成的代码以及数据。链接器会将它与其它目标文件链接起来从而创建可执行文件或者共享目标文件。在 Linux 系统中,这种文件的后缀一般为
.o
。 - 可执行文件(Executable File),就是我们通常在 Linux 中执行的程序。
- 共享目标文件(Shared Object File),包含代码和数据,这种文件是我们所称的库文件,一般以
.so
结尾。一般情况下,它有以下两种使用情景:- 链接器(Link eDitor, ld)可能会处理它和其它可重定位文件以及共享目标文件,生成另外一个目标文件。
- 动态链接器(Dynamic Linker)将它与可执行文件以及其它共享目标组合在一起生成进程镜像。
文件格式
ELF文件可能既会参与程序链接也会被执行,出于效率考虑,提供了两种视图用于结构化文件格式
链接视图
- ELF Header
- Program Header Table optional
- Section 1~n
- Section Header Table
- ELF Header:这里是文件开始处,为ELF头部,给出了整个文件的组织信息
- Program Header Table:程序头部表,可能存在。如果存在,用于告诉系统如何创建进程。
- Section 1~n:文件节。节区部分包含在链接视图中要使用的大部分信息:指令、数据、符号表、重定位信息等等。
- Section Header Table:节区头部表(Section Header Table)包含了描述文件节区的信息,每个节区在表中都有一个表项,会给出节区名称、节区大小等信息。用于链接的目标文件必须有节区头部表,其它目标文件则无所谓,可以有,也可以没有。
执行视图
- ELF Header
- Program Header Table optional
- Segment 1~n
- Section Header Table
- ELF Header:这里是文件开始处,为ELF头部,给出了整个文件的组织信息
- Program Header Table:程序头部表,可能存在。如果存在,用于告诉系统如何创建进程。
- Segment 1~n:文件段。对于执行视图来说,其主要的不同点在于没有了 section,而有了多个 segment。其实这里的 segment 大都是来源于链接视图中的 section。
- Section Header Table:节区头部表(Section Header Table)包含了描述文件节区的信息,每个节区在表中都有一个表项,会给出节区名称、节区大小等信息。用于链接的目标文件必须有节区头部表,其它目标文件则无所谓,可以有,也可以没有。
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;
Program Header Table
Program Header Table 是一个结构体数组,每一个元素的类型是 Elf32_Phdr
,描述了一个段或者其它系统在准备程序执行时所需要的信息。其中,ELF 头中的 e_phentsize
和 e_phnum
指定了该数组每个元素的大小以及元素个数。一个目标文件的段包含一个或者多个节。程序的头部只有对于可执行文件和共享目标文件有意义。
可以说,Program Header Table 就是专门为 ELF 文件运行时中的段所准备的。
Segment
一个段可能包括一到多个节区,但是这并不会影响程序的加载。尽管如此,我们也必须需要各种各样的数据来使得程序可以执行以及动态链接等等。下面会给出一般情况下的段的内容。对于不同的段来说,它的节的顺序以及所包含的节的个数有所不同。此外,与处理相关的约束可能会改变对应的段的结构。
- 代码段Text Segment
.text
可执行的指令,这里是程序的主体.rodata
存放只读数据,一般是字符串常量、全局常量等.hash
这个是字符串表的一个hash序列,主要用于快速索引对应字符串,如果修改So文件里面的字符串,该字段也需要进行修改。这也就是为什么So文件直接修改字符串会出现崩溃等问题。.dynsym
这里面主要存放了函数名称,保存了导入和导出等函数名字信息,符号表里将包含进程中所有的动态链接所需要的符号。dynsym会被全部加载到内存。.dynstr
So文件里面包含全部的字符串,dynsym只是该字段的一部分.plt
这也是我们常说的PLT, 即Procedure Linkage Table, 进程链接表. 这个表里包含了一些代码, (1)调用链接器来解析某个外部函数的地址, 并填充到.got.plt中, 然后跳转到该函数;(2)直接在.got.plt中查找并跳转到对应外部函数.rel.got
保存了重定位地址。
- 数据段Data Segment
.data
:保存初始化的全局变量.dynamic
里面包含很多信息比如你需要的动态库,以及是否立即加载以及符号表等.got
可以用于存放全局变量的地址,也可以用于存放不需要延迟绑定的函数的地址.bss
未初始化的全局变量