# [ELF文件格式]

ELF是程序文件的一种类型，它并非由Linux研发，但Linux采用这种类型管理程序文件，具体又分为以下四类

1. 可执行文件，可以自己在操作系统中执行的程序。

2. 可重定向文件，不能自己在操作系统中执行的程序，其只有程序本身的数据，没有操作系统识别其各种属性以及辅助执行的数据，需要使用连接器添加属性信息、以及辅助执行代码制作为可执行文件才能在操作系统中执行，或用于被其它程序调用，与其它程序一起组合为可执行文件。

3. 动态连接库文件，类似可重定向文件，不能自己执行，需要被其它程序调用执行，但是在执行期间与其组合为一体

4. 核心转储文件，进程执行出错后操作系统将其终止执行，同时将进程在内存中的数据保存一份到辅存，用户可以读取核心转储文件查询进程终止前的执行状态与终止原因。
__静态连接库不归类于ELF文件__

## ELF文件内的数据可以分为四类：

1. 文件头，文件内容起始数据，用于说明文件类型、文件属性。

2. 程序节属性表，同节属性表，但只用于存储指令数据节、指令执行相关节的属性

3. 节，英文名为section，也有人称为段，程序的指令数据、数学数据、运行相关属性数据会按作用分为多组，每一组使用一个节存储。

4. 节属性表，是一个元素为结构体的数组，每个元素存储一个节的属性信息。

### 文件头
	文件头使用一个结构体定义，以64位ELF文件为例，文件头原型如下：
```c
	typedef struct
	{
		unsigned char e_ident[16];     
		//前4个字节数据分别为：7f 45 4c 46，第一个字节为删除键编码、后三个字节为ELF字母的编码，表示这是ELF文件
	    //第5个字节说明ELF文件运行在32位处理器还是64位处理器，值为1表示32位，值为2表示64位
	    //第6个字节说明数学数据使用的字节序，值为1表示小端序，值为2表示大端序
	    //第7个字节说明ELF文件的版本，目前只能设置为1
	    //第8个字节指定程序使用的ABI的类型，通常设置为0
	    //第9个字节指定程序使用的ABI的版本，通常设置为0
	    //第10-16字节保留不用，全部赋值为0
	
		u16  e_type;          //指定ELF文件的具体类型，值为1-4，1表示可重定向文件，2表示可执行文件，3表示动态连接库，4表示核心转储文件
		u16  e_machine;       //指定程序使用的CPU类型，值为62表示64位X86处理器，值为40表示32位ARM处理器，值为183表示64位ARM处理器
		u32  e_version;       //指定ELF文件的版本，目前只有一个版本，值固定为1
	
		u64  e_entry;         //指定程序执行入口虚拟地址，虚拟地址从0x400000开始算起，若为可重定向文件、动态连接库，则此值为0
		u64  e_phoff;         //指定程序节属性表的文件内部地址，文件内部地址从0开始分配，文件头占用最开始的地址，程序节属性表在之后，若e_phoff为0表示不包含程序节属性表
		u64  e_shoff;         //指定节属性表的文件内部地址，若为0表示不包含节属性表
	
		u32  e_flags;         //程序运行在特殊处理器中时使用此数据设置某些信息，运行在x86处理器中时此数据设置为0
	
		u16  e_ehsize;        //文件头的长度，以字节为单位
	
		u16  e_phentsize;     //程序节属性表的元素长度，以字节为单位
		u16  e_phnum;         //程序节属性表的元素数量
	
		u16  e_shentsize;     //节属性表的元素长度，以字节为单位
		u16  e_shnum;         //节属性表的元素数量
	
		u16  e_shstrndx;      //指定节属性表中哪个元素存储shstrtab节信息，值为元素下标
	} Elf64_Ehdr;	
```
+	e_ident的第8-9个字节设置ABI信息，ABI全称为Application Binary Interface，中文名为应用程序二进制接口，它是一组规则的名称，是操作系统为程序制定的运行规则，比如：多字节数据排序方式、函数调用时参数如何传递、函数返回值如何存储、系统调用使用规则、异常功能使用规则，CPU在设计时会为了兼容ABI的某些规则进行优化，提升程序运行速度

#### 一个 x86-64 ELF 可执行文件的文件头
```
	7F 45 4C 46  02 01 01 00  00 00 00 00  00 00 00 00    //e_ident
	02 00 3E 00  01 00 00 00                              //e_type - e_version
	40 10 40 00  00 00 00 00                              //e_entry
	40 00 00 00  00 00 00 00                              //e_phoff
	18 39 00 00  00 00 00 00                              //e_shoff
	00 00 00 00  40 00 38 00                              //e_flags - e_phentsize
	0B 00 40 00  1D 00 1C 00                              //e_phnum - e_shstrndx	
```

