ELF文件格式
在介绍ELF格式之前,先简单说明一下可执行文件的生成流程:1)编写C源文件,或汇编源文件;2)准备共享库格式的目标文件(shared object file),如数学库、标准库;2)用编译器(compiler)将C编译成可重定位格式的目标文件(relocatable object file),用汇编器(assembler)将汇编源文件编译成可重定位格式的目标文件;3)用连接器(linker)将第二步的共享个库文件和第三步生成的目标文件链接生成可执行文件(executable file)。
ELF(excutable and linking format)是一种可执行可链接格式的二进制文件,它可以用来表示relocatable file、executable file或者shared object file,这三者都是目标文件(object file)。所谓“可执行”指可以被调入内存供CPU直接运行;“可链接”指多个ELF格式的目标文件可以被链接在一起形成一个可执行文件。下图左边是可链接格式的ELF文件格式,右边是可执行格式的ELF文件格式。
无论是linking view还是execution view的ELF文件,他们都包含一个ELF Header,它包含文件的基本信息。ELF自定义了一些类型,并强制规定了他们所占的字节个数,以实现跨平台,如Elf32_Half占2字节、Elf32_Word占4字节、Elf32_Off占4字节等。


前面已经提到,Section header table有多个entry,其实每个entry都是一个Elf32_Shdr类型数据结构,用这个数据结构可以找到每个Section在文件中的位置,e_shentsize=sizeof(Elf32_Shdr),e_shnum=numof(Elf32_Shdr)。sh_name是一个字符串数组的索引,它指示当前Section header所描述的这个Section的名字在Section name string table中的位置。sh_type指示当前Section header的类型,SHT_NULL(0,该Section header所指示的section无效,不占用文件空间)/ SHT_PROGBITS(1,该Section header所指示的section包含程序指令)/ SHT_SYMTAB(2,该Section header所指示的section包含重定位符号表)/ SHT_STRTAB(3,该Section header所指示的section包含字符表)/ SHT_RELA(4,重定位相关,用于链接过程)/ SHT_HASH(5,重定位相关,用于链接过程)/ SHT_DYNAMIC(6,用于链接过程)/ SHT_NOTE(7,信息说明)/ SHT_NOBITS(8,,该Section header所指示的section不占用文件字节,除此之外和SHT_PROGBITS含义相同) /SHT_REL(9,重定位相关)/ SHT_SHLIB(10,保留类型)/ SHT_DYNSYM(11,重定位相关)/ SHT_LOPROC(0x70000000)- SHT_HIPROC(0x7fffffff)(这一范围的类型值跟CPU相关)/ SHT_LOUSER(0x80000000)- SHT_HIUSER(0xffffffff)(这一范围的类型值保留给应用程序)。sh_flags指示当前Section header所对应的section的属性,WRITE(0x1,当前Section header所对应的section包含可写的数据)/ALLOC(0x2,当前Section header所对应的section在程序执行时占用实际的存储空间)/EXECINSTR(0x4,当前Section header所对应的section包含可执行的指令)/MASKPROC(0xf0000000,保留)。sh_addr指示当前Section header所对应的section在内存中起始地址。sh_offset指示当前Section header所对应的section在文件中以字节为单位的偏移量。sh_size指示当前Section header所对应的section在文件中占用的字节数。sh_link和sh_info供链接器使用。sh_addralign指示对sh_addr的对其要求。有一些特殊的section,它内部仍然包含多个entry,每个entry大小相同,如symbol table,sh_entsize用于指示这个特殊的section中每个entry的大小。

上表列出了一些常见的section。.bss中,包含未初始化的全局数据变量,这些变量不占用ELF文件的空间(SHT_NOBITS),但占用实际内存空间(SHF_ALLOC),在程序运行启动时由OS负责初始化为0。.comment包含版本控制信息。.data和.data1包含已经初始化的全局数据变量,这些变量占用ELF文件空间,也占用实际内存空间。.fini包含主函数退出时执行的指令。.init包含主函数执行前所执行的指令。.rodata和.rodata1包含只读数据。.shstrtab包含section header string table。.text包含程序指令。
{
Elf32_Word p_type; /* Segment type */
Elf32_Off p_offset; /* Segment offset in file */
Elf32_Addr p_vaddr; /* Segment virtual address in memory*/
Elf32_Addr p_paddr; /* Segment physical address */
Elf32_Word p_filesz; /* Segment size in file */
Elf32_Word p_memsz; /* Segment size in memory */
Elf32_Word p_flags; /* Segment flags */
Elf32_Word p_align; /* Segment alignment */
} Elf32_Phdr;
前面已经提到,Program header table有多个entry,其实每个entry都是一个Elf32_Phdr类型数据结构,用这个数据结构可以找到每个Segment在文件中的位置,e_phentsize=sizeof(Elf32_Phdr),e_phnum=numof(Elf32_Phdr),每个Segment包含若干个section,只有对executable file和shared object file才能存在Program header。p_type指示当前Program header所指的Segment的类型,PT_NULL(0,指示无效的segment)/ PT_LOAD(1,指示当前Program header所指的Segment须加载到内存中执行)/ PT_DYNAMIC(2)/ PT_INTERP(3)/ PT_NOTE(4)/ PT_SHLIB(5)/ PT_PHDR(6)/PT_LOPROC(0x70000000)/PT_HIPROC(0x7fffffff)。p_offset指示Program header所指的Segment在文件中偏移量。p_vaddr指示Program header所指的Segment在内存中的虚拟地址。p_vaddr指示Program header所指的Segment在内存中的物理地址,一般可忽略。p_filesz指示Program header所指的Segment在文件中的大小。p_memsz指示Program header所指的Segment在内存中的大小,一般地,p_memsz>=p_filesz,对p_filesz不足的内存区域填0。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步