一、简介
1、目标文件
ELF (Executable and Linkable Format)文件,也就是在 Linux 中的目标文件,主要有以下三种类型:
- 可重定位文件(Relocatable File),包含由编译器生成的代码以及数据。链接器会将它与其它目标文件链接起来从而创建可执行文件或者共享目标文件。在 Linux 系统中,这种文件的后缀一般为
.o
。 -
可执行文件(Executable File),就是我们通常在 Linux 中执行的程序。
-
共享目标文件(Shared Object File),包含代码和数据,这种文件是我们所称的库文件,一般以
.so
结尾。一般情况下,它有以下两种使用情景。
注:目标文件由汇编器和链接器创建,是文本程序的二进制形式,可以直接在处理器上运行 。
2、ELF文件格式
(1)链接视图(Linking View)
- ELF头( ELF Header):给出了整个文件的组织情况
-
程序头部表(Program Header Table):可以不存在,若存在,其作用为告诉系统如何创建进程。
注:用于生成进程的目标文件必须具有程序头部表;重定位文件不需要这个表。 - 节区部分(section):包含在链接视图中要使用的大部分信息,如指令、数据、符号表、重定位信息等等。
- 节区头部表(Section Header Table):包含了描述文件节区的信息,每个节区在表中都有一个表项,会给出节区名称、节区大小等信息。
- 注:用于链接的目标文件必须有节区头部表,其它目标文件则无所谓,可以有,也可以没有。
(2)执行视图(Execution View)
注:与链接视图主要的不同点在于没有了section,而有了多个segment。其实这里的 segment 大都是来源于链接视图中的 section。
3、数据形式
目标文件中的所有数据结构都遵从“自然”大小和对齐规则。
注:包含一个 Elf32_Addr 类型成员的结构体会在文件中的 4 字节边界处对齐。
二、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;
(1) e_ident
作用:变量给出了用于解码和解释文件中与机器无关的数据的方式
该数组对于不同的下标的含义如下:
e_ident[EI_MAG0]
到 e_ident[EI_MAG3]
,即文件的头4个字节,为ELF的magic,标识该文件是一个ELF目标文件。具体的值和位置如下:
e_ident[EI_CLASS]
为 e_ident[EI_MAG3]
的下一个字节,标识文件的类型或容量。其值对应的含义如下:
注:ELF 文件的设计使得它可以在多种字节长度的机器之间移植,而不需要强制规定机器的最长字节长度和最短字节长度。
ELFCLASS32
类型支持文件大小和虚拟地址空间上限为 4GB 的机器
e_ident[EI_DATA]
字节给出了目标文件中的特定处理器数据的编码方式,其已定义的编码如下:
e_ident[EI_DATA]
给出了 ELF 头的版本号。目前这个值必须是EV_CURRENT
,即之前已经给出的e_version
。
e_ident[EI_PAD]
给出了 e_ident
中未使用字节的开始地址。这些字节被保留并置为0;处理目标文件的程序应该忽略它们。如果之后这些字节被使用,EI_PAD的值就会改变。
(2)e_type
作用:标识目标文件类型
其值对应的意义信息如下:
注:从 ET_LOPROC
到 ET_HIPROC
(包括边界)被保留用于处理器指定的场景
(3)e_machine
作用:指定了当前文件可以运行的机器架构
其值的含义如下:
(4)e_version
作用:标识目标文件的版本。
其值的含义如下:
(5)e_entry
作用为系统转交控制权给 ELF 中相应代码的虚拟地址。如果没有相关的入口项,则这一项为0。
(6)e_phoff
这一项给出程序头部表在文件中的字节偏移(Program Header table OFFset)。如果文件中没有程序头部表,则为0。
(7)e_shoff
这一项给出节头表在文件中的字节偏移( Section Header table OFFset )。如果文件中没有节头表,则为0。
(8)e_flags
这一项给出文件中与特定处理器相关的标志,这些标志命名格式为EF_machine_flag
。
(9)e_ehsize
这一项给出 ELF 文件头部的字节长度(ELF Header Size)。
(10)e_phentsize
这一项给出程序头部表中每个表项的字节长度。每个表项的大小相同。
(11)e_phnum
这一项给出程序头部表的项数。因此,e_phnum
与 e_phentsize
的乘积即为程序头部表的字节长度。如果文件中没有程序头部表,则该项值为0。
(12)e_shentsize
这一项给出节头的字节长度。一个节头是节头表中的一项;节头表中所有项占据的空间大小相同。
(13)e_shnum
这一项给出节头表中的项数。因此, e_shnum
与 e_shentsize
的乘积即为节头表的字节长度。如果文件中没有节头表,则该项值为0。
(14)e_shstrndx
这一项给出节头表中与节名字符串表相关的表项的索引值。如果文件中没有节名字符串表,则该项值为SHN_UNDEF
。
三、Program Header Table
作用:描述了一个段或者其它系统在准备程序执行时所需要的信息
注:ELF 头中的 e_phentsize
和 e_phnum
指定了该数组每个元素的大小以及元素个数。
一个目标文件的段包含一个或者多个节。
程序的头部只有对于可执行文件和共享目标文件有意义。
1、数据格式
typedef struct {
ELF32_Word p_type;
ELF32_Off p_offset;
ELF32_Addr p_vaddr;
ELF32_Addr p_paddr;
ELF32_Word p_filesz;
ELF32_Word p_memsz;
ELF32_Word p_flags;
ELF32_Word p_align;
} Elf32_Phdr;
2、每个字节的说明
3、段类型
4、基地址(Base Address)
(1)程序头部的虚拟地址可能并不是程序内存镜像中实际的虚拟地址。通常,可执行程序都会包含绝对地址的代码,为了使得程序可以正常执行,段必须在相应的虚拟地址处;
(2)共享目标文件通常来说包含与地址无关的代码。这可以使得共享目标文件可以被多个进程加载,同时保持程序执行的正确性。
(3)尽管系统会为不同的进程选择不同的虚拟地址,但是它仍然保留段的相对地址,因为地址无关代码使用段之间的相对地址来进行寻址,内存中的虚拟地址之间的差必须与文件中的虚拟地址之间的差相匹配。
(4)内存中任何段的虚拟地址与文件中对应的虚拟地址之间的差值对于任何一个可执行文件或共享对象来说是一个单一常量值。这个差值就是基地址,基地址的一个用途就是在动态链接期间重新定位程序。
(5)可执行文件或者共享目标文件的基地址是在执行过程中由虚拟内存加载地址、最大页面大小和程序可加载段的最低虚拟地址三个数值计算的。
(6)要计算基地址,首先要确定可加载段中 p_vaddr (p_vaddr存储着该段第一个字节在内存中的虚拟地址)最小的内存虚拟地址,之后把该内存虚拟地址缩小为与之最近的最大页面的整数倍即是基地址。
5、段权限-p_flags
1、按照 p_flags 将段设置为对应的权限。具体权限如下:
其中,所有在 PF_MASKPROC 中的bit位都是被保留用于与处理器相关的语义信息。
注:如果一个权限位被设置为 0,这种类型的段是不可访问的,故可能会产生的权限类型有:
6、段内容
一个段可能包括一到多个节区。对于不同的段来说,它的节的顺序以及所包含的节的个数有所不同。
(1)代码段
只包含只读的指令以及数据,通常来说,包含以下内容
(2)数据段
包含可写的数据以及以及指令,通常来说,包含以下内容
注:程序头部的 PT_DYNAMIC 类型(用于给出动态链接信息)的元素指向指向 .dynamic 节。其中,got 表和 plt 表包含与地址无关的代码相关信息。
对不同的段来说可能会有所重合,即不同的段包含相同的节
四、Section Header Table
作用:用于定位 ELF 文件中的每个节区的具体位置
格式:节头表是一个数组,每个数组的元素的类型是 ELF32_Shdr
,每一个元素都描述了一个节区的概要内容。
1、数据格式
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;
2、ELF32_Shdr中每个字节的说明
注:索引为零的节区头,具体形式
3、部分节头字段
(1)sh_type
注:SHT 是Section Header Table 的简写
(2)sh_flags
该字段中每一个比特位都可以给出其相应的标记信息,其定义了对应的节区的内容是否可以被修改、被执行等信息。如果一个标志位被设置,则该位取值为1,未定义的位都为0。
(3)sh_link 和 sh_info
不同的sh_type(节区类型)对应的sh_link(节区头部表索引链接)和sh_info(附加信息)的含义也不同。
五、Section
1、特点
- 每个节区都有对应的节头来描述它。但是反过来,节区头部并不一定会对应着一个节区。
- 每个节区在目标文件中是连续的,但是大小可能为 0。
- 任意两个节区不能重叠,即一个字节不能同时存在于两个节区中。
- 目标文件中可能会有闲置空间,各种头和节不一定会覆盖到目标文件中的所有字节,闲置区域的内容未指定。
2、一些特殊的节
有一些特殊的节可以支持调试,比如说 .debug 以及 .line 节;
支持程序控制的节有 .bss,.data, .data1, .rodata, .rodata1。
注:
- 以 “.” 开头的节区名称是系统保留的,当然应用程序也可以使用这些节区。为了避免与系统节区冲突,应用程序应该尽量使用没有前缀的节区名称。
- 目标文件格式允许定义不在上述列表中的节区,可以包含多个名字相同的节区。
- 保留给处理器体系结构的节区名称一般命名规则为:处理器体系结构名称简写+ 节区名称。其中,处理器名称应该与 e_machine 中使用的名称相同。例如 .FOO.psect 节区是 FOO 体系结构中的 psect 节区。
3、.strtab节区(String Table)
(1) 该节区描述默认的字符串表,包含了一系列的以 NULL 结尾的字符串。ELF 文件使用这些字符串来存储程序中的符号名,包括变量名和函数名。
(2)该节在运行的过程中不需要加载,只需要加载对应的子集 .dynstr 节。
(3)一般通过对字符串的首个字母在字符串表中的下标来索引字符串。
(4)字符串表的首尾字节都是NULL。
(5)一个节区头部的 sh_name (在Section Header Table表中)成员的值为其相应的节区头部字符串表节区的索引,此节区由 ELF 头的 e_shstrndx (在ELF Header表中)成员给出。
如下图给出了一个包含 25 个字节的字符串表,以及与不同索引相关的字符串。
其中包含的字符串有:
通过该表得出的包含的字符串可以得出:
- 字符串表索引可以引用节区中任意字节。(引用类name.、variable等等,其字节长度不等)
- 字符串可以出现多次。(出现了两次able)
- 可以存在对子字符串的引用。(在variable中引用了able)
- 同一个字符串可以被引用多次。(variable被用了两次)
- 字符串表中也可以存在未引用的字符串。(22 xx并未被引用)
4、.symtab节区 (Symbol Table)
Symbol Table 包含了一组 Symbol。这些 Symbol 在程序中,要么表示定义,要么表示引用,它们的作用是在编译和链接的过程中,进行定位或者重定位
(1)数据格式
其本质是一个数组,数组中的每一个元素都是一个结构体。
typedef struct {
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Half st_shndx;
} Elf32_Sym;
(2)每个字段含义
(3)st_value
情况一:若该符号对应着一个变量,那么表明该变量在内存中的偏移。此时我们可由这个值获取其文件偏移,具体过程如下:
a、获取该符号对应的 st_shndx(符号所在节在节区头部表中的下标),进而获取到相关的节区。
b、根据节区头元素可以获取节区的虚拟基地址和文件基地址。
c、value-内存基虚拟地址=文件偏移-文件基地址
情况二:若该符号对应着一个函数,那么表明该函数在文件中的起始地址。
(4)st_info
st_info 中包含符号类型和绑定信息
在 st_info 中,其低 4 位表示符号的类型,具体定义如下:
其高 4 位表示符号绑定的信息,这部分信息确定了符号的链接可见性以及其行为,具体的取值如下:
注:在每个符号表中,所有具有 STB_LOCAL 绑定的符号都优先于弱符号和全局符号。符号表节区中的 sh_info 项所对应的头部的成员包含第一个非局部符号的符号表索引。
(5)定位一个符号对应字符串的地址
具体步骤如下:
A、根据 Section Header Table 中符号节头中的 sh_link 获取该符号节中对应符号字符串节在 Section Header Table 中的下标。进而获取对应符号节的地址。
B、根据该符号的定义中的 st_name 获取该符号的偏移,即在对应符号节中的偏移。
C、根据上述两者就可以定位一个符号对应的字符串的地址了。