浅析elf格式的二进制文件
elf格式的二进制文件
elf指的是executable and linkable format,可执行链接格式。最初由UNIX系统实验室作为应用程序二进制接口(ABI)而开发的。后来发展成了二进制文件格式标准,Linux操作系统下的可执行程序使用的就是该标准。
elf格式文件的产生
在Linux下代码的编译链接后的生成文件都要符合elf文件格式。将c代码编译链接后生成的二进制可执行文件,该文件可以直接运行。只经过编译过的文件称为目标文件或者待重定位文件(relocatable file)。
#以gcc编译器为例:现有hello.c文件
#生成待重定位文件
gcc -c hello.c -o hello.o #hello.o文件就是待重定位文件
#生成可执行文件
gcc hello.c -o hello #hello文件就是可执行文件
elf目标文件有待重定位文件,可共享目标文件,可执行文件。这里重点描述可执行文件。
节、段
节(section)是指在汇编源码中由section或segment修饰,的指令区域或者数据区域。不同的汇编器关键字可能不一样。汇编器将这两个关键字修饰的区域在目标文件中编译成节,所以说节是在待重定位文件目标文件时产生的。
段(segment)是链接器根据待重定位文件中属性相同的多个section合并成section集合,这个集合就是段(segment)。链接器最后生成的是可执行文件,所以段是在可执行文件中生成的。平时所说的数据段,代码段就是这里说的segment。
节头表、程序头表、节头、程序头
一个程序中,节和段的大小和数量是不固定的,所以就需要一个专门的表来描述它们,这个表就是所说的节头表(section header table)和程序头表(program header table)。节头表中描述的信息是多个节头(section header),段头表中描述的信息是多个段头(program header)。
在表中,每一个成员称为条目,及entry,一个条目代表一个段或者一个节的头信息。
elf header
因为程序中的段和节的大小和数量不确定,所以程序头表和节头表的大小就是不确定的,表在程序文件中的存储顺序也是由先后的,所以这两个表的位置也是不确定的。这时就必须在用一个固定的结构来描述这些不确定的信息,这个结构就是elf header,它位于文件最开始的部分。这里就想感叹一下计算机中的分层结构。
elf文件格式作用是在文件链接和运行时方便进行程序的重定位。
elf结构
这里给出elf header结构体,然后通过实例来分析结构体中的内容。其他结构可查看/usr/include/elf.h获得。
这里分析的程序是32位的,64位的程序在分析后也很好理解。
现有程序hello.c,编译链接后的hello
使用xxd命令查看二进制文件,使用readelf查看elf文件信息,以此对照着看
gcc -m32 hello.c -o hello
xxd hello
readelf -h hello
这里先声明要用到的数据类型
数据类型名称 | 字节大小 | 对齐 | 意义 | 声明 |
---|---|---|---|---|
ELF32_Half | 2 | 2 | 无符号中等大小的整数 | typedef uint16_t Elf32_Half; |
ELF32_Word | 4 | 4 | 无符号大整数 | typedef uint32_t Elf32_Word; |
ELF32_Addr | 4 | 4 | 无符号程序运行地址 | typedef uint32_t Elf32_Addr; |
ELF32_Off | 4 | 4 | 无符号的文件偏移量 | typedef uint32_t Elf32_Off; |
//elf header结构
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ //elf文件的魔数
Elf32_Half e_type; //elf文件的格式
Elf32_Half e_machine; //描述elf文件的体系结构类型
Elf32_Word e_version; //版本信息
Elf32_Addr e_entry; //操作系统运行程序时,将控制权交到的虚拟地址
Elf32_Off e_phoff; //程序头表在文件内的偏移量
Elf32_Off e_shoff; //节头表在文件内的偏移量
Elf32_Word e_flags; //与处理器相关的标志
Elf32_Half e_ehsize; //elf header的大小
Elf32_Half e_phentsize; //程序头表中每个条目的大小
Elf32_Half e_phnum; //程序头表中条目的数量
Elf32_Half e_shentsize; //节头表中每个条目的大小
Elf32_Half e_shnum; //节头表中条目的数量
Elf32_Half e_shstrndx; //string name table在节头表中的索引
} Elf32_Ehdr;
# xxd hello的结果,只截取了elf header相关的部分
00000000: 7f45 4c46 0101 0100 0000 0000 0000 0000 .ELF............
00000010: 0300 0300 0100 0000 e003 0000 3400 0000 ............4...
00000020: a817 0000 0000 0000 3400 2000 0900 2800 ........4. ...(.
00000030: 1d00 1c00 0600 0000 3400 0000 3400 0000 ........4...4...
#readelf -h hello的结果
ELF 头:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
类别: ELF32
数据: 2 补码,小端序 (little endian)
版本: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: DYN (共享目标文件)
系统架构: Intel 80386
版本: 0x1
入口点地址: 0x3e0
程序头起点: 52 (bytes into file)
Start of section headers: 6056 (bytes into file)
标志: 0x0
本头的大小: 52 (字节)
程序头大小: 32 (字节)
Number of program headers: 9
节头大小: 40 (字节)
节头数量: 29
字符串表索引节头: 28
文件起始的十六字节对应的是elf结构体中的魔数部分,在readelf中可以看到是相符的。往后可以按照xxd的结果和readelf的结果对照,都是一一对应的。根据数据类型的字节大小很好分析,这里就不在赘述。