### 程序节属性表
+	程序节属性表紧邻文件头，是一个数组，元素为结构体，专用于存储程序节的属性
```c
	typedef struct
	{
		u32  p_type;      //指定节的类型
		u32  p_flags;     //设置节的访问权限，最低位设置执行权限、第2位设置写权限、第3位设置读权限，对应位设置为1表示有此权限，指令节设置为可执行，全局变量节设置为可读可写，全局常量节设置为只读
	
		u64  p_offset;    //节的文件内部地址
		u64  p_vaddr;     //节的虚拟地址
		u64  p_paddr;     //节的内存物理地址，某些特殊操作系统需要使用，在linux中不使用此数据
	
		u64  p_filesz;    //ELF文件在辅存中存储时，此节的长度
		u64  p_memsz;     //ELF文件读取到内存中时，此节的长度，有些节只在程序执行时才会存储数据，比如.bss节，这种节只在内存中分配存储空间
		u64  p_align;     //节的内存地址对齐值，若值为0或1等同于无对齐要求
	}Elf64_Phdr;
```
```
	06 00 00 00  04 00 00 00    //p_type、p_flags
	40 00 00 00  00 00 00 00    //p_offset
	40 00 40 00  00 00 00 00    //p_vaddr
	40 00 40 00  00 00 00 00    //p_paddr
	68 02 00 00  00 00 00 00    //p_filesz
	68 02 00 00  00 00 00 00    //p_memsz
	08 00 00 00  00 00 00 00    //p_align
```

### 节

__动态连接相关__
```
.interp，存储动态库加载器的路径，是一个字符串，ELF可执行文件才有此节。

.dynsym（Dynamic Symbol），存储动态库全局成员的属性，类似.strtab。

.dynstr（Dynamic String），存储动态库全局成员的名称，每个名称都是以空字符结尾的字符串，同时此节的第一个字节也定义为空字符。
```
__指令数据节__
```
.init，存储程序执行入口指令，用于程序执行前的基础初始化工作，代码由编译器自动生成，之后跳转到.text节执行，存储指令数据的节会被操作系统设置为只读。

.plt（procedure linkage table），调用动态库内数据相关，内部是由编译器生成的多个汇编代码模块。

.text，存储程序主体功能代码，程序执行前复杂的初始化工作代码在这里（编译器生成），用户自己编写的代码也存储在这里。

.fini，存储程序终止时执行的相关代码，代码由编译器生成
```
__数学数据节__
```
.rodata，存储全局常量，此节分配的内存页会被操作系统设置为只读。

.init_array，存储一组函数地址，这些函数会在main函数之前执行。

.fini_array，存储一组函数地址，这些函数会在main函数之后执行。

.dynamic，存储操作系统将程序加载到内存执行时需要使用的某些信息，比如程序需要使用哪些动态库、某些节的虚拟地址，具体数据的含义可使用 readelf -d 命令查看。

.got（global offset table），主要存储glibc两个函数的地址：__gmon_start__、__libc_start_main，此地址在ELF文件中存储0，程序执行时操作系统将gilbc读取到内存，之后将函数的具体地址写入.got。

.data，存储定义时已赋值的全局变量。

.bss，存储定义时未赋值的全局变量，编译后的文件此节不占用存储空间，程序读取到内存执行时才会为此节分配存储空间，此节占用的内存单元会全部设置为0，定义全局变量不赋值的话初始值为0
```
__编译器相关__
```
.comment，存储编译器版本信息，内部是一个字符串
```
__调试相关__
```
.debug，存储调试程序时需要的相关信息，供调试器使用，比如数据的类型、指令数据对应的高级语言源代码行号，使用gcc编译程序时添加 -g 参数会生成此节。
```

__全局成员相关__
```
.symtab（symbol table），程序中的全局变量、全局常量、函数这三者统称为symbol，有人将其翻译为符号，鉴于这种翻译完全不合理，这里将其称为全局成员，.symtab节用于存储所有全局成员的属性信息，比如数据类型、数据长度、成员名称（实际存储的是数据名在.strtab节的位置）、成员是否可被外部程序调用，每个全局成员的属性使用一个Elf64_Sym结构体存储，所有的Elf64_Sym结构体组成一个数组，这个数组就是.symtab节。

.strtab（string table），存储全局成员（symbol）的名称，对程序进行调试和反汇编时看见的数据名由此节记录，使用gcc编译程序时可以添加-s参数不保留这些数据名。对于C语言程序，此节记录的是数据名原型，对于C++程序，此节记录的并非原始数据名，因为C++支持函数重载、符号重载、虚拟类型、命名空间，导致多个数据可以同名，编译器会在原始数据名的前后分别添加随机字符从而区分同名数据，另外类成员、命名空间成员还会包含类名、命名空间名
```
__节名相关__
```
.shstrtab，存储其它节的名称，为节设置名称是编译器的工作，使用gcc编译程序时添加-s参数将不会生成节名称
```

