剑指Offer--057-删除链表中重复的结点--http://blog.csdn.net/gatieme/article/details/51604199

This commit is contained in:
gatieme
2016-06-08 12:18:47 +08:00
parent c122ab9de9
commit 7a3a0d1402
@@ -266,151 +266,259 @@ ELF文件由各个部分组成。
包含了与各段相关的附加信息。
##ELF数据类型定义
##ELF基本数据类型定义
-------
在具体介绍ELF的格式之前,我们先来了解在ELF文件中都有哪些数据类型的定义:
ELF数据编码顺序与机器相关,为了使数据结构更加通用, linux内核自定义了几种通用的数据, 使得数据的表示与具体体系结构分离, 数据类型有六种
ELF数据编码顺序与机器相关,为了使数据结构更加通用, linux内核自定义了几种通用的数据, 使得数据的表示与具体体系结构分离
但是由于32位程序和64位程序所使用的数据宽度不同, 同时64位机必须兼容的执行32位程序, 因此我们所有的数据都被定义为32bit和64bit两个不同类型的数据
常规定义在[include/uapi/linux](http://lxr.free-electrons.com/source/include/uapi/linux/elf.h?v=4.5#L7)中, 各个结构也可以按照需求重新定义
**32位机器上的定义**
| 名称 | 常规定义 |大小 | 对齐 | 目的 |
| ------------- |:-------------:|:-------------:|:-------------:|:-------------:|
| Elf32_Addr | __u32 | 4 | 4 | 无符号程序地址 |
| Elf32_Half | __u16 | 2 | 2 | 无符号中等整数 |
| Elf32_Off | __u32 | 4 | 4 | 无符号文件偏移 |
| Elf32_SWord | __u32 | 4 | 4 | 有符号大整数 |
| Elf32_Word | __u32 | 4 | 4 | 无符号大整数 |
| unsigned char | 无 | 1 | 1 | 无符号小整数 |
*64位机器上的定义**
| 名称 | 常规定义 |大小 | 对齐 | 目的 |
| ------------- |:-------------:|:-------------:|:-------------:|:-------------:|
| Elf64_Addr | __u64 | 8 | 8 | 无符号程序地址 |
| Elf64_Half | __u16 | 2 | 2 | 无符号小整数 |
| Elf64_SHalf | __s16 | 2 | 2 | 无符号小整数
| Elf64_Off | __u64 | 8 | 8 | 无符号文件偏移 |
| Elf64_Sword | __s32 | 4 |4 | 有符号中等整数 |
| Elf64_Word | __u32 | 4 | 4 | 无符号中等整数 |
| Elf64_Xword | __u64 | 8 | 8 | 无符号大整数 |
| Elf64_Sxword | __s64 | 8 | 8 | 有符号大整数 |
| unsigned char | 无 | 1 | 1 | 无符号小整数 |
#ELF头部Elfxx_Ehdr
-------
elf头部用Elfxx_Ehdr结构(被定义在[linux/uapi/linux/elf.h](http://lxr.free-electrons.com/source/include/uapi/linux/elf.h?v=4.5#L200)来表示, [Elf32_Ehdr(32bit)](http://lxr.free-electrons.com/source/include/uapi/linux/elf.h?v=4.5#L203)和[Elf64_Ehdr(64bit)](http://lxr.free-electrons.com/source/include/uapi/linux/elf.h?v=4.5#L220)
##数据成员
-------
内部成员, 如下
| 成员 | 类型 | 描述 |
| ------------- |:-------------:|:-------------:|
| e_ident[EI_NIDENT] | unsigned char |目标文件标识信息, 即"魔数", EI_NIDENT=16, 因此共占用128位 |
| e_type | Elf32_Half/Elf64_Half | 目标文件类型 |
| e_machine | Elf32_Half/Elf64_Half | 目标体系结构类型 |
| e_version | Elf32_Word/Elf64_Word | 目标文件版本 |
| e_entry | Elf32_Addr/Elf64_Addr | 程序入口的虚拟地址,若没有,可为0 |
| e_phoff | Elf32_Off/Elf64_Off | 程序头部表格(Program Header Table)的偏移量(按字节计算),若没有,可为0 |
| e_shoff | Elf32_Off/Elf64_Off | 节区头部表格(Section Header Table)的偏移量(按字节计算),若没有,可为0 |
| e_flags | Elf32_Word/Elf64_Word | 保存与文件相关的,特定于处理器的标志。标志名称采用 EF_machine_flag的格式 |
| e_ehsize | Elf32_Half/Elf64_Half | ELF 头部的大小(以字节计算) |
| e_phentsize | Elf32_Half/Elf64_Half | 程序头部表格的表项大小(按字节计算) |
| e_phnum | Elf32_Half/Elf64_Half | 程序头部表格的表项数目。可以为 0 |
| e_shentsize | Elf32_Half/Elf64_Half | 节区头部表格的表项大小(按字节计算) |
| e_shnum | Elf32_Half/Elf64_Half | 节区头部表格的表项数目。可以为 0 |
| e_shstrndx | Elf32_Half/Elf64_Half | 节区头部表格中与节区名称字符串表相关的表项的索引。如果文件没有节区名称字符串表,此参数可以为 SHN_UNDEF |
| 名称 | 大小 | 对齐 | 目的 |
##魔数e_ident
-------
其中需要注意地是e_ident是一个16字节的数组,这个数组按位置从左到右都是有特定含义,每个数组元素的下标在标准中还存在别称,如byte0的下标0别名为EI_MAG0,具体如下:
| 名称 | 元素下标值 | 含义 |
| ------------- |:-------------:|:-------------:|
| EI_MAG0 | 0 | 文件标识 |
| EI_MAG1 | 1 | 文件标识 |
| EI_MAG2 | 2 | 文件标识 |
| EI_MAG3 | 3 | 文件标识 |
| EI_CLASS | 4 | 文件类 |
| EI_DATA | 5 | 数据编码 |
| EI_VERSION | 6 | 文件版本 |
| EI_PAD | 7 | 补齐字节开始处 |
| EI_NIDENT | 16 | e_ident[]大小 |
e_ident[EI_MAG0]~e_ident[EI_MAG3]即e_ident[0]~e_ident[3]被称为魔数(Magic Number,其值一般为0x7f,'E','L','F'
e_ident[EI_CLASS](即e_ident[4])识别目标文件运行在目标机器的类别,取值可为三种值:ELFCLASSNONE0)非法类别;ELFCLASS32132位目标;ELFCLASS64264位目标。
e_ident[EI_DATA](即e_ident[5]):给出处理器特定数据的数据编码方式。即大端还是小端方式。取值可为3种:ELFDATANONE0)非法数据编码;ELFDATA2LSB1)高位在前;ELFDATA2MSB2)低位在前
##目标文件类型e_type
-------
e_type表示elf文件的类型,如下定义:
| 名称 | 取值 | 含义 |
| ------------- |:-------------:|:-------------:|
| ET_NONE | 0 | 未知目标文件格式 |
| ET_REL | 1 | 可重定位文件 | |
| ET_EXEC | 2 | 可执行文件 |
| ET_DYN | 3 | 共享目标文件 |
| ET_CORE | 4 | Core 文件(转储格式) |
| ET_LOPROC | 0xff00 | 特定处理器文件 |
| ET_HIPROC | 0xffff | 特定处理器文件 |
| ET_LOPROC~ET_HIPROC | 0xff00~0xffff | 特定处理器文件 |
##目标体系结构类型e_machine
-------
e_machine表示目标体系结构类型:
| 名称 | 取值 | 含义 |
| ------------- |:-------------:|:-------------:|
| EM_NONE | 0 | 未指定 |
| EM_M32 | 1 | AT&T WE 32100 |
| EM_SPARC | 2 | SPARC |
| EM_386 | 3 | Intel 80386 |
| EM_68K | 4 | Motorola 68000 |
| EM_88K | 5 | Motorola 88000
| EM_860 | 7 | Intel 80860 |
| EM_MIPS | 8 | MIPS RS3000 |
| others | 9~ | 预留 |
#节区(Sections
-------
节区中包含目标文件中的所有信息,除了:ELF 头部、程序头部表格、节区头部表格。节区满足以下条件:
1. 目标文件中的每个节区都有对应的节区头部描述它,反过来,有节区头部不意味着有节区。
2. 每个节区占用文件中一个连续字节区域(这个区域可能长度为 0)。
3. 文件中的节区不能重叠,不允许一个字节存在于两个节区中的情况发生。
4. 目标文件中可能包含非活动空间(INACTIVE SPACE)。这些区域不属于任何头部和节区,其内容未指定。
##节区头部表格
-------
ELF 头部中,
e_shoff 成员给出从文件头到节区头部表格的偏移字节数;
e_shnum给出表格中条目数目;
e_shentsize 给出每个项目的字节数。
从这些信息中可以确切地定位节区的具体位置、长度。
从之前的描述中可知,每一项节区在节区头部表格中都存在着一项元素与它对应,因此可知,这个节区头部表格为一连续的空间,每一项元素为一结构体
那么这个节区头部由elfxx_shdr(定义在[include/uapi/linux/elf.h](http://lxr.free-electrons.com/source/include/uapi/linux/elf.h?v=4.5#L8), 32位elf32_shdr 64位elf64_shdr
结构体的成员如下
| 成员 | 类型 | 描述 |
| ------------- |:-------------:|:-------------:|
| sh_name | Elf32_Word/Elf64_Word | 节区名,是节区头部字符串表节区(Section Header String Table Section)的索引。名字是一个 NULL 结尾的字符串 |
| sh_type | Elf32_Word/Elf64_Word | 节区类型 |
| sh_flags | Elf32_Word/Elf64_Word | 节区标志 |
| sh_addr | Elf32_Addr/Elf64_Addr | 如果节区将出现在进程的内存映像中,此成员给出节区的第一个字节应处的位置。否则,此字段为 0 |
| sh_offset | Elf32_Off/Elf64_Off | 此成员的取值给出节区的第一个字节与文件头之间的偏移 |
| sh_size | Elf32_Word/Elf64_Word | 此成员给出节区的长度(字节数) |
| sh_link | Elf32_Word/Elf64_Word | 此成员给出节区头部表索引链接。其具体的解释依赖于节区类型 |
| sh_info | Elf32_Word/Elf64_Word | 此成员给出附加信息,其解释依赖于节区类型 |
| sh_addralign | Elf32_Word/Elf64_Word | 某些节区带有地址对齐约束 |
| sh_entsize | Elf32_Word/Elf64_Word | 某些节区中包含固定大小的项目,如符号表。对于这类节区,此成员给出每个表项的长度字节数 |
###节区类型sh_type
-------
sh_type的取值如下:
| 名称 | 取值 | 说明 |
| ------------- |:-------------:|:-------------:|
| SHT_NULL | 0 | 此值标志节区头部是非活动的,没有对应的节区。此节区头部中的其他成员取值无意义 |
| SHT_PROGBITS | 1 | 此节区包含程序定义的信息,其格式和含义都由程序来解释 |
| SHT_SYMTAB | 2 | 此节区包含一个符号表。目前目标文件对每种类型的节区都只能包含一个,不过这个限制将来可能发生变化<br>一般,SHT_SYMTAB 节区提供用于链接编辑(指 ld 而言)的符号,尽管也可用来实现动态链接 |
| SHT_STRTAB | 3 | 此节区包含字符串表。目标文件可能包含多个字符串表节区 |
| SHT_RELA | 4 | 此节区包含重定位表项,其中可能会有补齐内容(addend),例如 32 位目标文件中的 Elf32_Rela 类型。目标文件可能拥有多个重定位节区 |
| SHT_HASH | 5 | 此节区包含符号哈希表。所有参与动态链接的目标都必须包含一个符号哈希表。目前,一个目标文件只能包含一个哈希表,不过此限制将来可能会解除 |
| SHT_DYNAMIC | 6 | 此节区包含动态链接的信息。目前一个目标文件中只能包含一个动态节区,将来可能会取消这一限制 |
| SHT_NOTE | 7 | 此节区包含以某种方式来标记文件的信息 |
| SHT_NOBITS | 8 | 这种类型的节区不占用文件中的空间 , 其他方面和SHT_PROGBITS相似。尽管此节区不包含任何字节,成员sh_offset 中还是会包含概念性的文件偏移 |
| SHT_REL | 9 | 此节区包含重定位表项,其中没有补齐(addends),例如 32 位目标文件中的 Elf32_rel 类型。目标文件中可以拥有多个重定位节区 |
| SHT_SHLIB | 10 | 此节区被保留,不过其语义是未规定的。包含此类型节区的程序与 ABI 不兼容 |
| SHT_DYNSYM | 11 | 作为一个完整的符号表,它可能包含很多对动态链接而言不必要的符号。因此,目标文件也可以包含一个 SHT_DYNSYM 节区,其中保存动态链接符号的一个最小集合,以节省空间 |
| SHT_LOPROC | X70000000 | 这一段(包括两个边界),是保留给处理器专用语义的 |
| SHT_HIPROC | OX7FFFFFFF | 这一段(包括两个边界),是保留给处理器专用语义的 |
| SHT_LOUSER | 0X80000000 | 此值给出保留给应用程序的索引下界 |
| SHT_HIUSER | 0X8FFFFFFF | 此值给出保留给应用程序的索引上界 |
###节区标志sh_flags
-------
sh_flag标志着此节区是否可以修改,是否可以执行,如下定义:
| 名称 | 取值 | 含义 |
| ------------- |:-------------:|:-------------:|
| SHF_WRITE | 0x1 | 节区包含进程执行过程中将可写的数据 |
| SHF_ALLOC | 0x2| 此节区在进程执行过程中占用内存。某些控制节区并不出现于目标文件的内存映像中,对于那些节区,此位应设置为 0 |
| SHF_EXECINSTR | 0x4 | 节区包含可执行的机器指令 |
| SHF_MASKPROC | 0xF0000000 | 所有包含于此掩码中的四位都用于处理器专用的语义 |
###sh_link 和 sh_info 字段
-------
sh_link和sh_info字段的具体含义依赖于sh_type的值
| sh_type | sh_link | sh_info |
| ------------- |:-------------:|:-------------:|
| SHT_DYNAMIC | 此节区中条目所用到的字符串表格的节区头部索引 | 0 |
| SHT_HASH | 此哈希表所适用的符号表的节区头部索引 | 0 |
| SHT_REL<br>SHT_RELA | 相关符号表的节区头部索引 | 重定位所适用的节区的节区头部索引 |
| SHT_SYMTAB<br>SHT_DYNSYM | 相关联的字符串表的节区头部索引 | 最后一个局部符号(绑定 STB_LOCAL)的符号表索引值加一 |
| 其它 | SHN_UNDEF | 0 |
##特殊节区
-------
有些节区是系统预订的,一般以点开头号,因此,我们有必要了解一些常用到的系统节区。
| 名称 | 类型 | 属性 | 含义 |
| ------------- |:-------------:|:-------------:|:-------------:|
| Elf32_Addr | 4 | 4 | 无符号程序地址 |
| Elf32_Half | 2 | 2 | 无符号中等整数 |
| Elf32_Off | 4 | 4 | 无符号文件偏移 |
| Elf32_SWord | 4 | 4 | 有符号大整数 |
| Elf32_Word | 4 | 4 | 无符号大整数 |
| unsigned char | 1 | 1 | 无符号小整数 |
http://lxr.free-electrons.com/source/include/uapi/linux/elf.h?v=4.5#L218
##ELF头部
-------
#execve系统调用
-------
##execve系统调用
-------
我们前面提到了, fork, vfork等复制出来的进程是父进程的一个副本, 那么如何我们想加载新的程序, 可以通过execve来加载和启动新的程序。
>x86架构下, 其实还实现了一个新的exec的系统调用叫做execveat(自linux-3.19后进入内核)
>
>[syscalls,x86: Add execveat() system call](http://lwn.net/Articles/600344)
##exec()函数族
-------
exec函数一共有六个,其中execve为内核级系统调用,其他(execlexecleexeclpexecvexecvp)都是调用execve的库函数。
```c
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,
..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
```
#ELF文件格式以及可执行程序的表示
-------
##struct linux_bin_rpm结构描述一个可执行程序
-------
linux_binprm是定义在[include/linux/binfmts.h](http://lxr.free-electrons.com/source/include/linux/binfmts.h#L14)中, 用来保存要要执行的文件相关的信息
```c
/*
* This structure is used to hold the arguments that are used when loading binaries.
*/
struct linux_binprm {
char buf[BINPRM_BUF_SIZE]; // 保存可执行文件的头128字节
#ifdef CONFIG_MMU
struct vm_area_struct *vma;
unsigned long vma_pages;
#else
# define MAX_ARG_PAGES 32
struct page *page[MAX_ARG_PAGES];
#endif
struct mm_struct *mm;
unsigned long p; /* current top of mem , 当前内存页最高地址*/
unsigned int
cred_prepared:1,/* true if creds already prepared (multiple
* preps happen for interpreters) */
cap_effective:1;/* true if has elevated effective capabilities,
* false if not; except for init which inherits
* its parent's caps anyway */
#ifdef __alpha__
unsigned int taso:1;
#endif
unsigned int recursion_depth; /* only for search_binary_handler() */
struct file * file; /* 要执行的文件 */
struct cred *cred; /* new credentials */
int unsafe; /* how unsafe this exec is (mask of LSM_UNSAFE_*) */
unsigned int per_clear; /* bits to clear in current->personality */
int argc, envc; /* 命令行参数和环境变量数目 */
const char * filename; /* Name of binary as seen by procps, 要执行的文件的名称 */
const char * interp; /* Name of the binary really executed. Most
of the time same as filename, but could be
different for binfmt_{misc,script} 要执行的文件的真实名称,通常和filename相同 */
unsigned interp_flags;
unsigned interp_data;
unsigned long loader, exec;
};
```
##struct linux_binfmt可执行程序的结构
-------
linux内核对所支持的每种可执行的程序类型都有个struct linux_binfmt的数据结构,定义如下
>linux_binfmt定义在[include/linux/binfmts.h](http://lxr.free-electrons.com/source/include/linux/binfmts.h#L74)中
```c
/*
* This structure defines the functions that are used to load the binary formats that
* linux accepts.
*/
struct linux_binfmt {
struct list_head lh;
struct module *module;
int (*load_binary)(struct linux_binprm *);
int (*load_shlib)(struct file *);
int (*core_dump)(struct coredump_params *cprm);
unsigned long min_coredump; /* minimal dump size */
};
```
这里的linux_binfmt对象包含了一个单链表,这个单链表中的第一个元素的地址存储在formats这个变量中list_for_each_entry依次应用load_binary的方法,同时我们可以看到这里会有递归调用,bprm会记录递归调用的深度。
装载ELF可执行程序的load_binary的方法叫做load_elf_binary方法,下面会进行具体分析
|.bss | SHT_NOBITS | SHF_ALLOC +SHF_WRITE | 包含将出现在程序的内存映像中的为初始化数据。根据定义,当程序开始执行,系统将把这些数据初始化为 0。此节区不占用文件空间 |
| .comment | SHT_PROGBITS | (无) | 包含版本控制信息 |
| .data | SHT_PROGBITS | SHF_ALLOC + SHF_WRITE | 这些节区包含初始化了的数据,将出现在程序的内存映像中 |
| .data1 | SHT_PROGBITS | SHF_ALLOC + SHF_WRITE | 这些节区包含初始化了的数据,将出现在程序的内存映像中 |
| .debug | SHT_PROGBITS | (无) | 此节区包含用于符号调试的信息 |
| .dynamic | SHT_DYNAMIC | 此节区包含动态链接信息。节区的属性将包含 SHF_ALLOC 位。是否 SHF_WRITE 位被设置取决于处理器 |
| .dynstr | SHT_STRTAB | SHF_ALLOC | 此节区包含用于动态链接的字符串,大多数情况下这些字符串代表了与符号表项相关的名称 |
| .dynsym | SHT_DYNSYM | SHF_ALLOC | 此节区包含了动态链接符号表 |
| .fini | SHT_PROGBITS | SHF_ALLOC + SHF_EXECINSTR | 此节区包含了可执行的指令,是进程终止代码的一部分。程序正常退出时,系统将安排执行这里的代码 |
| .got | SHT_PROGBITS | 此节区包含全局偏移表 |
| .hash | SHT_HASH | SHF_ALLOC | 此节区包含了一个符号哈希表 |
| .init | SHT_PROGBITS | SHF_ALLOC +SHF_EXECINSTR | 此节区包含了可执行指令,是进程初始化代码的一部分。当程序开始执行时,系统要在开始调用主程序入口之前(通常指 C 语言的 main 函数)执行这些代码 |
| .interp | SHT_PROGBITS | 此节区包含程序解释器的路径名。如果程序包含一个可加载的段,段中包含此节区,那么节区的属性将包含 SHF_ALLOC 位,否则该位为 0 |
| .line | SHT_PROGBITS | (无) | 此节区包含符号调试的行号信息,其中描述了源程序与机器指令之间的对应关系。其内容是未定义的 |
| .note | SHT_NOTE | (无) | 此节区中包含注释信息,有独立的格式。
| .plt | SHT_PROGBITS | 此节区包含过程链接表(procedure linkage table)。
| .relname
| .relaname SHT_REL
SHT_RELA 这些节区中包含了重定位信息。如果文件中包含可加载的段,段中有重定位内容,节区的属性将包含 SHF_ALLOC 位,否则该位置 0。传统上 name 根据重定位所适用的节区给定。例如 .text 节区的重定位节区名字将是:.rel.text 或者 .rela.text。
| .rodata
| .rodata1 SHT_PROGBITS SHF_ALLOC 这些节区包含只读数据,这些数据通常参与进程映像的不可写段。
.shstrtab SHT_STRTAB 此节区包含节区名称。
.strtab SHT_STRTAB 此节区包含字符串,通常是代表与符号表项相关的名称。如果文件拥有一个可加载的段,段中包含符号串表,节区的属性将包含SHF_ALLOC 位,否则该位为 0。
.symtab SHT_SYMTAB 此节区包含一个符号表。如果文件中包含一个可加载的段,并且该段中包含符号表,那么节区的属性中包含SHF_ALLOC 位,否则该位置为 0。
.text SHT_PROGBITS SHF_ALLOC +
SHF_EXECINSTR 此节区包含程序的可执行指令