ELF文件详解
ELF(Executable and Linkable Format)可执行和可链接格式是一种对象文件格式,分为三种类型:
a.可重定位目标文件:包含了适合用来链接其他目标文件的代码和数据,从而创建出可执行或可共享的目标文件(文件后缀.o)
b.可执行目标文件:包含用于执行的程序,该文件规定了exec如何创建一个程序的进程映像(文件后缀可有可无)
c.共享目标文件:包含用来在两个上下文之间链接的代码和数据(文件后缀.so)
注:可重定位目标文件和共享目标文件用于程序链接,可执行目标文件用于程序执行
因为对于目标文件两种用途,因此目标文件的组织结构有两种:一种是用于链接的链接格式,另一种是用于执行的执行格式.
ELF文件格式
ELF文件有32位版本和64位版本,故其头文件结构也有32位结构和64位结构,分别定义为Elf32_Ehdr和Elf64_Ehdr.两种版本文件内容一样,只是有些成员的大小不一样.以下是32位版本的文件头结构Elf32_Ehdr.
数据类型说明:
名称 | 大小 | 对齐 | 用途 |
---|---|---|---|
Elf32_Addr | 4 | 4 | 无符号程序地址 |
Elf32_Half | 2 | 2 | 无符号中等大小整数 |
Elf32_Off | 4 | 4 | 无符号文件偏移 |
Elf32_Sword | 4 | 4 | 有符号大整数 |
Elf32_Word | 4 | 4 | 无符号大整数 |
unsigned char | 1 | 1 | 无符号小整数 |
1、ELF文件头 (Elf32_Ehdr 52个字节)
#define EI_NIDENT 16
typedef struct elf32_hdr{
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type; /* file type */
Elf32_Half e_machine; /* architecture */
Elf32_Word e_version;
Elf32_Addr e_entry; /* entry point */
Elf32_Off e_phoff; /* PH table offset */
Elf32_Off e_shoff; /* SH table offset */
Elf32_Word e_flags;
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* PH size */
Elf32_Half e_phnum; /* PH number */
Elf32_Half e_shentsize; /* SH size */
Elf32_Half e_shnum; /* SH number */
Elf32_Half e_shstrndx; /* SH name string table index */
} Elf32_Ehdr;
文件类型
/* Legal values for e_type (object file type). */
#define ET_NONE 0 /* No file type */
#define ET_REL 1 /* Relocatable file */
#define ET_EXEC 2 /* Executable file */
#define ET_DYN 3 /* Shared object file */
#define ET_CORE 4 /* Core file */
2、Section Header (Elf32_Shdr 40个字节)
typedef struct {
Elf32_Word sh_name; /* name of section, index */
Elf32_Word sh_type;
Elf32_Word sh_flags;
Elf32_Addr sh_addr; /* memory address, if any */
Elf32_Off sh_offset;
Elf32_Word sh_size; /* section size in file */
Elf32_Word sh_link;
Elf32_Word sh_info;
Elf32_Word sh_addralign;
Elf32_Word sh_entsize; /* fixed entry size, if have */
} Elf32_Shdr;
/* Legal values for sh_flags (section flags). */
#define SHF_WRITE (1 << 0) /* Writable */
#define SHF_ALLOC (1 << 1) /* Occupies memory during execution */
#define SHF_EXECINSTR (1 << 2) /* Executable */
#define SHF_MERGE (1 << 4) /* Might be merged */
#define SHF_STRINGS (1 << 5) /* Contains nul-terminated strings */
/* Legal values for sh_type (section type). */
#define SHT_NULL 0 /* Section header table entry unused */
#define SHT_PROGBITS 1 /* Program data */
#define SHT_SYMTAB 2 /* Symbol table */
#define SHT_STRTAB 3 /* String table */
#define SHT_RELA 4 /* Relocation entries with addends */
#define SHT_HASH 5 /* Symbol hash table */
#define SHT_DYNAMIC 6 /* Dynamic linking information */
#define SHT_NOTE 7 /* Notes */
#define SHT_NOBITS 8 /* Program space with no data (bss) */
#define SHT_REL 9 /* Relocation entries, no addends */
#define SHT_SHLIB 10 /* Reserved */
#define SHT_DYNSYM 11 /* Dynamic linker symbol table */
#define SHT_INIT_ARRAY 14 /* Array of constructors */
#define SHT_FINI_ARRAY 15 /* Array of destructors */
#define SHT_PREINIT_ARRAY 16 /* Array of pre-constructors */
#define SHT_GROUP 17 /* Section group */
#define SHT_SYMTAB_SHNDX 18 /* Extended section indeces */
#define SHT_NUM 19 /* Number of defined types. */
3、Program Header (Elf32_Phdr 32个字节)
typedef struct elf32_phdr{
Elf32_Word p_type;
Elf32_Off p_offset;
Elf32_Addr p_vaddr; /* virtual address */
Elf32_Addr p_paddr; /* ignore */
Elf32_Word p_filesz; /* segment size in file */
Elf32_Word p_memsz; /* size in memory */
Elf32_Word p_flags;
Elf32_Word p_align;
} Elf32_Phdr;
4、Symbol Table (Elf32_Sym 16个字节 )
typedef struct elf32_sym{
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_info) 该成员低4位表示符号的类型(Symbol Type),高28位表示符号绑定信息(Symbol Binding)
绑定信息
/* Legal values for ST_BIND subfield of st_info (symbol binding). */
#define STB_LOCAL 0 /* Local symbol 局部符号,对于目标文件的外部不可见*/
#define STB_GLOBAL 1 /* Global symbol 全局符号,外部可见*/
#define STB_WEAK 2 /* Weak symbol 弱引用*/
#define STB_NUM 3 /* Number of defined types. */
#define STB_LOOS 10 /* Start of OS-specific */
#define STB_GNU_UNIQUE 10 /* Unique symbol. */
#define STB_HIOS 12 /* End of OS-specific */
#define STB_LOPROC 13 /* Start of processor-specific */
#define STB_HIPROC 15 /* End of processor-specific */
符号类型
/* Legal values for ST_TYPE subfield of st_info (symbol type). */
#define STT_NOTYPE 0 /* Symbol type is unspecified 未知类型符号*/
#define STT_OBJECT 1 /* Symbol is a data object 该符号是个数据对象,比如变量、数组等*/
#define STT_FUNC 2 /* Symbol is a code object 该符号是个函数或其他可执行代码*/
#define STT_SECTION 3 /* Symbol associated with a section 该符号表示一个段,这种符号必须是STB_LOCAL的*/
#define STT_FILE 4
/* Symbol's name is file name 该符号表示文件名,一般都是该目标文件所对应的源文件名,它一定是STB_LOCAL类型的,
并且它的st_shndx一定是SHN_ABS*/
#define STT_COMMON 5 /* Symbol is a common data object */
#define STT_TLS 6 /* Symbol is thread-local data object*/
#define STT_NUM 7 /* Number of defined types. */
#define STT_LOOS 10 /* Start of OS-specific */
#define STT_GNU_IFUNC 10 /* Symbol is indirect code object */
#define STT_HIOS 12 /* End of OS-specific */
#define STT_LOPROC 13 /* Start of processor-specific */
#define STT_HIPROC 15 /* End of processor-specific */
#define SHN_UNDEF 0
/* Undefined section
符号块未定义,在本目标文件被引用到,但是定义在其他目标文件中*/
#define SHN_ABS 0xfff1
/* Associated symbol is absolute
表示该符号包含了一个绝对的值。比如表示文件名的符号就属于这种类型的*/
#define SHN_COMMON 0xfff2
/* Associated symbol is common
表示该符号是一个“COMMON块”类型的符号,一般来说,未初始化的全局符号定义就是这种类型的
*/
readelf -a hello
nm命令还是比较简单而且强大的。以下是一些常见的符号类型
nm输出字符 | 含义 |
---|---|
R | Read only symbol. 比如在代码中有一个const MAXDATA = 3095; 则MAXDATA就是一个Read only symbol |
N | 这是一个调试符号 |
D | 这是一个已经初始化的变量的符号。比如代码中int i = 1和char *str = "Hello"则i和str都是这种类型的符号 |
T | Text段的符号。子程序都是这种符号,比如文件中实现了一个函数function,则function就是这种符号 |
U | 未定义的符号。如果文件中引用了不存在的函数,则这些未定义的函数符号就是这种类型 |
S | 未初始化的符号,比如全局变量int s;则s的符号就是此类型 |
Section
类型 | 含义 |
---|---|
.text | 已编译程序的机器代码 |
.rodata | 只读数据,如pintf和switch语句中的字符串和常量值 |
.data | 已初始化的全局变量 |
.bss | 未初始化的全局变量 |
.symtab | 符号表,存放在程序中被定义和引用的函数和全局变量的信息 |
.rel.text | 当链接器吧这个目标文件和其他文件结合时,.text节中的信息需修改 |
.rel.data | 被模块定义和引用的任何全局变量的信息 |
.debug | 一个调试符号表。 |
.line | 原始C程序的行号和.text节中机器指令之间的映射 |
.strtab | 一个字符串表,其内容包含.systab和.debug节中的符号表 |
ELF执行过程
Linux可执行文件类型的注册机制
为什么Linux可以运行ELF文件?
回答:内核对所支持的每种可执行的程序类型都有个struct linux_binfmt的数据结构
struct linux_binfmt {
struct linux_binfmt * next;
struct module *module;
int (*load_binary)(struct linux_binprm *, struct pt_regs * regs);
int (*load_shlib)(struct file *)
int (*core_dump)(long signr, struct pt_regs * regs, struct file * file);
unsigned long min_coredump; /* minimal dump size */
int hasvdso;
};
ELF中即为load_elf_binary
以我们的Hello World为例,gcc在编译时,除非显示的使用static标签,否则所有程序的链接都是动态链接的,也就是说需要解释器。由此可见,我们的Hello World程序在被内核加载到内存,内核跳到用户空间后并不是执行Hello World的,而是先把控制权交到用户空间的解释器,由解释器加载运行用户程序所需要的动态库(Hello World需要libc),然后控制权才会转移到用户程序
ELF文件中符号的动态解析过程
Global Offset Table(GOT)
在位置无关代码中,一般不能包含绝对虚拟地址(如共享库)
Procedure Linkage Table(PLT)
过程链接表(PLT)的作用就是将位置无关的函数调用转移到绝对地址
ELF文件加载和链接的简要总结
用户通过shell执行程序,shell通过exceve进入系统调用。(User-Mode)
sys_execve经过一系列过程,并最终通过ELF文件的处理函数load_elf_binary将用户程序和ELF解释器加载进内存,并将控制权交给解释器。(Kernel-Mode)
ELF解释器进行相关库的加载,并最终把控制权交给用户程序。由解释器处理用户程序运行过程中符号的动态解析
测试代码
/* hello.c */
#include <stdio.h>
int main()
{
printf(“hello world!\n”);
return 0;
}