0. 简介
在linux系统的可执行文件(ELF文件)中,开头是一个文件头,用来描述程序的布局,整个文件的属性等信息,包括文件是否可执行、静态还是动态链接及入口地址等信息;如下图所示:
程序文件中包含了程序头,程序的入口地址等信息不需要写死,调用代码可以通用,根据实际情况加载;此时的文件不是纯碎的二进制可执行文件了,因为包含的程序头不是可执行代码;将这种包含程序头的文件读入到内存后,从程序头中读取入口地址,跳转到入口地址执行;
0.1 文件格式
linux系统中用C语言编写完代码,使用编译器gcc编译生成的是ELF格式的二进制文件;
ELF指:Executable and Linkable Format,可执行链接格式;本文中的目标文件指各种类型符合ELF规范的文件,如:二进制可执行文件、Linux下的.o目标文件和.so动态库文件;
可执行文件(Executable file):经过编译链接后,可以直接运行的程序文件;
共享目标文件(Shared object file):动态链接库,在可执行文件被加载的过程中动态链接,成为程序代码的一部分;
可重定位文件(Relocatable file):可重定位文件即目标文件,是源文件编译后但未完成链接的半成品,被用于与其他目标文件合并链接,以构建出二进制可执行文件或动态链接库;
核心转储文件(Core dump file):当进程意外终止时,系统可以将该进程的地址空间的内容及终止时的一些信息转储到核心转储文件;
0.2 段和节
程序中的段(Segment)和节(Secton)是真正的程序体;段包括代码段和数据段等,段是由节组成的,多个节经过链接后被合并为一个段;
段和节的信息用header描述,程序头是program header,节头是section header;段和节的大小和数量都是不固定的,需要用专门的数据结构来描述,即程序头表(program header table)和节头表(section header table),这是两个数组,元素分别是程序头(program header)和节头(section header);程序头表(program header table)中的元素全是程序头(program header),节头表(section header table)中的元素全是节头(section header);程序头表是用来描述段(Segment)的,成为段头表;段是程序本身的组成部分;
由于段和节的大小和数量都是不固定的,程序头表和节头表的大小也不固定,两个表在程序文件中的位置也不固定;需要在一个固定的位置,用一个固定大小的数据结构来描述程序头表和节头表的大小和位置信息,即位于文件最开始部分的ELF header;
1. ELF header
ELF文件分为文件头和文件体两部分;先用ELF header从文件全局概要出程序中程序头表、节头表的位置和大小等信息;然后从程序头表和节头表中分别解析出各个段和节的位置和大小等信息;
可执行文件和待重定位文件,文件最开头的部分是ELF header;程序头表对于可执行文件是必须的,而对于待重定位文件是可选的;
ELF文件头用Elf32_Ehdr和Elf64_Ehdr结构体表示;
// include/uapi/linux/elf.h
#define EI_NIDENT 16
typedef struct elf32_hdr{ unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type; Elf32_Half e_machine; Elf32_word e_version; Elf32_Addr e_entry; /* Entry point */ 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;typedef struct elf64_hdr { unsigned char e_ident[EI_NIDENT]; /* ELF "magic number" */
Elf64_Half e_type; Elf64_Half e_machine; Elf64_Word e_version; Elf64_Addr e_entry; /* Entry point virtual address */ Elf64_Off e_phoff; /* Program header table file offset */
Elf64_Off e_shoff; /* Section header table file offset */
Elf64_Word e_flags; Elf64_Half e_ehsize; Elf64_Half e_phentsize; Elf64_Half e_phnum; Elf64_Half e_shentsize; Elf64_Half e_shnum; Elf64_Half e_shstrndx;} Elf64_Ehdr;
e_ident[EI_NIDENT]是16字节大小的数组,用来表示ELF字符等信息,开头四个字节是固定不变的elf文件魔数,0x7f 0x45 0x4c 0x46;
e_type:2字节,用来指定ELF目标文件的类型;
// include/uapi/linux/elf-em.h
/* These constants define the various ELF target machines */
#define EM_NONE 0
#define EM_M32 1
#define EM_SPARC 2
#define EM_386 3
#define EM_68K 4
#define EM_88K 5
#define EM_486 6 /* Perhaps disused */
#define EM_860 7
#define EM_MIPS 8 /* MIPS R3000 (officially, big-endian only) */
/* Next two are historical and binaries and
modules of these types will be rejected by Linux. */
#define EM_MIPS_RS3_LE 10 /* MIPS R3000 little-endian */
#define EM_MIPS_RS4_BE 10 /* MIPS R4000 big-endian */
#define EM_PARISC 15 /* HPPA */
#define EM_SPARC32PLUS 18 /* Sun's "v8plus" */
#define EM_PPC 20 /* PowerPC */
......
e_machine:2字节,用来描述ELF目标文件的体系结构类型,即要在哪种硬件平台运行;
// include/uapi/linux/elf-em.h
/* These constants define the various ELF target machines */
#define EM_NONE 0
#define EM_M32 1
#define EM_SPARC 2
#define EM_386 3
#define EM_68K 4
#define EM_88K 5
#define EM_486 6 /* Perhaps disused */
#define EM_860 7
#define EM_MIPS 8 /* MIPS R3000 (officially, big-endian only) */
/* Next two are historical and binaries and
modules of these types will be rejected by Linux. */
#define EM_MIPS_RS3_LE 10 /* MIPS R3000 little-endian */
#define EM_MIPS_RS4_BE 10 /* MIPS R4000 big-endian */
#define EM_PARISC 15 /* HPPA */
#define EM_SPARC32PLUS 18 /* Sun's "v8plus" */
#define EM_PPC 20 /* PowerPC */
......
2. 程序头表
程序头表(也称为段表)是一个描述文件中各个段的数组,程序头表描述了文件中各个段在文件中的偏移位置及段的属性等信息;从程序头表里可以得到每个段的所有信息,包括代码段和数据段等;各个段的内容紧跟ELF文件头保存;程序头表中各个段用Elf32_Phdr或Elf64_Phdr结构体表示;
// include/uapi/linux/elf.h
typedef struct elf32_phdr{ Elf32_Word p_type; Elf32_Off p_offset; Elf32_Addr p_vaddr; Elf32_Addr p_paddr; Elf32_Word p_filesz; Elf32_Word p_memsz; Elf32_Word p_flags; Elf32_Word p_align;} Elf32_Phdr;typedef struct elf64_phdr { Elf64_Word p_type; Elf64_Word p_flags; Elf64_Off p_offset; /* Segment file offset */
Elf64_Addr p_vaddr; /* Segment virtual address */
Elf64_Addr p_paddr; /* Segment physical address */
Elf64_Xword p_filesz; /* Segment size in file */
Elf64_Xword p_memsz; /* Segment size in memory */
Elf64_Xword p_align; /* Segment alignment, file & memory */
} Elf64_Phdr;
Elf32_Phdr或Elf64_Phdr结构体用来描述位于磁盘上的程序中的一个段;
p_type:4字节,用来指明程序中该段的类型;
// include/uapi/linux/elf.h
/* These constants define the permissions on sections in the program header, p_flags. */
#define PF_R 0x4 // 可读
#define PF_W 0x2 // 可写
#define PF_X 0x1 // 可执行
p_flags:4字节,用来指明与本段相关的标志;
// include/uapi/linux/elf.h
/* These constants define the permissions on sections in the program header, p_flags. */
#define PF_R 0x4 // 可读
#define PF_W 0x2 // 可写
#define PF_X 0x1 // 可执行
3. 节头表
节头表中各个节用Elf32_Shdr或Elf64_Shdr结构体表示;
// include/uapi/linux/elf.h
typedef struct elf32_shdr { 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;typedef struct elf64_shdr { Elf64_Word sh_name; /* Section name, index in string tbl */
Elf64_Word sh_type; /* Type of section */
Elf64_Xword sh_flags; /* Miscellaneous section attributes */
Elf64_Addr sh_addr; /* Section virtual addr at execution */
Elf64_Off sh_offset; /* Section file offset */
Elf64_Xword sh_size; /* Size of section in bytes */
Elf64_Word sh_link; /* Index of another section */
Elf64_Word sh_info; /* Additional section information */
Elf64_Xword sh_addralign; /* Section alignment */
Elf64_Xword sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;
Elf32_Shdr或Elf64_Shdr结构体用来描述位于磁盘上的程序中的一个节;
sh_name
节名称sh_name的值是字符串的一个索引,节名称字符串以'