可执行链接格式(executable and linking format)最初是由 unix 系统实验室(unix system laboratories,usl)开发并发布的,作为应用程序二进制接口(application binary interface,abi)的一部分。工具接口标准(tool interface standards,tis)委员会将还在发展的 elf 标准选作为一种可移植的目标文件格式,可以在 32 位 intel 体系结构上的很多操作系统中使用。
目标文件有三种类型:
- 可重定位文件(relocatable file) (*.o) 包含适合于与其他目标文件链接来创建可执行文件或者共享目标文件的代码和数据。
- 可执行文件(executable file) (*.exe) 包含适合于执行的一个程序,此文件规定了exec() 如何创建一个程序的进程映像。
- 共享目标文件(shared object file) (*.so)包含可在两种上下文中链接的代码和数据。首先链接编辑器可以将它和其它可重定位文件和共享目标文件一起处理,
生成另外一个目标文件(比如:编译器和连接器 把*.o和*.so一起装配成一个*.exe文件)。其次,动态链接器(dynamic linker)可能将它与某个可执行文件以及其它共享目标一起组合,创建进程映像(比如:动态加载器把exe程序和*.so加载进内存执行)。
目标文件全部是程序的二进制表示,目的是直接在某种处理器上直接执行。
说了那么多,那么elf格式文件到底是什么样的呢?首先elf格式文件是一种二进制文件,其次它有好多种后缀,比如.o,.so,.exe后缀名的文件都是elf格式的文件,下面让我们来看看这种二进制文件的格式到底是怎么样的?
elf目标文件既要参与程序链接又要参与程序执行。出于方便性和效率考虑,目标文件格式提供了两种并行视图,分别反映了这些活动的不同需求。
上面这张图怎么看呢?首先左边部分,它是以链接视图来看待elf文件的,从左边可以看出,包含了一个elf头部,它描绘了整个文件的组织结构。它还包括很多节区(section)。这些节有的是系统定义好的,有些是用户在文件在通过.section命令自定义的,链接器会将多个输入目标文件中的相同的节合并。节区部分包含链接视图的大量信息:指令、数据、符号表、重定位信息等等。除此之外,还包含程序头部表(可选)和节区 头部表,程序头部表,告诉系统如何创建进程映像。用来构造进程映像的目标文件必须具有程序头部表,可重定位文件不需要这个表。而节区头部表(section heade table)包含了描述文件节区的信息,每个节区在表中都有一项,每一项给出诸如节区名称、节区大小这类信息。用于链接的目标文件必须包含节区头部表,其他目标文件可以有,也可以没有这个表。
需要注意地是:尽管图中显示的各个组成部分是有顺序的,实际上除了 elf 头部表以外,其他节区和段都没有规定的顺序。
右半图是以程序执行视图来看待的,与左边对应,多了一个段(segment)的概念,编译器在生成目标文件时,通常使用从零开始的相对地址,而在链接过程中,链接器从一个指定的地址开始,根据输入目标文件的顺序,以段(segment)为单位将它们拼装起来。其中每个段可以包括很多个节(section)。
下面一个一个来...
2.1 elf的数据类型定义
在具体介绍elf的格式之前,我们先来了解在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 | 无符号小整数 |
2.2 elf头部结构
从上一节可知,一个elf文件的开始部分为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; //程序入口的虚拟地址,若没有,可为0
elf32_off e_phoff; //程序头部表格(program header table)的偏移量(按字节计算),若没有,可为0
elf32_off e_shoff; //节区头部表格(section header table)的偏移量(按字节计算),若没有,可为0
elf32_word e_flags; //保存与文件相关的,特定于处理器的标志。标志名称采用 ef_machine_flag的格式。
elf32_half e_ehsize; //elf 头部的大小(以字节计算)。
elf32_half e_phentsize; //程序头部表格的表项大小(按字节计算)。
elf32_half e_phnum; //程序头部表格的表项数目。可以为 0。
elf32_half e_shentsize; //节区头部表格的表项大小(按字节计算)。
elf32_half e_shnum; //节区头部表格的表项数目。可以为 0。
elf32_half e_shstrndx; //节区头部表格中与节区名称字符串表相关的表项的索引。如果文件没有节区名称字符串表,此参数可以为 shn_undef。
}elf32_ehdr;
2.2.1 e_ident
其中需要注意地是e_ident是一个16字节的数组,这个数组按位置从左到右都是有特定含义,每个数组元素的下标在标准中还存在别称,如byte0的下标0别名为ei_mag0,具体如下:
名称 | 元素下标值 | 含义 |
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[]大小 |
e_ident[ei_mag0]~e_ident[ei_mag3]即e_ident[0]~e_ident[3]被称为魔数(magic number),其值一般为0x7f,'e','l','f'。
e_ident[ei_class](即e_ident[4])识别目标文件运行在目标机器的类别,取值可为三种值:elfclassnone(0)非法类别;elfclass32(1)32位目标;elfclass64(2)64位目标。
e_ident[ei_data](即e_ident[5]):给出处理器特定数据的数据编码方式。即大端还是小端方式。取值可为3种:elfdatanone(0)非法数据编码;elfdata2lsb(1)高位在前;elfdata2msb(2)低位在前。
其它数组元素就不作介绍了。
2.2.2 e_type
e_type表示elf文件的类型,如下定义:
名称 | 取值 | 含义 |
et_none | 0 | 未知目标文件格式 |
et_rel | 1 | 可重定位文件 |
et_exec | 2 | 可执行文件 |
et_dyn | 3 | 共享目标文件 |
et_core | 4 | core 文件(转储格式) |
et_loproc | 0xff00 | 特定处理器文件 |
et_hiproc | 0xffff | 特定处理器文件 |
et_loproc~et_hiproc | 0xff00~0xffff | 特定处理器文件 |
2.2.3 e_machine
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 |
others | 9~ | 预留 |
2.2.4 其它
其它的已在结构体注释中有说明,这里不再重复.
2.3 节区(sections)
节区中包含目标文件中的所有信息,除了:elf 头部、程序头部表格、节区头部表格。节区满足以下条件:
(1). 目标文件中的每个节区都有对应的节区头部描述它,反过来,有节区头部不意味着有节区。
(2). 每个节区占用文件中一个连续字节区域(这个区域可能长度为 0)。
(3). 文件中的节区不能重叠,不允许一个字节存在于两个节区中的情况发生。
(4). 目标文件中可能包含非活动空间(inactive space)。这些区域不属于任何头部和节区,其内容未指定。
2.3.1 节区头部表格
elf 头部中,e_shoff 成员给出从文件头到节区头部表格的偏移字节数;e_shnum给出表格中条目数目;e_shentsize 给出每个项目的字节数。从这些信息中可以确切地定
位节区的具体位置、长度。
从之前的描述中可知,每一项节区在节区头部表格中都存在着一项元素与它对应,因此可知,这个节区头部表格为一连续的空间,每一项元素为一结构体,那么这个结构体的定义如下:
typedef struct{
elf32_word sh_name; //节区名,是节区头部字符串表节区(section header string table section)的索引。名字是一个 null 结尾的字符串。
elf32_word sh_type; //为节区类型
elf32_word sh_flags; //节区标志
elf32_addr sh_addr; //如果节区将出现在进程的内存映像中,此成员给出节区的第一个字节应处的位置。否则,此字段为 0。
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.3.1.1 sh_type
sh_type的取值如下:
名称 | 取值 | 说明 |
sht_null | 0 | 此值标志节区头部是非活动的,没有对应的节区。此节区头部 中的其他成员取值无意义。 |
sht_progbits | 1 | 此节区包含程序定义的信息,其格式和含义都由程序来解释。 |
sht_symtab | 2 | 此节区包含一个符号表。目前目标文件对每种类型的节区都只能包含一个,不过这个限制将来可能发生变化。 一般,sht_symtab 节区提供用于链接编辑(指 ld 而言)的符号,尽管也可用来实现动态链接。 |
sht_strtab | 3 | 此节区包含字符串表。目标文件可能包含多个字符串表节区。 |
sht_rela | 4 | 此节区包含重定位表项,其中可能会有补齐内容(addend),例 如 32 位目标文件中的 elf32_rela 类型。目标文件可能拥有多 个重定位节区。 |
sht_hash | 5 | 此节区包含符号哈希表。所有参与动态链接的目标都必须包含一个符号哈希表。目前,一个目标文件只能包含一个哈希表, 不过此限制将来可能会解除。 |
sht_dynamic | 6 | 此节区包含动态链接的信息。目前一个目标文件中只能包含一个动态节区,将来可能会取消这一限制。 |
sht_note | 7 | 此节区包含以某种方式来标记文件的信息。 |
sht_nobits | 8 | 这 种 类 型 的 节 区 不 占 用 文 件 中 的 空 间 , 其 他 方 面 和sht_progbits 相似。尽管此节区不包含任何字节,成员 sh_offset 中还是会包含概念性的文件偏移 |
sht_rel | 9 | 此节区包含重定位表项,其中没有补齐(addends),例如 32 位目标文件中的 elf32_rel 类型。目标文件中可以拥有多个重定 位节区。 |
sht_shlib | 10 | 此节区被保留,不过其语义是未规定的。包含此类型节区的程 序与 abi 不兼容。 |
sht_dynsym | 11 | 作为一个完整的符号表,它可能包含很多对动态链接而言不必 要的符号。因此,目标文件也可以包含一个 sht_dynsym 节 区,其中保存动态链接符号的一个最小集合,以节省空间。 |
sht_loproc | 0x70000000 | 这一段(包括两个边界),是保留给处理器专用语义的。 |
sht_hiproc | ox7fffffff | 这一段(包括两个边界),是保留给处理器专用语义的。 |
sht_louser | 0x80000000 | 此值给出保留给应用程序的索引下界。 |
sht_hiuser | 0x8fffffff | 此值给出保留给应用程序的索引上界。 |
2.3.1.2 sh_flag
sh_flag标志着此节区是否可以修改,是否可以执行,如下定义:
名称 | 取值 | 含义 |
shf_write | 0x1 | 节区包含进程执行过程中将可写的数据。 |
shf_alloc | 0x2 | 此节区在进程执行过程中占用内存。某些控制节区并不出现于目标文件的内存映像中,对于那些节区,此位应设置为 0。 |
shf_execinstr | 0x4 | 节区包含可执行的机器指令。 |
shf_maskproc | 0xf0000000 | 所有包含于此掩码中的四位都用于处理器专用的语义。 |
2.3.1.3 sh_link 和 sh_info 字段
从2.3.1节中可知,sh_link和sh_info字段的具体含义依赖于sh_type的值:
sh_type | sh_link | sh_info |
sht_dynamic | 此节区中条目所用到的字符串表格的节区头部索引 | 0 |
sht_hash | 此哈希表所适用的符号表的节区头部索引 | 0 |
sht_rel sht_rela |
相关符号表的节区头部索引 | 重定位所适用的节区的节区头部索引 |
sht_symtab sht_dynsym |
相关联的字符串表的节区头部索引 | 最后一个局部符号(绑定 stb_local)的符号表索引值加一 |
其它 | shn_undef | 0 |
2.3.2 特殊节区
有些节区是系统预订的,一般以点开头号,因此,我们有必要了解一些常用到的系统节区。
名称 | 类型 | 属性 | 含义 |
.bss | sht_nobits | shf_alloc shf_write |
包含将出现在程序的内存映像中的为初始化数据。根据定义,当程序开始执行,系统将把这些数据初始化为 0。此节区不占用文件空间。 |
.comment | sht_progbits | (无) | 包含版本控制信息。 |
.data | sht_progbits | shf_alloc shf_write |
这些节区包含初始化了的数据,将出现在程序的内存映像中。 |
.data1 | sht_progbits | shf_alloc shf_write |
这些节区包含初始化了的数据,将出现在程序的内存映像中。 |
.debug | sht_progbits | (无) | 此节区包含用于符号调试的信息。 |
.dynamic | sht_dynamic | 此节区包含动态链接信息。节区的属性将包含 shf_alloc 位。是否 shf_write 位被设置取决于处理器。 | |
.dynstr | sht_strtab | shf_alloc | 此节区包含用于动态链接的字符串,大多数情况下这些字符串代表了与符号表项相关的名称。 |
.dynsym | sht_dynsym | shf_alloc | 此节区包含了动态链接符号表。 |
.fini | sht_progbits | shf_alloc shf_execinstr |
此节区包含了可执行的指令,是进程终止代码的一部分。程序正常退出时,系统将安排执行这里的代码。 |
.got | sht_progbits | 此节区包含全局偏移表。 | |
.hash | sht_hash | shf_alloc | 此节区包含了一个符号哈希表。 |
.init | sht_progbits | shf_alloc shf_execinstr |
此节区包含了可执行指令,是进程初始化代码的一部分。当程序开始执行时,系统要在开始调用主程序入口之前(通常指 c 语言的 main 函数)执行这些代码。 |
.interp | sht_progbits | 此节区包含程序解释器的路径名。如果程序包含一个可加载的段,段中包含此节区,那么节区的属性将包含 shf_alloc 位,否则该位为 0。 | |
.line | sht_progbits | (无) | 此节区包含符号调试的行号信息,其中描述了源程序与机器指令之间的对应关系。其内容是未定义的。 |
.note | sht_note | (无) | 此节区中包含注释信息,有独立的格式。 |
.plt | sht_progbits | 此节区包含过程链接表(procedure linkage table)。 | |
.relname .relaname |
sht_rel sht_rela |
这些节区中包含了重定位信息。如果文件中包含可加载的段,段中有重定位内容,节区的属性将包含 shf_alloc 位,否则该位置 0。传统上 name 根据重定位所适用的节区给定。例如 .text 节区的重定位节区名字将是:.rel.text 或者 .rela.text。 | |
.rodata .rodata1 |
sht_progbits | shf_alloc | 这些节区包含只读数据,这些数据通常参与进程映像的不可写段。 |
.shstrtab | sht_strtab | 此节区包含节区名称。 | |
.strtab | sht_strtab | 此节区包含字符串,通常是代表与符号表项相关的名称。如果文件拥有一个可加载的段,段中包含符号串表,节区的属性将包含shf_alloc 位,否则该位为 0。 | |
.symtab | sht_symtab | 此节区包含一个符号表。如果文件中包含一个可加载的段,并且该段中包含符号表,那么节区的属性中包含shf_alloc 位,否则该位置为 0。 | |
.text | sht_progbits | shf_alloc shf_execinstr |
此节区包含程序的可执行指令。 |
2.4 字符串表(string table)
首先要知道,字符串表它本身就是一个节区,从第二章描述中可知,每一个节区都存在一个节区头部表项与之对应,所以字符串表这个节区也存在一个节区头部表项对应,而在elf文件头部结构中存在一个成员e_shstrndx给出这个节区头部表项的索引位置。因此可以通过
shstrab = (rt_uint8_t *)module_ptr shdr[elf_module->e_shstrndx].sh_offset;
来得到字符串表的起始位置。
字符串表节区包含以 null(ascii 码 0)结尾的字符序列,通常称为字符串。elf目标文件通常使用字符串来表示符号和节区名称。对字符串的引用通常以字符串在字符
串表中的下标给出。
一般,第一个字节(索引为 0)定义为一个空字符串。类似的,字符串表的最后一个字节也定义为 null,以确保所有的字符串都以 null 结尾。索引为 0 的字符串在
不同的上下文中可以表示无名或者名字为 null 的字符串。
允许存在空的字符串表节区,其节区头部的 sh_size 成员应该为 0。对空的字符串表而言,非 0 的索引值是非法的。
例如:对于各个节区而言,节区头部的 sh_name 成员包含其对应的节区头部字符串表节区的索引,此节区由 elf 头的 e_shstrndx 成员给出。下图给出了包含 25 个字节
的一个字符串表,以及与不同索引相关的字符串。
那么上面字符串表包含以下字符串:
索引 | 字符串 |
0 | (无) |
1 | name. |
7 | variable |
11 | able |
16 | able |
24 | (空字符串) |
2.5 符号表(symbol table)
首先,字号表同样本身是一节区,也存在一对应节区头部表项。目标文件的符号表中包含用来定位、重定位程序中符号定义和引用的信息。符号表索引是对此数组的索引。索引 0 表示表中的第一表项,同时也作为未定义符号的索引。符号表是由一个个符号元素组成,每个元素的数据结构如下定义:
typedef struct {
elf32_word st_name; //名称,索引到字符串表
elf32_addr st_value; //给出相关联的符号的取值。依赖于具体的上下文.
elf32_word st_size; //相关的尺寸大小
unsigned char st_info; //给出符号的类型和绑定属性.
unsigned char st_other; //该成员当前包含 0,其含义没有定义。
elf32_half st_shndx; //给出相关的节区头部表索引。某些索引具有特殊含义。
} elf32_sym;
2.5.1 st_info
st_info 中包含符号类型和绑定信息,操纵方式如:
#define elf32_st_bind(i) ((i)>>4)
#define elf32_st_type(i) ((i)&0xf)
#define elf32_st_info(b, t) (((b)<<4) ((t)&0xf))
2.5.1.1 st_info 的高四位(elf32_st_bind(i))
表示符号绑定,用于确定链接可见性和行为。具体的绑定类型如:
名称 | 取值 | 说明 |
stb_local | 0 | 局部符号在包含该符号定义的目标文件以外不可见。相同名称的局部符号可以存在于多个文件中,互不影响。 |
stb_global | 1 | 全局符号对所有将组合的目标文件都是可见的。一个文件中对某个全局符号的定义将满足另一个文件对相同全局符号的未定义引用。 |
stb_weak | 2 | 弱符号与全局符号类似,不过他们的定义优先级比较低。 |
stb_loproc | 13 | 处于这个范围的取值是保留给处理器专用语义的。 |
stb_hiproc | 15 | 处于这个范围的取值是保留给处理器专用语义的。 |
全局符号与弱符号之间的区别主要有两点:
(1) 当 链 接 编 辑 器 组 合 若 干 可 重 定 位 的 目 标 文 件 时 , 不 允 许 对 同 名 的stb_global 符号给出多个定义。另一方面如果一个已定义的全局符号已经存在,出现一个同名的弱符号并不会产生错误。链接编辑器尽关心全局符号,忽略弱符号。类似地,如果一个公共符号(符号的 st_shndx 中包含 shn_common),那么具有相同名称的弱符号出现也不会导致错误。链接编辑器会采纳公共定义,而忽略弱定义。
(2). 当链接编辑器搜索归档库(archive libraries)时,会提取那些包含未定义全局符号的档案成员。成员的定义可以是全局符号,也可以是弱符号。连接编辑器不会提取档案成员来满足未定义的弱符号。未能解析的弱符号取值为 0。
在每个符号表中,所有具有 stb_local 绑定的符号都优先于弱符号和全局符号。符号表节区中的 sh_info 头部成员包含第一个非局部符号的符号表索引。
2.5.1.2 st_info的低四位elf32_st_type(i)
定义如下:
名称 | 取值 | 说明 |
stt_notype | 0 | 符号的类型没有指定 |
stt_object | 1 | 符号与某个数据对象相关,比如一个变量、数组等等 |
stt_func | 2 | 符号与某个函数或者其他可执行代码相关 |
stt_section | 3 | 符号与某个节区相关。这种类型的符号表项主要用于重定位,通常具有 stb_local 绑定。 |
stt_file | 4 | 传统上,符号的名称给出了与目标文件相关的源文件的名称。文件符号具有 stb_local 绑定,其节区索引是shn_abs,并且它优先于文件的其他 stb_local 符号(如果有的话) |
stt_loproc~ stt_hiproc |
13~15 | 此范围的符号类型值保留给处理器专用语义用途。 |
在共享目标文件中的函数符号(类型为 stt_func)具有特别的重要性。当其他目标文件引用了来自某个共享目标中的函数时,链接编辑器自动为所引用的符号创建过
程链接表项。类型不是 stt_func 的共享目标符号不会自动通过过程链接表进行引用。
如果一个符号的取值引用了某个节区中的特定位置,那么它的节区索引成员(st_shndx)包含了其在节区头部表中的索引。当节区在重定位过程中被移动时,符号的取值也会随之变化,对符号的引用始终会“指向”程序中的相同位置。
2.5.2 st_shndx
如前面所述,st_shndx给出相关的节区头部表索引。但其值也存在一些特殊值,具有某些特殊的含义:
- shn_abs:符号具有绝对取值,不会因为重定位而发生变化。
- shn_common: 符号标注了一个尚未分配的公共块。符号的取值给出了对齐约束,与节区的 sh_addralign成员类似。就是说,链接编辑器将为符号分配存储空间,地址位于 st_value 的倍数处。符号的大小给出了所需要的字节数。
- shn_undef: 此节区表索引值意味着符号没有定义。当链接编辑器将此目标文件与其他定义了该符号的目标文件进行组合时,此文件中对该符号的引用将被链接到实际定义的位置。
2.5.3 st_value
不同的目标文件类型中符号表项对 st_value 成员具有不同的解释:
(1). 在可重定位文件中,st_value 中遵从了节区索引为 shn_common 的符号的对齐约束。
(2). 在可重定位的文件中,st_value 中包含已定义符号的节区偏移。就是说,st_value 是从 st_shndx 所标识的节区头部开始计算,到符号位置的偏移。
(3). 在可执行和共享目标文件中,st_value 包含一个虚地址。为了使得这些文件的符号对动态链接器更有用,节区偏移(针对文 件的解释)让位于虚拟地址(针对内存的解释),因为这时与节区号无关。
尽管符号表取值在不同的目标文件中具有相似的含义,适当的程序可以采取高效的数据访问方式。
2.6 重定位信息
重定位是将符号引用与符号定义进行连接的过程。例如,当程序调用了一个函数时,相关的调用指令必须把控制传输到适当的目标执行地址。
2.6.1 重定位表项
可重定位文件必须包含如何修改其节区内容的信息,从而允许可执行文件和共享目标文件保存进程的程序映像的正确信息。重定位表项就是这样一些数据。
可重定位表项的数据结构如下定义:
typedef struct {
elf32_addr r_offset; //给出了重定位动作所适用的位置
elf32_word r_info; //给出要进行重定位的符号表索引,以及将实施的重定位类型.
} elf32_rel;
typedef struct {
elf32_addr r_offset;
elf32_word r_info;
elf32_word r_addend; //给出一个常量补齐,用来计算将被填充到可重定位字段的数值。
} elf32_rela;
重定位节区会引用两个其它节区:符号表、要修改的节区。节区头部的 sh_info 和sh_link 成员给出这些关系。不同目标文件的重定位表项对 r_offset 成员具有略微不同的解释。
r_info通常分为高8位和低8位,分别表示不同的含义:
#define elf32_r_sym(i) ((i)>>8)
#define elf32_r_type(i) ((unsigned char)(i))
#define elf32_r_info(s, t) (((s)<<8) (unsigned char)(t))
高8位用作要进行重定位的符号表索引,通过它可以得出一个符号表项,而低8位表示将实施的重定位类型,它是和处理器相关的。
2.6.2 elf32_r_type(i)
重定位表项描述如何修改后面的指令和数据字段。一般,共享目标文件在创建时,其基本虚拟地址是 0,不过执行地址将随着动态加载而发生变化。
2.7 程序头部
以上说了那么多的有关节区的相关内容,那么现在来讨论一个程序头部表。其实节区头部表是以elf资源的角度来看待elf文件的,即这个elf文件到底存在哪些资源,以及这些资源之间的关联关系,而程序头部表,则以程序运行来看elf文件的,即要运行这个elf文件,需要将哪些东西载入到内存镜像。
程序头部是一个表,它的起始地址在elf头部结构中的e_phoff成员指定,数量由e_phnum表示,每个程序头部表项的大小由e_phentsize指出。
可执行文件或者共享目标文件的程序头部是一个结构数组,每个结构描述了一个段或者系统准备程序执行所必需的其它信息。目标文件的“段”包含一个或者多个“节区”,也就是“段内容(segment contents)”。程序头部仅对于可执行文件和共享目标文件有意义。
下面来看程序头号部表项的数据结构:
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.7.1 p_type
名称 | 取值 | 说明 |
pt_null | 0 | 此数组元素未用。结构中其他成员都是未定义的。 |
pt_load | 1 | 此数组元素给出一个可加载的段,段的大小由 p_filesz 和 p_memsz描述。文件中的字节被映射到内存段开始处。如果 p_memsz 大于p_filesz,“剩余”的字节要清零。p_filesz 不能大于 p_memsz。可加载的段在程序头部表格中根据 p_vaddr 成员按升序排列。 |
pt_dynamic | 2 | 数组元素给出动态链接信息。 |
pt_interp | 3 | 数组元素给出一个 null 结尾的字符串的位置和长度,该字符串将被当作解释器调用。这种段类型仅对与可执行文件有意义(尽管也可能在共享目标文件上发生)。在一个文件中不能出现一次以上。如果存在这种类型的段,它必须在所有可加载段项目的前面。 |
pt_note | 4 | 此数组元素给出附加信息的位置和大小。 |
pt_shlib | 5 | 此段类型被保留,不过语义未指定。包含这种类型的段的程序与 abi不符。 |
pt_phdr | 6 | 此类型的数组元素如果存在,则给出了程序头部表自身的大小和位置,既包括在文件中也包括在内存中的信息。此类型的段在文件中不能出现一次以上。并且只有程序头部表是程序的内存映像的一部分时才起作用。如果存在此类型段,则必须在所有可加载段项目的前面。 |
pt_loproc~ pt_hiproc |
0x70000000~ 0x7fffffff |
此范围的类型保留给处理器专用语义。 |