数据类型
首先在解析之前, 必须对数据类型格式声明一下
名称 | 大小 | 说明 |
---|---|---|
Elf32_Addr | 4 | 无符号程序地址 |
Elf32_Half | 2 | 无符号中等整数 |
Elf32_Off | 4 | 无符号文件偏移 |
Elf32_SWord | 4 | 有符号大整数 |
Elf32_Word | 4 | 无符号大整数 |
unsigned char | 1 | 无符号小整数 |
整体结构
概述
- ELF头部(ELF_Header): 每个ELF文件都必须存在一个ELF_Header,这里存放了很多重要的信息用来描述整个文件的组织,如: 版本信息,入口信息,偏移信息等。程序执行也必须依靠其提供的信息。
- 程序头部表(Program_Header_Table): 可选的一个表,用于告诉系统如何在内存中创建映像,在图中也可以看出来,有程序头部表才有段,有段就必须有程序头部表。其中存放各个段的基本信息(包括地址指针)。
- 节区头部表(Section_Header_Table): 类似与Program_Header_Table,但与其相对应的是节区(Section)。
- 节区(Section): 将文件分成一个个节区,每个节区都有其对应的功能,如符号表,哈希表等。
- 段(Segment): 嗯…就是将文件分成一段一段映射到内存中。段中通常包括一个或多个节区
注:每个节区都应该是前后相连的,且不可有重叠。即在一个地址上的字节只能属于一个节区
详解
ELF_Header
以下是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 这是一个数组,其每个字节又都有所代表的含义:
- EI_MAG0 - EI_MAG3 文件标识就是平时所说的ELF头,即 7F 45 4C 46(ELF)
- EI_CLASS 文件类,其实代表的是32位/64位程序
代表 含义 ELFCLASS32 32位程序 ELFCLASS64 64位程序 - EI_DATA 数据编码,一般都是01[td]
代表 含义 ELFDATA2LSB 高位在前 ELFDATA2MSB 低位在前 - EI_VERSION 文件版本,固定值01 EV_CURRENT
- EI_PAD 呃…就是一堆全是00的用来补全大小的数组
- EI_NIDENT 说是e_ident数组的大小,但我看了好几个so都是00
-
e_type 标识文件类型
取值 | 代表 | 含义 |
---|---|---|
00 | ET_NONE | 未知文件类型格式 |
01 | ET_REL | 可重定位文件 |
02 | ET_EXEC | 可执行文件 |
03 | ET_DYN | 共享目标文件(SO) |
04 | … | … |
- e_machine 声明ABI
取值 | 代表 | 含义 |
---|---|---|
01 | … | … |
03 | EM_386 | x86 |
04 | … | … |
28hEM_ARM | arm | |
29h | … | … |
- e_version 跟ident[]里的EI_VERSION一样,为01
- e_entry 可执行程序入口点地址。
- e_phoff Program Header Offset,程序头部表索引地址,没有则为0。
- e.shoff Section Header Offset,节区表索引地址,没有则为0。
- e_flags “保存与文件相关的,特定于处理器的标志。”不知道有什么用,看了几个arm都是00 00 00 05,x86都是0。
- e_ehsize ELF_Header Size,嗯…ELF头部的大小
- e_phentsize 程序头部表的单个表项的大小
- e_phnum 程序头部表的表项数
- e_shentsize 节区表的单个表项的大小
- e_shnum 节区表的表项数
- e_shstrndx String Table Index,在节区表中有一个存储各节区名称的节区(通常是最后一个),这里表示名称表在第几个节区。
Program Header
在ELF_Header中,我们可以得到Program Header的索引地址(e_phoff)、段数量(e_phnum)、表项大小(e_phentsize)。
然后我们来看一下Program Header中表项的结构定义:
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_flage;
Elf32_Word p_align;
} Elf32_phdr;
- p_type 声明此段的作用类型
取值 | 代表 | 含义 |
---|---|---|
00 | PT_NULL | 此数组元素未用。结构中其他成员都是未定义的。 |
01 | PT_LOAD | 此数组元素给出一个可加载的段,段的大小由 p_filesz 和 p_memsz 描述。文件中的字节被映射到内存段开始处。如果 p_memsz 大于 p_filesz,“剩余”的字节要清零。p_filesz 不能大于 p_memsz。可加载的段在程序头部表格中根据 p_vaddr 成员按升序排列。 |
02 | PT_DYNAMIC | 数组元素给出动态链接信息。 |
03 | PT_INTERP | 数组元素给出一个 NULL 结尾的字符串的位置和长度,该字符串将被当作解释器调用。这种段类型仅对与可执行文件有意义(尽管也可能在共享目标文件上发生)。在一个文件中不能出现一次以上。如果存在这种类型的段,它必须在所有可加载段项目的前面。 |
04 | PT_NOTE | 此数组元素给出附加信息的位置和大小。 |
05 | PT_SHLIB | 此段类型被保留,不过语义未指定。包含这种类型的段的程序与 ABI不符。 |
06 | PT_PHDR | 此类型的数组元素如果存在,则给出了程序头部表自身的大小和位置,既包括在文件中也包括在内存中的信息。此类型的段在文件中不能出现一次以上。并且只有程序头部表是程序的内存映像的一部分时才起作用。如果存在此类型段,则必须在所有可加载段项目的前面。 |
0x70000000 | PT_LOPROC | 此范围的类型保留给处理器专用语义。 |
0x7fffffff | PT_HIPROC | 此范围的类型保留给处理器专用语义。 |
… | … | … |
还有一些编译器或者处理器标识的段类型,有待补充。
- p_offset 段相对于文件的索引地址
- p_vaddr 段在内存中的虚拟地址
- p_paddr 段的物理地址
- p_filesz 段在文件中所占的长度
- p_memsz 段在内存中所占的长度
- p_flage 段相关标志(read、write、exec)
- p_align 字节对其,p_vaddr 和 p_offset 对 p_align 取模后应该相等。
Section Header Table
与Progarm Header类似,我们同样可以从ELF Header中得到索引地址(e_shoff)、节区数量(e_shnum)、表项大小(e_shentsize),还可以由**名称节区索引(e_shstrndx)**得到各节区的名称。
Section Header Table 表项结构定义:
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;
-
sh_name 节区名称,此处是一个在名称节区的索引。
-
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 | 0X7FFFFFFF | 这一段(包括两个边界),是保留给处理器专用语义的。 |
SHT_LOUSER | 0X80000000 | 此值给出保留给应用程序的索引下界。 |
SHT_HIUSER | 0X8FFFFFFF | 此值给出保留给应用程序的索引上界。 |
-
sh_flags 同Program Header的p_flags
-
sh_addr 节区索引地址
-
sh_offset 节区相对于文件的偏移地址
-
sh_size 节区的大小
-
sh_link 此成员给出节区头部表索引链接。
-
sh_info 此成员给出附加信息。
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 |
- sh_addralign
某些节区带有地址对齐约束。例如,如果一个节区保存一个doubleword,那么系统必须保证整个节区能够按双字对齐。sh_addr
对sh_addralign 取模,结果必须为 0。目前仅允许取值为 0 和 2的幂次数。数值 0 和 1 表示节区没有对齐约束。
sh_entsize
某些节区中包含固定大小的项目,如符号表。对于这类节区,此成员给出每个表项的长度字节数。
如果节区中并不包含固定长度表项的表格,此成员取值为 0。
一般来说,节区索引为0,即第一个节区一般都是SHN_UNDEF,其各项值都固定为0
划重点:
1. 以“.”开头的节区名称是系统保留的。应用程序可以使用没有前缀的节区名称,以避免与系统节区冲突。
2. 目标文件中也可以包含多个名字相同的节区。
3. 保留给处理器体系结构的节区名称一般构成为:处理器体系结构名称简写 + 节区名称。
4. 处理器名称应该与 e_machine 中使用的名称相同。例如 .FOO.psect 街区是由 FOO 体系结构定义的 psect 节区。
部分系统节区作用详解
字符串表
在一个ELF文件中通常拥有一个或以上的字符串表,即类型为 SHT_STRTAB 的节区,如: ELF Header 中 e_shstrndx 索引的节区名称表(.shstrtab)、符号名称表(.dynstr) 等。
对于字符串的定义,是以NULL(\0)开头,以NULL结尾。
以一个.shstrtab表的内容为例:
00 2E 73 68 73 74 72 74 61 62 00 2E 69 6E 74 65 72 70 00 2E 64 79 6E 73 79 6D 00 …
从这里可以得到3个字符串即:
- 2E 73 68 73 74 72 74 61 62 (.shstrtab);
- 2E 69 6E 74 65 72 70 (.interp);
- 2E 64 79 6E 73 79 6D (.dynsym);
假如索引为0,那么字符串的内容就是 2E 73 68 73 74 72 74 61 62 (.shstrtab)
符号表
符号: 指函数或者数据对象等。
既然叫做表,那么也分为一个一个表项,其表项也有自己的结构定义:
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;
- st_name 符号名称,给出的是一个在符号名称表(.dynstr)中的索引
- st_value 一般都是函数地址,或者是一个常量值
- st_size 从 st_value 地址开始,共占的长度大小
- st_info 用于标示此符号的属性,占一个字节(2个字),两个标示位,第一个标示位(低四位)标志作用域,第二个标示位(高四位)标示符号类型
取值 | 代表 | 含义 |
---|---|---|
0 | STB_LOCAL | 局部符号在包含该符号定义的目标文件以外不可见。相同名称的局部符号可以存在于多个文件中,互不影响。 |
1 | STB_GLOBAL | 全局符号对所有将组合的目标文件都是可见的。一个文件中对某个全局符号的定义将满足另一个文件对相同全局符号的未定义引用。 |
2 | STB_WEA | 弱符号与全局符号类似,不过他们的定义优先级比较低。 |
(以我的理解…就是LOACL是局部变量,GLOBAL 和 WEAK 是全局量, 两者的差别在于是不是常量?猜的。)
取值 | 代表 | 含义 |
---|---|---|
1 | STT_OBJECT | 符号与某个数据对象相关,比如一个变量、数组等等 |
2 | STT_FUNC | 符号与某个函数或者其他可执行代码相关 |
3 | STT_SECTION | 符号与某个节区相关。这种类型的符号表项主要用于重定位,通常具有 STB_LOCAL 绑定。 |
4 | STT_FILE | 传统上,符号的名称给出了与目标文件相关的源文件的名称。文件符号具有 STB_LOCAL 绑定,其节区索引是SHN_ABS,并且它优先于文件的其他 STB_LOCAL 符号(如果有的话) |
举个例子: 比如此处数值为0x12,那么他就对应着 STB_GLOBAL 和 STT_FUNC。从编程的方面来说,就是一个 public 的函数。
5. st_other 固定值为0。
6. st_shndx
每个符号表项都以和其他节区间的关系的方式给出定义。此成员给出相关的节区头部表索引。某些索引具有特殊含义。
唔…我也不知道有什么用…
代码段
代码段就是存放指令的节区(.text),符号表中的 st_value 指向代码段中具体的函数地址,以其地址的指令为函数开头。
全局偏移表
指.got节区,.got内的值均为 Elf32_Addr。其为全局符号提供偏移地址(指向过程链接表)。
过程链接表
.plt节区,其每个表项都是一段代码,作用是跳转至真实的函数地址
哈希表
指.hash节区。哈希表的结构:
其中nchain为符号表表项数,nchain 和 nbucket 是 chain 和 bucket 的数量。
数据段
.data、.bss、.rodata都属于数据段。其中,
- .data 存放已初始化的全局变量、常量。
- .bss 存放未初始化的全局变量,所以此段数据均为0,仅作占位。
- .rodata 是只读数据段,此段的数据不可修改,存放常量。
.init_array .fini_array
程序运行时,执行.init_array中的指令。
程序退出时,执行.fini_array中的指令。
暂时就写这些吧。写的不清楚的麻烦自己百度下…