赞
踩
ELF的英文全称是The Executable and Linking Format,最初是由UNIX系统实验室开发、发布的ABI(Application Binary Interface)接口的一部分,也是Linux的主要可执行文件格式。
从使用上来说,主要的ELF文件的种类主要有三类:
本文主要从elf文件的组成构造的角度来进行分析,将elf文件的解析通过一步一步的分析得到里面的信息,同时通过python脚本解析,可以直观的看到文件的信息,通过本文的阅读,将对elf文件格式有着更加深刻的理解。
elf文件是有一定的格式的,从文件的格式上来说,分为汇编器的链接视角与程序的执行视角两种去分析ELF文件。
从程序执行视角来说,这就是Linux加载器加载的各种Segment的集合。比如只读代码段、数据的读写段、符号段等等。而从链接的视角上来看,elf又分为各种的sections。
注意Section Header Table和Program Header Table并不是一定要位于文件开头和结尾的,其位置由ELF Header指出,上图这么画只是为了清晰。
为了彻底的弄清楚elf文件的内容,可以先从ELF文件的头部开始分析。
对于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;
上述的Elf32_Half定义
- /* Type for a 16-bit quantity. */
- typedef uint16_t Elf32_Half;
其中Elf32_Word的定义
- /* Types for signed and unsigned 32-bit quantities. */
- typedef uint32_t Elf32_Word;
然后Elf32_Addr与Elf32_Off定义
- /* Type of addresses. */
- typedef uint32_t Elf32_Addr;
-
- /* Type of file offsets. */
- typedef uint32_t Elf32_Off;
有了这些数据结构的信息,然后对应具体的数据细节如下:
文件的标识以及标识描述了elf如何编码等信息。
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
关于该结构体的索引可以看下面的表格:
名称 | 取值 | 目的 |
EI_MAG0 | 0 | 文件标识(0x7f) |
EI_MAG1 | 1 | 文件标识(E) |
EI_MAG2 | 2 | 文件标识(L) |
EI_MAG3 | 3 | 文件标识(F) |
EI_CLASS | 4 | 文件类 |
EI_DATA | 5 | 数据编码 |
EI_VERSION | 6 | 文件版本 |
EI_PAD | 7 | 补齐字节开始处 |
EI_NIDENT | 16 | e_ident[]大小 |
EI_CLASS的内容,当取值为0时,是非法类别,1是32位的目标,2是64位的目标。这里是1所以程序是32位的目标。
EI_DATA表示数据的编码,当为0时,表示非法数据编码,1表示高位在前,2表示低位在前。
EL_VERSION表示了elf的头部版本号。
前面四个基本上确定的,内容第一个字符为7f,后面用ELF字符串表示该文件为ELF格式。
该数据类型是uint16_t数据类型的,占两个字节。通过字段查看,可以看到这个值为00 03。表格定义如下:
名称 | 取值 | 含义 |
ET_NONE | 0x0000 | 未知目标文件格式 |
ET_ERL | 0x0001 | 可重定位文件 |
ET_EXEC | 0x0002 | 可执行文件 |
ET_DYN | 0x0003 | 共享目标文件 |
ET_CORE | 0x0004 | Core文件(转储格式) |
ET_LOPROC | 0xff00 | 特定处理器文件 |
ET_HIPROC | 0xffff | 特定处理器文件 |
对应表格内容,可以看到类型为DYN即可执行文件类型。
由字段可以看到为00 28,关于这个字段的解析,基本上就是表示该elf文件是针对哪个处理器架构的。
下面只列出几个常见的架构的序号
名称 | 取值 | 含义 |
EM_NONE | 0 | No machine |
EM_SPARC | 2 | SPARC |
EM_386 | 3 | Intel 80386 |
EM_MIPS | 8 | MIPS I Architecture |
EM_PPC | 0x14 | PowerPC |
EM_ARM | 0x28 | Advanced RISC Machines ARM |
通过上述的表格,可以看到该架构是ARM处理器上运行的程序。
该字段占四个字节,表示当前文件版本的信息。现在取值为00 00 00 01。从取值上来看
名称 | 取值 | 含义 |
EV_NONE | 0 | 非法版本 |
EV_CURRENT | 1 | 当前版本 |
这里表示程序的入口地址,目前为四字节,所以通过字段解析到的内容为00 00 09 B4。得到可执行程序的入口地址为0x9B4。
该字段表示程序表头偏移。占四个字节,根据字段解析,可以查看当前的偏移量为00 00 00 34。也就是实际的偏移量为52个字节。这52个字节其实就是头部的信息数据结构体的大小。
该区域比较重要,记录了section的偏移地址。为四字节,解析出来的字段为00 00 12 E4。所以得到地址为0x12E4。
根据这个偏移得到section的内容:
通过readelf -t也可以得到类似的结果。
关于节区如何解析。后面再进行描述。
特定处理器格式的标志,这里的字段解析为05 00 02 00。与特定的处理器相关。
elf文件的头部大小。该取值与头文件结构体的大小相关,目前为52字节,即00 34。
程序头部表项大小,当前取值为00 20,为32个字节,这里表示
关于程序表项的解析,后面再进行具体分析。
前取值为00 09,这里表示程序头的个数当前有9个程序头。elf文件有多个程序头表,那么会在elf头文件之后,也就是52个字节之后,依次向下排列。
表示节区头部表格大小,解析字段为00 28,也就是第一个节区的大小为40个字节的偏移处。根据e_shoff可以知道。
将从e_shoff的区域向后面偏移40个字节,得到第一个节区的内容。
节区的数量,由字段解析得到数据为00 1A。此时得到节区的数量为26个。通过readelf -t也可以解析到节区的数量为26个。
标记字符串节区的索引。当前的解析为00 19。也就是第25个节区为字符节区。
到这里,头部信息的相关字段就解析完成了。
elf文件中的节是从编译器链接角度来看文件的组成的。从链接器的角度上来看,包括指令、数据、符号以及重定位表等等。
在可重定位的可执行文件中,节区描述了文件的组成,节的位置等信息。通过readelf -s可以查看信息。
这些节信息通过特定的地址偏移组成了一个elf文件的整体。
关于理解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的内容。
sh_name:section名称,4个字节,指向字符串表的索引
sh_type:section类型,4个字节
sh_flags:section标志,每一位对应一个标志,但有的位是保留的,32位4个字节,64位8个字节
sh_addr:程序执行时section所在的虚拟地址,32位4个字节,64位8个字节
sh_offset:section在文件内的偏移,32位4个字节,64位8个字节
sh_size:section的大小,32位4个字节,64位8个字节
sh_link:指向其他section的索引,有特殊含义,4个字节
sh_info:存储额外信息,依section类型而定,4个字节
sh_addralign:section载入内存时按几个字节对齐,32位4个字节,64位8个字节
sh_entsize:section内每条记录占的字节数,32位4个字节,64位8个字节
如果要找到每个段的具体细节,首先可以根据e_shstrndx找到节的字段。由于e_shstrndx=25。而且每个为40字节。那么一共是1000字节的偏移。从e_shoff的地址0x12E4开始,那么可以得到e_shstrndx对应的节区地址为0x16CC。
其中0x11f0地址为节区字符串存在的区域,节区大小为0xf2。
接下来我们来举个具体的例子来解析Section。比如要读取.text的段。那么首先看一下细节。
首先从字段结构体上进行分析:
sh_name
表示从e_shstrndx的偏移地址0x11f0开始,偏移0x18字节得到的字符串信息为该section的名称,最后算出得到实际的名称为.text。
sh_type
字段的类型为01,关于sh_type的类型,解析如下:
- /* Legal values for sh_type (section type). */
-
- #define SHT_NULL 0 /* Section header table entry unused */
- #define SHT_PROGBITS 1 /* Program data */
- #define SHT_SYMTAB 2 /* Symbol table */
- #define SHT_STRTAB 3 /* String table */
- #define SHT_RELA 4 /* Relocation entries with addends */
- #define SHT_HASH 5 /* Symbol hash table */
- #define SHT_DYNAMIC 6 /* Dynamic linking information */
- #define SHT_NOTE 7 /* Notes */
- #define SHT_NOBITS 8 /* Program space with no data (bss) */
- #define SHT_REL 9 /* Relocation entries, no addends */
- #define SHT_SHLIB 10 /* Reserved */
- #define SHT_DYNSYM 11 /* Dynamic linker symbol table */
- #define SHT_INIT_ARRAY 14 /* Array of constructors */
- #define SHT_FINI_ARRAY 15 /* Array of destructors */
- #define SHT_PREINIT_ARRAY 16 /* Array of pre-constructors */
- #define SHT_GROUP 17 /* Section group */
- #define SHT_SYMTAB_SHNDX 18 /* Extended section indeces */
- #define SHT_NUM 19 /* Number of defined types. */
- #define SHT_LOOS 0x60000000 /* Start OS-specific. */
- #define SHT_GNU_ATTRIBUTES 0x6ffffff5 /* Object attributes. */
- #define SHT_GNU_HASH 0x6ffffff6 /* GNU-style hash table. */
- #define SHT_GNU_LIBLIST 0x6ffffff7 /* Prelink library list */
- #define SHT_CHECKSUM 0x6ffffff8 /* Checksum for DSO content. */
- #define SHT_LOSUNW 0x6ffffffa /* Sun-specific low bound. */
- #define SHT_SUNW_move 0x6ffffffa
- #define SHT_SUNW_COMDAT 0x6ffffffb
- #define SHT_SUNW_syminfo 0x6ffffffc
- #define SHT_GNU_verdef 0x6ffffffd /* Version definition section. */
- #define SHT_GNU_verneed 0x6ffffffe /* Version needs section. */
- #define SHT_GNU_versym 0x6fffffff /* Version symbol table. */
- #define SHT_HISUNW 0x6fffffff /* Sun-specific high bound. */
- #define SHT_HIOS 0x6fffffff /* End OS-specific type */
- #define SHT_LOPROC 0x70000000 /* Start of processor-specific */
- #define SHT_HIPROC 0x7fffffff /* End of processor-specific */
- #define SHT_LOUSER 0x80000000 /* Start of application-specific */
- #define SHT_HIUSER 0x8fffffff /* End of application-specific */
当前为1,所以得到数据为程序数据。比如.text .data .rodata等等。
关于Linking View与Execution View的具体含义,可以查看:http://www.skyfree.org/linux/references/ELF_Format.pdf
这里有一张图值得研究一下:
对于链接视图,也就是我们前面分析的Section,可以理解目标代码文件的内容布局。而右边的ELF的执行视图,则可以理解为可执行的文件内容布局。链接视图由sections组成,而可执行的文件的内容由segment组成。
两者是有一些区别的,我们平时在进行程序构建的时候理解的.text、.bss、.data段,这些都是section,也就节区的概念。这些段通过section header table进行组织与重定位。
但是对于segment来说,程序代码段、数据段是Segment。代码段又可以分为.text,数据段又分为.data、.bss等。
通过readelf -l可以查看具体的可执行文件的细节。
这里的信息和程序的加载直接相关。具体的elf文件加载过程这篇文章不会多说。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。