ELF文件格式
ELF文件类型 | 说明 | 实例 |
---|---|---|
Relocatable File 可重定位文件 | 未链接之前的ELF文件,可用于链接可执行文件或静态链接库 | Linux下的".o"文件,Windows下".obj"文件 |
Executable File 可执行文件 | 最终的可执行程序 | 如Linux下"/bin"目录下文件,Windows的".exe"文件 |
Shared Objected File 共享目标文件 | 一种是可用于静态链接文件,另一种是程序运行中被动态链接文件 | 如Linux下的".a"和".so"文件,Windows的dll文件 |
Core Dump File 核心转储文件 | 进程意外终止时,保存进程地址空间的内容以及其他信息 | Linux下的core dump |
前言
在早期的UNIX中,可执行文件格式为a.out格式,由于其格式简单,随着共享库概念的出现被COFF格式取代,后来Linux和Windows基于COFF格式,分别制定了ELF和PE格式,我们日常使用的".exe"文件".lib",".dll"文件就属于PE文件的一种;Linux平台下的可执行文件,中间目标文件".o"以及静态库".a"和动态链接库".so"文件属于ELF文件。
本节以中间目标文件(Relocatable File in ELF)来对ELF类型的文件结构进行讲解。目标文件编译器将源代码经过预编译,编译,汇编后得到的第一层ELF文件,目标文件经过链接后才能成为真正在Linux下运行的可执行文件,这一类型会在后续blog中讲解。
ELF文件的结构
假设有一个目标文件为SimpleSection.o,该ELF文件的总体结构如下图所示:
上图中一层层的字段(定义:每种字段存储不同类型的内容)就是ELF结构的内容层次了,在目标文件的开头为一个长度为64(0x40)字节的ELF头——ELF Header,只要分析ELF头内存储的信息,可以得出段表——Section Header table(在图的最顶层的那个段)在整个目标文件中的偏移。段表是一个元素为"Elf64_Shdr"结构体类型的数组,它的元素数量正好是图中那些字段的数量,她的每个元素存储了中间一些字段如".text",".symtab"等字段的信息,这些信息包括字段名,大小等等属性。
概括的来说,通过读取ELF头,可以得到段表,然后通过读取段表中各个字段元素,就可以得出各个字段的信息了。那么读到这里,ELF结构轮廓已然清晰,接下来就是分析ELF文件各个字段的具体用途,以及某些字段是具体如何关联才使得链接器能够完全理解这个文件。
ELF文件中各个字段的用途
- .text字段:用于保存程序中的代码片段
- .data字段:用于保存已经初始化的全局变量和局部变量
- .bss字段:用于保存未初始化的全局变量和局部变量
- .rodata:顾名思义,保存只读的变量
- .comment:保存编译器版本信息
- .symtab:符号表,各个目标文件链接的接口
- .strtab:字符串表,保存符号的名字,因为各个字符串大小不一,所以统一把所有字符串放到这个段里,后续其他段通过某个符号在字符串标中的偏移可以取到符号
- .rela.text:因为程序声明使用了未在程序内部定义的函数或者变量,所以需要等到链接时(定义在别的目标文件或者库里)对这个符号的地址进行重新定位,不然会引用到错误的地址
- .shstrtab:和strtab类似,不过保存是段名,也就是说里面保存的字符串是所有段的名字
- Section Header Table:段表,保存了所有段的信息,本身通过Elf头找到,可以解析出所有段的位置
ELF头(ELF Header)
像bmp、exe等文件一样,ELF头包含整个文件的控制结构。它的定义如下,可在/usr/include/elf.h中可以找到ELF头结构的定义:
e_ident的16个字节标识是个ELF文件(7F+’E’+’L’+’F’)。
e_type表示文件类型,2表示可执行文件。
e_machine说明机器类别,3表示386机器,8表示MIPS机器。
e_entry给出进程开始的虚地址,即系统将控制转移的位置。
e_phoff指出program header table的文件偏移。
e_phentsize表示一个program header表中的入口的长度(字节数表示)。
e_phnum给出program header表中的入口数目。类似的。
e_shoff,e_shentsize,e_shnum 分别表示section header表的文件偏移,表中每个入口的的字节数和入口数目。
e_flags给出与处理器相关的标志。
e_ehsize给出ELF文件头的长度(字节数表示)。
e_shstrndx表示section名表的位置,指出在section header表中的索引。
段表(Section Header table)
目标文件的段表可以定位所有的段,它是一个Elf64_Shdr结构的数组,每个Elf64_Shdr结构表示一个段。段表中有些索引号是保留的,目标文件不能使用这些特殊的索引。
Elf64_Shdr结构包含除了ELF头、段表的所有信息,而且目标文件的section满足几个条件:
- 目标文件中的每个section都只有一个Elf64_Shdr结构项描述,可以存在不指示任何section的Elf64_Shdr结构项。
- 每个section在文件中占据一块连续的空间。
- section之间不可重叠。
- 目标文件可以有非活动空间,各种文件头和section没有覆盖目标文件的每一个字节,这些非活动空间是没有定义的。
Elf64_Shdr结构定义如下,可在/usr/include/elf.h中可以找到Elf64_Shdr结构的定义:
sh_name指出section的名字,它的值是后面将会讲到的section header string table中的偏移,指出一个以null结尾的字符串。
sh_type是类别。
sh_flags指示该section在进程执行时的特性。
sh_addr指出若此section在进程的内存映像中出现,则给出开始的虚地址。
sh_offset给出此section在文件中的偏移。其它字段的意义不太常用,在此不细述。
符号表(Symbol Table)
目标文件的符号表包含定位或重定位程序符号定义和引用时所需要的信息,它是一个Elf64_Sym结构的数组,可在/usr/include/elf.h中可以找到Elf64_Sym结构的定义:
st_name包含指向符号表字符串表(strtab)中的索引,从而可以获得符号名。
st_value指出符号的值,可能是一个绝对值、地址等。
st_size指出符号相关的内存大小,比如一个数据结构包含的字节数等。
st_info规定了符号的类型和绑定属性,指出这个符号是一个数据名、函数名、section名还是源文件名;并且指出该符号的绑定属性是local、global还是weak。