1.ELF二进制格式
ELF代表Executable and Linkable Format,它是用于可执行文件、目标文件和库文件的格式,内核本身也是elf格式。elf的文件格式可用于linux内核支持的几乎所有体系结构上,但这并不代表不同系统间存在二进制兼容性,只是程序本身的相关信息以及程序的各个部分在二进制文件中的编码方式是相同的。
1.1布局
ELF格式在此我们区分链接对象和可执行文件两种。下图是ELF文件的基本布局

ELF头:几个字节标识ELF文件,还包含了有关文件类型和大小的有关信息,以及文件加载后程序执行的入口点信息。
程序头表:向系统提供了可执行文件的数据在进程虚拟地址空间中组织的方式的相关信息。还表示了文件可能包含的段数目、位置和用途。
段:保存了与文件相关的各种形式的数据,如符号表、实际的二进制码或程序使用的数值常数。
节头表:包含了与各段相关的附加信息。
readelf工具用于分析ELF文件结构
生成可执行文件与可重定位的目标文件,用file工具分析

ELF头 readelf -h
首先分析可执行文件的elf头,文件头开始有四个字节0x7f454c46标识ELF文件。ELF64标识64位的cpu。文件类型DYN是一种介于可执行文件和目标文件之间的代码是像共享库一样可重新分配地址的程序,而标准的可执行程序需要固定的地址,避免了缓冲区溢出攻击的风险,在编译时可加上-no-pie 禁用。另外还有入口地址和程序大小等信息。

对于目标文件进行readelf分析,看到文件类型REL,即可重定位文件,代码可以移动到任何位置,意味着在汇编代码中必须使用相对转移地址而不是绝对地址。另外需要链接的对象没有程序头表,所以长度为0。

ELF程序头表 readelf -l
目标文件没有该表,可执行文件才有。在程序头表后,列出了几个段,这些组成了最终在内存中执行的程序,还提供了各段在虚拟地址空间和物理地址空间中的大小、位置、标志、访问授权和对齐方面的信息,还指定了类型来更精确地描述各段。
PHDR:保存程序头表
INTERP:指定在程序已经从可执行文件映射到内存后,必须调用的解释器。解释器在这里指的是通过链接其他库来满足未解决的引用。这里是/lib64/ld-linux-x86-64.so.2库,它用于在虚拟地址空间中插入程序运行时所需的动态库。
LOAD:表示一个需要从二进制文件映射到虚拟地址空间的段,其中保存了常量数据或程序的目标代码。
DYNAMIC:保存了由INTERP中指定的动态链接器需要使用的信息。
NOTE:保存了其他信息,这里不讨论。
虚拟地址空间中的各个段,填充了来自于ELF文件中特定段的数据,readelf输出的的二部分指定了哪些节载入到哪些段。

ELF节头表 readelf -S
ELF节头表中指定了将哪些节的数据映射到段中,节头表用于管理文件的各个节,下面分析目标文件的节头表。

这里指定的偏移量0x420是相对于二进制文件,节信息无须复制到虚拟地址空间中为可执行文件创建最终的进程映像。每个节都指定了一个类型,定义了节数据的语义。最重要的是PROGBITS(程序必须解释的信息,如二进制代码,也称为text),SYMTAB(符号表,用于存储与ELF格式有关的字符串,但与程序没有直接的关联,如节的符号名称,如.text或.comment),REL(重定位信息)。各节都指定了大小和在二进制文件内部的偏移量,Address字段可用于指定节加载到虚拟地址空间中的位置。但因为例子处理的是一个可链接对象,目标地址是未定义的,因而表示为0。Flags表明各个节如何访问或处理,A标志控制着装载文件时是否将节的数据复制到虚拟地址空间。
可执行文件包含了一些附加信息


.interp保存了解释器的文件名,这是一个ASCII字符串。
.data保存了初始化过的数据,这是普通程序数据的一部分,可以在程序运行时间修改(例如,预先初始化的结构)。
.rodata保存了只读数据,可以读取但不能修改。例如,编译器将出现在printf语句中的所有静态字符串封装到该节。
.init和.fini保存了进程初始化和结束所用的代码。这两个节通常是由编译器自动添加的,无须应用程序员关注。
.hash是一个散列表,允许在不对全表元素进行线性搜索的情况下,快速访问所有的符号表项。
节(对于可执行文件)的address字段保存了有效的值,因为相应的代码必须映射到虚拟地址空间中某些定义好的位置。
1.2 符号表
符号表保存了程序实现或使用的所有(全局)变量和函数。如果程序引用了一个自身代码未定义的符号,则称之为未定义符号。例如printf函数,就是定义在C标准库中。此类引用必须在静态链接期间用其他目标模块或库解决,或在加载时间通过动态链接(使用ld-linux.so)解决。 nm工具可生成程序定义和使用的所有符号列表

左侧一列给出了符号的值,即符号定义在目标文件中的位置。函数定义在text段(由缩写T标明),而未定义的引用由U标明。逻辑上,未定义的引用没有符号值。因为该目标文件中只有add的定义,exit和printf都在库中,还未链接起来,所以现在还是未定义的符号值。下面是可执行文件中的符号,大部分由编译器生成。

在这里,exit和printf仍然是未定义的,但同时增加了一些信息,表明能够提供函数的GNU标准库的最低版本。由程序本身定义的add和main符号已经移到虚拟地址空间中的固定位置(在文件加载时,对应的代码将映射到这些位置)。
ELF用以下3个节容纳相关的符号表数据:
.symtab确定符号的名称与其值之间的关联。但符号的名称不是直接以字符串形式出现的,而是表示为某个字符串数组的索引。
.strtab保存了字符串数组。
.hash保存了一个散列表,以帮助快速查找符号。
.shstrtab用于存放文件中各个节的文本名称(例如,.text)
.symtab节中的每一项由两个元素组成,符号名在字符串表中的位置和符号的值。读者接下来会看到,实际情况要稍微复杂一些,因为需要为每一项考虑更多的信息。
字符串表:

表的第一个字节是0,后续的各字符串通过0字节分隔。
本文深入解析了ELF(Executable and Linkable Format)格式,详细介绍了其在Linux内核支持的各种体系结构上的应用,包括ELF文件的基本布局、ELF头、程序头表、段、节头表等内容,以及符号表在程序链接过程中的作用。
1137

被折叠的 条评论
为什么被折叠?



