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和各种段组成,其结构图如下所示。详细文档可以查阅https://elinux.org/Executable_and_Linkable_Format_(ELF)。
2、ELF文件头
ELF 文件的最前面是文件头,描述了ELF文件的基本属性,比如ELF文件版本、目标机器型号、程序入口地址。
详细的描述可以参考:https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html
#define EI_NIDENT 16 typedef 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: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Position-Independent Executable file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x6180 Start of program headers: 64 (bytes into file) Start of section headers: 145256 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 11 Size of section headers: 64 (bytes) Number of section headers: 30 Section 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文件在加载到虚拟内存时,也会合并不同的段来达到节约资源的目的。
5、参考文献
1、Linux Referenced Specifications https://refspecs.linuxbase.org/
2、Executable and Linkable Format (ELF) https://elinux.org/Executable_and_Linkable_Format_(ELF)
3、Executable and Linkable Format - Wikipedia https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#Section_header
4、ELF文件格式 https://zhuanlan.zhihu.com/p/286088470
尊重原创技术文章,转载请注明: https://www.cnblogs.com/pingwen/p/17323862.html