1.说明
ELF的英文全称是The Executable and Linking Format,最初是由UNIX系统实验室开发、发布的ABI(Application Binary Interface)接口的一部分,也是Linux的主要可执行文件格式。
从使用上来说,主要的ELF文件的种类主要有三类:
1. 可执行文件(.out):Executable File,包含代码和数据,是可以直接运行的程序。其代码和数据都有固定的地址 (或相对于基地址的偏移 ),系统可根据这些地址信息把程序加载到内存执行。
2. 可重定位文件(.o文件):Relocatable File,包含基础代码和数据,但它的代码及数据都没有指定绝对地址,因此它适合于与其他目标文件链接来创建可执行文件或者共享目标文件。
3. 共享目标文件(.so):Shared Object File,也称动态库文件,包含了代码和数据,这些数据是在链接时被链接器(ld)和运行时动态链接器(ld.so.l、libc.so.l、ld-linux.so.l)使用的。
本文主要从elf文件的组成构造的角度来进行分析,将elf文件的解析通过一步一步的分析得到里面的信息,同时通过python脚本解析,可以直观的看到文件的信息,通过本文的阅读,将对elf文件格式有着更加深刻的理解。
2.elf文件的基本格式
elf文件是有一定的格式的,从文件的格式上来说,分为汇编器的链接视角与程序的执行视角两种去分析ELF文件。
从程序执行视角来说,这就是Linux加载器加载的各种Segment的集合。比如只读代码段、数据的读写段、符号段等等。而从链接的视角上来看,elf又分为各种的sections。
注意Section Header Table和Program Header Table并不是一定要位于文件开头和结尾的,其位置由ELF Header指出,上图这么画只是为了清晰。
为了彻底的弄清楚elf文件的内容,可以先从ELF文件的头部开始分析。
3.ELF header
对于elf头部文件信息,首先可以可以查看一下内存的布局情况:
根据readelf可以得到该文件的头部信息的情况。
根据定义,elf32的结构体定义,在Linux上可以在/usr/include/elf.h中找到
#define EI_NIDENT (16)
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;
可以看出来指定了Program header table和Section header table的位置,因此这两个表在elf中的位置是不固定的
4.elf文件的节区(Section)
elf文件中的节是从编译器链接角度来看文件的组成的。从链接器的角度上来看,包括指令、数据、符号以及重定位表等等。
4.1 节区的作用
在可从定位的可执行文件中,节区描述了文件的组成,节的位置等信息。通过readelf -s可以查看信息。
这些节信息通过特定的地址偏移组成了一个elf文件的整体。
4.2 节区的组成
关于理解ELF中的Section。首先需要知道程序的链接视图,在编译器将一个一个.o文件链接成一个可以执行的elf文件的过程中,同时也生成了一个表。这个表记录了各个Section所处的区域。在程序中,程序的section header有多个,但是大小是一样。拿elf32文件来说
typedef struct
{
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;
根据e_shoff可以找到section的地址,根据e_shentsize可以找到具体的第一个section的内容。
5.elf文件的段(Segment),即(代码段等)
对于链接视图,也就是我们前面分析的Section,可以理解目标代码文件的内容布局。而右边的ELF的执行视图,则可以理解为可执行的文件内容布局。链接视图由sections组成,而可执行的文件的内容由segment组成。
两者是有一些区别的,我们平时在进行程序构建的时候理解的.text、.bss、.data段,这些都是section,也就节区的概念。这些段通过section header table进行组织与重定位。
但是对于segment来说,程序代码段、数据段是Segment。代码段又可以分为.text,数据段又分为.data、.bss等。
通过readelf -l可以查看具体的可执行文件的细节。
6.Program header table
当内核加载一个应用程序时,就是通过读取 ELF 文件的信息,然后把文件中所有的段加载到虚拟内存的段中。ELF 文件通过 程序头表 来描述应用程序中所有的段,表中的每一个项都描述一个段的信息。我们先来看看 程序头表 项的结构定义:
typedef struct elf64_phdr {
Elf64_Word p_type; // 段的类型
Elf64_Word p_flags; // 可读写标志
Elf64_Off p_offset; // 段在ELF文件中的偏移量
Elf64_Addr p_vaddr; // 段的虚拟内存地址
Elf64_Addr p_paddr; // 段的物理内存地址
Elf64_Xword p_filesz; // 段占用文件的大小
Elf64_Xword p_memsz; // 段占用内存的大小
Elf64_Xword p_align; // 内存对齐
} Elf64_Phdr;
所以,程序加载器可以通过 ELF 头中获取到程序头表的偏移量,然后通过程序头表的偏移量读取到程序头表的数据,再通过程序头表来获取到所有段的信息。
我们可以通过 readelf -S file 命令来查看 ELF 文件的段(节)信息,如下图所示:
7.Section header table
表格中的元素结构如下
typedef struct
{
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;