### 节属性表
`程序节之外的节使用Elf64_Shdr结构体存储其属性，每个节的属性称为 section headers，简称sh，所有的 section headers 组合为节属性表。`
```c
	typedef struct
	{
		u32  sh_name;       //存储本节的名称在shstrtab节中的位置，若为0则表示此节没有名称
	
		u32  sh_type;		//节的类型，常用值如下：
			//SHT_NULL，值为0，无效节
			//SHT_PROGBITS，值为1，存储程序运行所用数据的节，比如.text、.data、.rodata，不包含.bss
			//SHT_SYMTAB，值为2，.symtab节
			//SHT_STRTAB，值为3，字符串表，包括 .strtab .shstrtab .dynstr
			//SHT_RELA，值为4，.rela节
			//SHT_HASH，值为5，.hash节
			//SHT_DYNAMIC，值为6，.dynamic节
			//SHT_NOTE，值为7，此节包含一些注释信息
			//SHT_NOBITS，值为8，此节不在ELF文件中，而是在程序执行期间分配内存空间，比如.bss
			//SHT_REL，值为9，.rel节
			//SHT_SHLIB，值为10，保留节，具体含义未定义
			//SHT_DYNSYM，值为11，.dynsym节
	
		u64  sh_flags;      //节的访问权限
	
		u64  sh_addr;       //节的虚拟地址，若为0则程序执行时此节不会读取到内存中，因为这些节只起辅助作用
		u64  sh_offset;     //节的文件内部地址
		u64  sh_size;       //节的长度，单位为字节
	
		u32  sh_link;         //节属性表中另一个元素的下标，连接器有时需要知道两个节的联系，若不需要与另一个节有关联则设置为0
		u32  sh_info;         //附加信息
		u64  sh_addralign;    //节的内存地址对齐值，若值为0或1则表示无对齐要求
		u64  sh_entsize;      //有些节的内容是一个数组，此成员存储数组元素的长度，若节的内容不是数组则设置为0
	} Elf64_Shdr;
```
```
// 一个 x86-64 ELF 可执行文件的 .shstrtab 节属性信息：

11 00 00 00 03 00 00 00    //sh_name - sh_type
00 00 00 00 00 00 00 00    //sh_flags
00 00 00 00 00 00 00 00    //sh_addr
2d 38 00 00 00 00 00 00    //sh_offset
03 01 00 00 00 00 00 00    //sh_size
00 00 00 00 00 00 00 00    //sh_link - sh_info
01 00 00 00 00 00 00 00    //sh_addralign
00 00 00 00 00 00 00 00    //sh_entsize
```

## RIP相对寻址
`在x86处理器中，指令使用32位内存地址读写内存，在x86-64处理器中，指令使用64位内存地址读写内存，64位处理器的mov指令使用直接内存寻址的方式读写内存数据时，若使用立即数存储要操作的内存地址则指令长度会超标（指令操作码 + 8字节内存地址 + 寄存器编号或4字节立即数），为此x86-64新增了一种内存寻址方式，称为RIP相对寻址，其让RIP寄存器增加一个值得出要操作的内存地址，就像跳转指令使用IP寄存器加一个立即数得出要跳转到的地址一样`

`RIP相对寻址用于程序的全局数据寻址，局部数据在栈中存储，栈空间数据不使用rip相对寻址，而是使用rsp、rbp确定要操作的地址`

```c
	#include <stdio.h>
	int a = 1;
	int main()
	{
		a = 9;
		printf("%d\n", a);
	
		return 0;
	}
```
```asm
	0000000000401122 <main>:
	  401122:	55                   			push   rbp
	  401123:	48 89 e5             			mov    rbp,rsp
	  401126:	c7 05 00 2f 00 00 09 00 00 00 	mov    DWORD PTR [rip+0x2f00],0x9    ;调用变量a（地址404030），rip现值 = 0x401130，0x401130 + 0x2f00 = 0x404030
	  401130:	8b 05 fa 2e 00 00    			mov    eax,DWORD PTR [rip+0x2efa]    ;变量a写入eax
	  401136:	89 c6                			mov    esi,eax
	  401138:	bf 04 20 40 00       			mov    edi,0x402004
	  40113d:	b8 00 00 00 00       			mov    eax,0x0
	  401142:	e8 e9 fe ff ff       			call   401030                        ;调用printf，rip加-0x117补码，向前跳转到0x401030
	  401147:	b8 00 00 00 00       			mov    eax,0x0
	  40114c:	5d                   			pop    rbp
	  40114d:	c3                   			ret
	
	Contents of section .data:
	 404020 00000000 00000000 00000000 00000000
	 404030 01000000
```
地址401126处的mov指令长度10字节，分别如下：

操作码，2字节，值为 05 c7。
地址码1，4字节，存储rip要加的数据，定位到操作的内存单元，值为 00 00 2f 00。
地址码2，4字节，存储要写入的立即数，值为 00 00 00 09。
rip与一个4字节有符号数相加，总共可以寻址约4GB内存空间，前后各2GB。
