ELF(Executable and Linkable Format) 即可执行可链接文件格式,是目前操作系统上最常见的可执行文件格式。不同系统的目标文件不一样,Windows是PE(Portable Executable),linux是ELF(Executable Linkable Format),它们都是COFF(Common file format)格式的变种。
1、基本格式
ELF格式的目标文件和可执行文件在结构上没有本质差异,ELF不仅仅描述目标文件,也用于描述可执行文件,Windows下的dll和.lib, Linux下的.so和.a文件都是按照类ELF格式存储,下图描述了ELF链接视图(.o文件、.so文件)和执行视图,链接视图描述了各个段(section)的组成,如.text、.data、bss段。执行视图由segment组成,segment用于表示一个一定长度的区域,按照只读/可读写划分,不区分数据的属性,如代码段、数据段。
目标文件是未经过链接的,里面的符号和地址没有调整导致无法运行。例如直接运行目标文件,系统提示无法执行该二进制文件。
$ . hello.o bash: .: hello.o: cannot execute binary file $ file hello.o hello.o: Intel amd64 COFF object file, no line number info, not stripped, 7 sections, symbol offset=0x2a0, 22 symbols, 1st section name ".text"
ELF文件格式在字节对齐和元素解析时,与系统架构、字长有密切关系,ELF 文件由ELF header和各种段组成,其结构图如下所示。详细文档可以查阅Executable and Linkable Format (ELF) - eLinux.org。
2、ELF文件头
ELF 文件的最前面是文件头,描述了ELF文件的基本属性,比如ELF文件版本、目标机器型号、程序入口地址。
详细的描述可以参考:ELF Header
#define EI_NIDENT 16typedef struct {unsigned char e_ident[EI_NIDENT];Elf32_Half e_type;Elf32_Half e_machine;Elf32_Word e_version;Elf32_Addr e_entry;Elf32_Off e_phoff;Elf32_Off e_shoff;Elf32_Word e_flags;Elf32_Half e_ehsize;Elf32_Half e_phentsize;Elf32_Half e_phnum;Elf32_Half e_shentsize;Elf32_Half e_shnum;Elf32_Half e_shstrndx; } Elf32_Ehdr;$ readelf -h /bin/ls ELF Header:Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64Data: 2's complement, little endianVersion: 1 (current)OS/ABI: UNIX - System VABI Version: 0Type: DYN (Position-Independent Executable file)Machine: Advanced Micro Devices X86-64Version: 0x1Entry point address: 0x6180Start of program headers: 64 (bytes into file)Start of section headers: 145256 (bytes into file)Flags: 0x0Size of this header: 64 (bytes)Size of program headers: 56 (bytes)Number of program headers: 11Size of section headers: 64 (bytes)Number of section headers: 30Section header string table index: 29
上面结构体里成员类型长度的定义为:
名称 | 大小 | 说明 |
Elf32_Addr | 4 | 无符号程序地址 |
Elf32_Half | 2 | 无符号中等整数 |
Elf32_Off | 4 | 无符号文件偏移 |
Elf32_SWord | 4 | 有符号大整数 |
Elf32_Word | 4 | 无符号大整数 |
unsigned char | 1 | 无符号字符型整数 |
Elf32_Ehdr 各个成员简介:
- e_ident magic bytes (0x7fELF), class, ABI version....是一组包含多个标志的数组。
- e_typeobject file type—ET{REL,DYN,EXEC,CORE} 00-未知, 01--RT_REL,02--ET_EXEC, 03---ET_DY
- e_machine required architecture—EM X86 64, ... 其中3h=386, 28h=ARM
- e_version EV CURRENT, always ”1”
- e_entry virt. addr. of entry point, dl start, jmp *%r12
- e_phoff program header offset
- e_shoff section header offset
- e_flags CPU-specific flags
- e_ehsize ELF header size
- e_phentsize size of program header entry, consistency check
- e_phnum number of program header entries
- e_shentsize size of section header entry
- e_shnum number of section header entries
- e_shstrndx section header string table index
其中e_ident 对应了对各字段,有MAGIC、Class、Data、Version、 ABI这几个参数。
Name | Value | Purpose |
EI_MAG0 | 0 | File identification |
EI_MAG1 | 1 | File identification |
EI_MAG2 | 2 | File identification |
EI_MAG3 | 3 | File identification |
EI_CLASS | 4 | File class |
EI_DATA | 5 | Data encoding |
EI_VERSION | 6 | File version |
EI_OSABI | 7 | Operating system/ABI identification |
EI_ABIVERSION | 8 | ABI version |
EI_PAD | 9 | Start of padding bytes |
EI_NIDENT | 16 | Size of e_ident[] |
1)MAGIC是ELF标志码:魔数,占4字节,byte0固定为0x7F,byte1--byte3是'E', 'L', 'F' 的ASCII码。
2)EI_CLASS 是CPU字长类型。
Name | Value | Meaning |
ELFCLASSNONE | 0 | Invalid class |
ELFCLASS32 | 1 | 32-bit objects |
ELFCLASS64 | 2 | 64-bit objects |
3)EI_DATA
0:非法格式
1:小端字节序LSB
2:大端字节序MSB
4)EI_VERSION: ELF版本号,为1
5)EI_OSABI
Name | Value | Meaning |
ELFOSABI_NONE | 0 | No extensions or unspecified |
ELFOSABI_HPUX | 1 | Hewlett-Packard HP-UX |
ELFOSABI_NETBSD | 2 | NetBSD |
ELFOSABI_LINUX | 3 | Linux |
ELFOSABI_SOLARIS | 6 | Sun Solaris |
ELFOSABI_AIX | 7 | AIX |
ELFOSABI_IRIX | 8 | IRIX |
ELFOSABI_FREEBSD | 9 | FreeBSD |
ELFOSABI_TRU64 | 10 | Compaq TRU64 UNIX |
ELFOSABI_MODESTO | 11 | Novell Modesto |
ELFOSABI_OPENBSD | 12 | Open BSD |
ELFOSABI_OPENVMS | 13 | Open VMS |
ELFOSABI_NSK | 14 | Hewlett-Packard Non-Stop Kernel |
64-255 | Architecture-specific value range |
3、段表--Section
1)段表的结构
ELF 文件中有很多段,段表(Section Header Table)就是保存这些段的基本属性的结构。段表描述了ELF各个段的信息,如段名、段的长度、在文件中的偏移、读写权限等。段表在ELF文件中 的偏移是由文件头的e_shoff成员决定的。
typedef struct{Elf32_Word sh_name;Elf32_Word sh_type;Elf32_Word sh_flags;Elf32_Addr sh_addr;Elf32_Off sh_offset;Elf32_Word sh_size;Elf32_Word sh_link;Elf32_Word sh_info;Elf32_Word sh_addralign;Elf32_Word sh_entsize; }Elf32_Shdr
Elf32_Shdr成员含义:
- sh_name 段名,但此次只是记录了段名字符串在 .shstrtab 中的偏移
- sh_type 段的类型
- sh_flags 段的标志位
- sh_addr 段的虚拟地址,如果此段可以被加载则表示在进程中的虚拟地址,否则为0
- sh_offset 如果此段位于文件中则表示此段在文件中的偏移
- sh_size 段的长度
- sh_link This member holds a section header table index link, whose interpretation depends on the section type.
- sh_info This member holds extra information, whose interpretation depends on the section type.
- sh_addralign 段对齐,以2的n次方表示,如果为0或1,表示没有对齐要求。
- sh_entsize Section Entry Size段的长度
sh_type :段的类型。
Name | Value |
SHT_NULL | 0 |
SHT_PROGBITS | 1 |
SHT_SYMTAB | 2 |
SHT_STRTAB | 3 |
SHT_RELA | 4 |
SHT_HASH | 5 |
SHT_DYNAMIC | 6 |
SHT_NOTE | 7 |
SHT_NOBITS | 8 |
SHT_REL | 9 |
SHT_SHLIB | 10 |
SHT_DYNSYM | 11 |
SHT_LOPROC | 0x70000000 |
SHT_HIPROC | 0x7fffffff |
SHT_LOUSER | 0x80000000 |
SHT_HIUSER | 0xffffffff |
sh_flag:段的标志位,表示该段在进程的虚拟地址空间中的属性,如是否可读、写、执行。
Name | Value | notes |
SHF_WRITE | 0x1 | 可读 |
SHF_ALLOC | 0x2 | 需要分配空间 |
SHF_EXECINSTR | 0x4 | 可执行 |
SHF_MASKPROC | 0xf0000000 |
sh_link 和 sh_info: 如果段是与链接相关的,比如重定位表符号表等,sh_link和sh_info这两个成员所包含的意义如下所示。
sh_type | sh_link | sh_info |
SHT_DYNAMIC | 该段所使用的字符串表在段表中的下标 | 0 |
SHT_HASH | 该段所使用的符号表在段表中的下标 | 0 |
SHT_REL | 该段所使用的相应符号表在段表中的下标 | 该重定位表所作用的段在段表中的下标 |
SHT_RELA | 该段所使用的相应符号表在段表中的下标 | 该重定位表所作用的段在段表中的下标 |
SHT_SYMTAB | 操作系统相关的 | 操作系统相关的 |
SHT_DYNAMIC | 操作系统相关的 | 操作系统相关的 |
other | SHN_UNDEF | 0 |
2)重定位表
rel.txt段就是重定位表,类型sh_type为SHT_REL(9)。链接器在处理目标文件时,对目标文件某些部位进行重定位,即代码段和数据段中的那部分绝对地址引用。这些重定位信息记录在ELF文件的重定位表中。
3)字符串表
ELF中有很多字符串,如变量名段名等,但是字符串长度是不定长的,如果用固定的长度来表示比较困难,常见的做法是把字符串集中起来放到一个表中,然后使用字符串在表中的偏移来引用字符串。
4)符号表
在ELF文件中,把函数和变量统称为符号(Sysbol),每个符号有一个相应的值叫做符号值。对函数和变量来说,符号值就是它们的地址。在ELF文件中,用.symtab这个段来记录符号表。
typedef struct{Elf32_Word st_name;Elf32_Addr st_value;Elf32_Word st_size;unsigned char st_info;unsigned char st_other;Elf32_Half st_shndx; }Elf32_Sym
- st_name 符号名,符号名称在字符串表中的索引
- st_value 符号相应的值,可能是地址或一个绝对值数
- st_size 符号大小
- st_info 符号类型和绑定值
- st_other 默认0
- st_shndx 符号所在的段
st_info 高4位表示符号绑定信息,低4位表示符号类型。
Symbol Binding, ELF32_ST_BIND
Name | Value |
STB_LOCAL | 0 |
STB_GLOBAL | 1 |
STB_WEAK | 2 |
STB_LOPROC | 13 |
STB_HIPROC | 15 |
Symbol Types, ELF32_ST_TYPE
Name | Value |
STT_NOTYPE | 0 |
STT_OBJECT | 1 |
STT_FUNC | 2 |
STT_SECTION | 3 |
STT_FILE | 4 |
STT_LOPROC | 13 |
STT_HIPROC | 15 |
5)全局偏移表和跳转表
.plt和.got 动态链接的跳转表和全局入口表。
6)其它
代码段(.text): 代码数据
数据段(.data): 初始化过了的全局变量和局部静态变量
只读数据段(.rodata) 只读数据如const值、字符串常量。
.bss段:未初始化的全局变量和局部变量,是否为全局变量和局部变量预留空间和编译器的实现相关。
自定义段:在变量和函数前加__attribute__((section("name"))) 属性就可以把相应的函数或变量放到以name作为段名的段中。
4、ELF文件加载视图
虽然ELF把可变数据和不可变数据分的很细,用户也可以自己添加段或命名一个段,但加载器把ELF文件加载到内存时,并不按照ELF的结构读取。例如目标文件.o里的代码段.text是section,链接时多个可重定位文件整合成一个可执行的文件,为了提高程序的效率,链接器把目标文件中相同的section 整合成一个segment,方便运行时加载器加载程序。由于虚拟内存的映射和优化的存在,ELF文件在加载到虚拟内存时,也会合并不同的段来达到节约资源的目的。