ELF文件格式浅析
概述
ELF(Executable Linkable Format)是linux操作系统下目标文件和可执行文件的文件格式,目标文件是指由源代码编译后但没有进行连接的中间文件(windows下的.obj和linux下的.o),它跟可执行文件的格式基本上式一样的,只是在可执行文件中多了program headers 和program sections,它们在程序装载的时候要有到。本文主要分析目标文件的格式。
目标文件里有什么了?首先给出目标文件总体结构示意图,如图1所示。图1中我们省去了ELF一些繁琐的结构,把重要的结构提取出来,形成了图1所示的基本结构图。
图1 ELF文件结构
整个文件总体上由三部分组成:文件头(ELF header),段表(Section Header Table),各种段(Section)。由于编译器的不同,这些段在文件结构中的位置可能和上图所示的有些不一样,但是基本结构是相同。
这三部分作用什么,它们之间的联系又是什么呢?下面将对这三部分做具体的分析。
1. ELF文件头
如果不深入到目标文件内部,是很理解其中的细节的,因此我们选用了下面一段小程序作为具体的分析对象,分析的平台式linux操作系统,程序名为elf.c,程序代码清单如下图所示。
图2 程序代码
我们使用gcc来编译这个文件:
gcc –c elf.c –o elf.o
生成目标文件elf.o
我们使用readelf命令来查看文件头:
readelf –h elf.o
文件头内容如图3所示:
图3 ELF文件头
文件头描述了整个文件的基本属性,其中定义了ELF魔数、文件机器字节长度、数据存储方式、版本、运行平台、ABI版本、ELF重定位类型、硬件平台、硬件平台版本、入口地址、程序头入口和长度、段表位置和长度,在这里我们看到由文件头可以确定段表在文件中的位置。
ELF文件头数据结构及相关常数定义在“/usr/include/elf.h”文件里,其定义如图4所示:
图4 ELF文件头结构
从ELF文件头数据结构跟前面readelf输出的ELF文件头信息相比较,可以看出它们的结构是一一对应的,其中e_inent[16]对应Magic,Class,Data,Version,OS/ABI这五项,其他部分一一对应。
2. ELF段表
从图1中我们可以看到,ELF文件中有很多各种各样的段,段表就是描述这些段的基本属性的结构,下面我们使用readelf工具来查看elf.o的段表结构:
readelf –S elf.o
段表的内容如下所示:
图5 段表的内容
从图4相中,我们可以发现,这个ELF文件有10个段,其实只要9个段,第一个段是个无效的段,段表描述了各个段的段名、类型、虚拟地址(目标文件各段的虚拟地址为0)在文件中的偏移位置、大小等详细信息。
段表的结构比较简单,它由“Elf32_Shdr”结构体数组组成,该结构定义在“/usr/include/elf.h”文件中,其结构如图6所示:
图6 段表的结构
它和上图一一对应,Elf32_Shdr定义了段表的详细信息。
3. ELF各种段
代码段,数据段就不用多说了,下面主要说说.shstrtab,.symtab,.strtab,rel.text这几个段。
.shstrtab段是做什么用的呢?这个段里面存放的是各段的段名,比方说,.text,.symtab等等这些都是段名, Elf32_Shdr结构中的第一个字段的就是段名,从这些段名可以看出,段名是个字符串,长短不一,无法用一个确定大小的字符数组表示,于是我们把所有的段名都放在一个段,即.shstrtab段,而通过段名在这个段中的偏移位置来得到段名字符串。
同样的,.strtab段和.shstrtab段的功能相似,它里面存放的是程序中出现的各种字符串,包括变量名,函数名等等。
我们知道,目标文件和可执行文件是通过不同的源文件经过编译、链接形成的,程序是以单个文件作为编译的基本单元,每个基本单元生成一个目标文件,再经过链接将这些目标文件组合成可执行程序或更大的目标文件,那么链接的桥梁就是符号表(.symtab)。符号表中记录了符号的类型,是变量还是函数等等;符号的绑定信息,是局部符号还是全局符号,是弱引用还是强引用;还有其他的一些与链接有关的信息。
既然有了符号表,我们还要rel.text这样的重定位段干什么呢?符号表不是都包含了链接信息了?符号表只是说明了各符号的信息,但是它并没有说明具体的链接信息,例如,我们main程序引用了外部变量extern_var,链接表中并没有说明这个符号在elf.o中的什么位置进行链接,链接时使用相对偏移地址还是决定地址,重定位段则说明了这些信息。
通过上面粗略的分析,我们大致的了解了目标文件里的内容,以及为什么有这些内容,他们在文件中的作用是什么,目标文件中的文件头是整个文件的大脑所在,文件头里有程序的入口地址(这个地址对可执行程序有意义),有段表在文件中的偏移,而段表又确定了各个段的位置,通过这些必要的说明,操作系统就可将可执行程序加载到正确的位置执行。