ELF = Executable and Linkable Format,可执行连接格式,是UNIX系统实验室(USL)作为应用程序二进制接口(Application Binary Interface,ABI)而开发和发布的。扩展名为elf。
其主要有三种主要类型:
适于连接的可重定位文件(relocatable file),可与其它目标文件一起创建可执行文件和共享目标文件。
适于执行的可执行文件(executable file),用于提供程序的进程映像,加载的内存执行。
共享目标文件(shared object file),连接器可将它与其它可重定位文件和共享目标文件连接成其它的目标文件,动态连接器又可将它与可执行文件和其它共享目标文件结合起来创建一个进程映像。
为了方便和高效,ELF文件内容有两个平行的视角:一个是程序连接角度,另一个是程序运行角度,如图所示。
ELF header在文件开始处描述了整个文件的组织,Section提供了目标文件的各项信息(如指令、数据、符号表、重定位信息等),Program header table指出怎样创建进程映像,含有每个program header的入口,section header table包含每一个section的入口,给出名字、大小等信息。其中Segment与section的关系后面会讲到。
要理解这个图,我们还要认识下ELF文件相关的几个重要的结构体:
像bmp、exe等文件一样,ELF的文件头包含整个文件的控制结构。它的定义如下,可在/usr/include/elf.h中可以找到文件头结构定义:
其中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表中的索引。
目标文件或者共享文件的program header table描述了系统执行一个程序所需要的段或者其它信息。目标文件的一个段(segment)包含一个或者多个section。Program header只对可执行文件和共享目标文件有意义,对于程序的链接没有任何意义。结构定义如下,可在/usr/include/elf.h中可以找到文件头结构定义:
其中p_type描述段的类型;
p_offset给出该段相对于文件开关的偏移量;
p_vaddr给出该段所在的虚拟地址;
p_paddr给出该段的物理地址;
p_filesz给出该段的大小,在字节为单元,可能为0;
p_memsz给出该段在内存中所占的大小,可能为0;
p_filesze与p_memsz的值可能会不相等。
目标文件的section header table可以定位所有的section,它是一个Elf64_Shdr结构的数组,Section头表的索引是这个数组的下标。有些索引号是保留的,目标文件不能使用这些特殊的索引。
Section包含目标文件除了ELF文件头、程序头表、section头表的所有信息,而且目标文件section满足几个条件:
目标文件中的每个section都只有一个section头项描述,可以存在不指示任何section的section头项。
每个section在文件中占据一块连续的空间。
Section之间不可重叠。
目标文件可以有非活动空间,各种headers和sections没有覆盖目标文件的每一个字节,这些非活动空间是没有定义的。
Section header结构定义如下,可在/usr/include/elf.h中可以找到文件头结构定义:
其中sh_name指出section的名字,它的值是后面将会讲到的section header string table中的偏移,指出一个以null结尾的字符串。
sh_type是类别。
sh_flags指示该section在进程执行时的特性。
sh_addr指出若此section在进程的内存映像中出现,则给出开始的虚地址。
sh_offset给出此section在文件中的偏移。其它字段的意义不太常用,在此不细述。
文件的section含有程序和控制信息,系统使用一些特定的section,并有其固定的类型和属性(由sh_type和sh_info指出)。下面介绍几个常用到的section:“.bss”段含有占据程序内存映像的未初始化数据,当程序开始运行时系统对这段数据初始为零,但这个section并不占文件空间。“.data.”和“.data1”段包含占据内存映像的初始化数据。“.rodata”和“.rodata1”段含程序映像中的只读数据。“.shstrtab”段含有每个section的名字,由section入口结构中的sh_name索引值来获取。“.strtab”段含有表示符号表(symbol table)名字的字符串。“.symtab”段含有文件的符号表,在后文专门介绍。“.text”段包含程序的可执行指令。
目标文件的符号表包含定位或重定位程序符号定义和引用时所需要的信息。符号表入口结构定义如下,可在/usr/include/elf.h中可以找到文件头结构定义:
其中st_name包含指向符号表字符串表(strtab)中的索引,从而可以获得符号名。
st_value指出符号的值,可能是一个绝对值、地址等。
st_size指出符号相关的内存大小,比如一个数据结构包含的字节数等。
st_info规定了符号的类型和绑定属性,指出这个符号是一个数据名、函数名、section名还是源文件名;并且指出该符号的绑定属性是local、global还是weak。
以一个最简单的helloworld程序为例 :
#include
int main()
{
printf("Hello World!\n");
return 0;
}
大小总共为64字节,换算成十六进制为0x40。在十六进制代码中找到前0x40字节,即为文件头信息部分(阅读时注意反序问题):
对比上面结构体的定义,来解释下结构体各个字段的值:
#define EF_SH_UNKNOWN 0x0。
使用工具查看Program header:readelf -l obj
从上图中可以知道,与我们上面对ELF文件头的分析完全对应的上,该目标文件一共有9个段,起始偏移地址是64。大小总共为56字节*9 ,换算成十六进制为0x1F8。在十六进制代码中偏移地址64字节开始找到前0x1F8字节,即为Program header信息部分(阅读时注意反序问题):
这里我们要解释下上面的一个问题 :Section和Segment的区别和联系
可执行文件中,一个program header描述的内容称为一个段(segment)。Segment包含一个或者多个section,我们以我们这个例子为例,看一下section与segment的映射关系:
如上图映射关系可知,文本段并不仅仅包含.text节,数据段也不仅仅包含.data节,而是都包含了多个section。
使用工具查看段表信息:readelf -S obj
在文件头中e_shoff可以找到段表偏移地址00 00 00 00 00 00 19 c8,从这个地址去查找段表。
段表长度由e_ehentsize为00 40(64字节)。
段表个数由e_shnum可知有30个。
这么多section我就不一一全部详细的列出来,我选一个text section来进行分析,上图中可以看到,text section索引序号为13,我们的段表的起始偏移地址为0x19c8,每个段长度为0x40,其前面有13个段,所以我们text section的起始地址应该是0x19c8 + (0x40*0x0d) = 0x1d08 , 我们来通过
hexdump -C helloworld
命令来验证下文件中这个地址是不是text section。
sh_name:四个字节,94 00 00 00表示该段名称在.shstrtab中偏移量,我们通过
readelf -x .shstrtab helloworld
来看下.shstrtab段里面偏移量为0x94处是不是.text :
由上图可知,偏移量为0x94处确实就是.text,看来我们上面的推算都是正确的。
sh_type:四个字节,01 00 00 00表示这个段拥有程序所定义的信息,其格式和含义完全由该程序确定,这里表示PROGBITS。
sh_flags:八个字节,06 00 00 00 00 00 00 00表示alloc和execute。
sh_addr:八个字节,40 04 40 00 00 00 00 00表示是section在内存中的虚拟地址为0x400440。
sh_offset:八个字节,40 04 00 00 00 00 00 00表示是section与文件头之间的偏移为0x0440。
sh_size:八个字节,84 01 00 00 00 00 00 00表示文件里面section占用的大小为0x0184。
sh_link:四个字节,00 00 00 00表示没有链接信息。
sh_info:四个字节,00 00 00 00表示没有辅助信息。
sh_addralign:八个字节,10 00 00 00 00 00 00 00表示字节对齐长度。
sh_entsize:八个字节,00 00 00 00 00 00 00 00表示没有入口。
我们按照上面的sh_offset和sh_size字段通过hexdump命令来看看目标文件偏移0x440处,长度为0x0184的内容:
然后我们通过如下命令直接把text section的具体内容可以打印出来,
readelf -x .text helloworld
跟上图的数据进行对比:
很明显我前面的分析是对的,其他section都可以通过上面办法来验证下,至于text section里的具体内容是什么,程序执行具体的过程,我将在后续跟进,这篇文章只是简单的认识下ELF文件。
文中有什么错误之处或者表达不明白的位置欢迎大家拍砖指出!