ELF全称:可执行链接格式,是UNIX系统实验室(USL)作为应用程序二进制接口(Application Binary Interface,ABI)而开发和发布的。ELF文件有三种不同类型:
- 可重定位文件:包含代码和数据,用于链接成可执行文件或共享目标文件后使用(.o文件和.a静态库)。
- 可执行文件:包含二进制代码和数据,可直接复制到存储器并执行(/bin或/usr/bin目录下的文件。
- 共享目标文件:特殊的可重定位文件,可在加载或运行时被动态地加载到存储器并链接。
文件最前部为ELF文件头,描述了整个文件的基本属性,如文件类型,版本,目标机器类型,程序入口地址等,然后是各段,每段由段表描述。链接视图和执行视图下,段分别由Section Header Table和Program Header Table描述。
ELF头文件
由/usr/include/elf.h文件给出定义
typedef struct elf64_hdr {
unsigned char e_ident[EI_NIDENT]; //魔数 7F+’E’+’L’+’F’
Elf64_Half e_type; //文件类型
Elf64_Half e_machine; //机器类型
Elf64_Word e_version; //文件版本
Elf64_Addr e_entry; //进程开始的虚地址,即系统将控制转移的位置
Elf64_Off e_phoff; //program header table的文件偏移
Elf64_Off e_shoff; //section header表的文件偏移
Elf64_Word e_flags; //处理器相关的标志
Elf64_Half e_ehsize; //ELF文件头的长度
Elf64_Half e_phentsize; //program header表中的入口的长度
Elf64_Half e_phnum; //program header表中的入口数目
Elf64_Half e_shentsize; //section header表中的入口的长度
Elf64_Half e_shnum; //section header表中的入口数目
Elf64_Half e_shstrndx; //section名表的位置,指出在section header表中的索引
} Elf64_Ehdr;
Program Header
目标文件或者共享文件的program header table描述了系统执行一个程序所需要的段或者其它信息。目标文件的一个段(segment)包含一个或者多个section。Program header只对可执行文件和共享目标文件有意义,对于程序的链接没有任何意义
typedef struct elf64_phdr {
Elf64_Word p_type; //段的类型
Elf64_Word p_flags; //段标志
Elf64_Off p_offset; //段偏移
Elf64_Addr p_vaddr; //段虚地址
Elf64_Addr p_paddr; //段物理地址
Elf64_Xword p_filesz; //段大小
Elf64_Xword p_memsz; //段在内存的占用
Elf64_Xword p_align; //段对齐
} Elf64_Phdr;
Section Header
文件的section header table可以定位所有的section
typedef struct elf64_shdr {
Elf64_Word sh_name; //段名,值为符号表索引
Elf64_Word sh_type; //类别
Elf64_Xword sh_flags; //程执行时的特性
Elf64_Addr sh_addr; //进程的内存映像中出现,则给出开始的虚地址
Elf64_Off sh_offset; //偏移
Elf64_Xword sh_size; //大小
Elf64_Word sh_link; //相对其他段的索引
Elf64_Word sh_info; //信息
Elf64_Xword sh_addralign; //对齐
Elf64_Xword sh_entsize; //
} Elf64_Shdr;
Symbol Table
文件的符号表包含定位或重定位程序符号定义和引用时所需要的信息
typedef struct elf64_sym {
Elf64_Word st_name; //符号名
unsigned char st_info; //符号类型
unsigned char st_other; //其他
Elf64_Half st_shndx; //段索引
Elf64_Addr st_value; //符号地址值
Elf64_Xword st_size; //符号大小
} Elf64_Sym;
ELF文件分析
为了分析elf文件,新建hello.c文件
#include <stdio.h>
int add(void) {
int a = 3;
int b = 2;
int c = a+b;
return c;
}
int main() {
printf("%d\n",add());
return 0;
}
新建MakeFile文件
hello: hello.o
gcc -o hello hello.o
hello.o: hello.c
gcc -c -o hello.o hello.c
执行make hello 后生成名为hello的elf可执行文件, 接着执行 readelf -h hello 输出头文件信息
从输出信息中可以得到魔数,机器架构,系统类型。可以看到文件大小为64bytes(0x40)执行hexdump -C hello 输出elf文件二进制信息,0x0 - 0x40 代表头结构数据范围
参考elf64_hdr结构定义我们可以读取到信息,比如7f 45 4c 56 ASCII码表示为 .ELF 。依次 02 01 01 00 分别表示64位对象,小端,文件头版本,其他。03 00 表示此文件为共享目标文件,等等。
执行 readelf -l hello 输出 Program header 信息
从先前表头hex读取到e_phnum = 0d 00 (小端转换00 0d)= (13) 即segment有13段,从Program header输出信息看到确实是13段。上图标注了第一段对应的hex编码范围。Section to Segment mapping 表示两种视图映射。
执行readelf -S hello 得到 Section Header 表
从先前的表头hex读取到 Section 数量e_shnum为 1f00 (小端转换001f) 代表有31个段
- 段起始偏移e_shoff为 98 39 00 00 00 00 00 00 (小端转换00 00 00 00 00 00 39 98)
- e_ehentsize为 40 00 (小端转换 00 40)
- 代码段编号16
所以代码段地址为0x3998 + (0x40 * 0x10) = 0x3D98
现在来验证 sh_name, b9 00 00 00 (00 00 00 b9) 表示该段名称在.shstrtab中偏移量,
执行 readelf -x .shstrtab hello
在00 00 00 b9 处的编码确实是text。 现在来验证section在内存中的虚拟地址 执行 readelf -x .text hello 输出代码段信息
代码段虚拟地址sh_addr为60 10 00 00 等于由代码段输出得到的地址信息(0x00001060),由此上述关于代码段的计算是正确的。其他段的推导也是一样的。