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文件在加载到虚拟内存时,也会合并不同的段来达到节约资源的目的。
