ELF格式文件

1. ELF格式

ELF(Executable Linkable Format)格式是Linux中的可执行文件格式。在Linux中,可执行文件、目标文件、动态链接库(linux的.so)、静态链接库(linux下的.a文件)都是ELF格式的文件。

ELF文件标准里面把ELF格式文件分为了4类。可重定位文件(relocatable file)、可执行文件(executable file)、共享目标文件(shared object file)、核心转储文件(core dump file)。可以在bash中用file命令来查看相应的文件格式。

$ file a.o
a.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

$ file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=697f760e13fbf75e42a80bbfb459678d805d51cc, not stripped

$ file liba.so
liba.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, BuildID[sha1]=915c865179f6bb09fe6c36c3649e816fb986a1e1, not stripped

2. Section和Segment

正确理解"Section"和"Segment"的概念有助于我们理解目标文件和可执行文件。从本质上讲,"Section"和"Segment"都是ELF文件中的一小块区域。"Section"是相对于目标文件的一个概念,一个目标文件由ELF头和若干Section组成,如目标文件中的代码段、数据段、BSS段等。"Segment"是相对于可执行文件的一个概念。我们知道,可执行文件是由多个目标文件链接起来的,链接器会将目标文件中具有相同性质的段合并到一起,如将各个目标文件中的.text段合并成一个.text段,将各个.data段合并成一个.data段。此时,这些合并后的.text段、.data段以Section的形式存放在可执行文件中。那么为什么要在可执行文件中引入Segment的概念呢?因为直接将可执行文件按Section为划分单位映射到虚拟内存空间会浪费大量的内存空间,因为虚拟内存是按页对齐的。而操作系统在将可执行文件加载到虚拟内存空间时,往往只关心每个Section的权限(可读、可写、可执行),于是大佬们决定把具有相同权限的Section合并成一个Segment映射到虚拟内存空间。因此,Segment的概念实际上是从装载的角度重新划分了ELF的各个Section,链接器会尽量将具有相同权限的Section分配在同一个空间,这些Section被称作为一个Segment,操作系统正是按照Segment来将可执行文件映射到虚拟内存空间的,而不是按Section。

3. 目标文件

目标文件是ELF格式的文件,整个目标文件被分成多个具有一定长度的区域,每个区域被称为段,这里的段指的是一个Section。例如,.text段就是目标文件中用来存放程序源代码的地方,这些源代码被翻译成机器指令存放在代码段。.data段是用来存放初始化的全局变量和局部静态变量的地方。

$ objdump -h  SimpleSection.o    # 参数 -h 表示把ELF文件的各个段的基本信息打印出来

# size命令也可以用来查看ELF文件的代码段、数据段和BSS段的长度,-A指定运行模式为"System V compatibility mode"
$ size -A  SimpleSection.o  

# 代码段
$ objdump -s -d SimpleSection.o  # -s 表示将所有段的内容以16进制方式打印,-d将所有包含指令的段反汇编

3.1 ELF头

在linux中,ELF文件的最前面是ELF文件头,文件头的信息在32位机器中是由Elf32_Ehdr结构体来描述的,在64位机器上用Elf64_Ehdr结构体来描述。

$ readelf -h SimpleSection.o  # 查看ELF文件头
$ hexdump -x SimpleSection.o -n 64  # hexdump查看二进制文件,-x表示以双字节16进制显示,-n显示指定字节

在这里插入图片描述
上图是64位机器下ELF文件头,共64个字节,下面对其中的部分选项进行说明。

  • e_ident: 前4个字节0x7f454c46是ELF文件的魔数。第5个字节0x02表示64位(0x01表示32位),第6个字节0x01表示小端(0x02表示大端),第7个字节是ELF文件的主版本号,一般是1。后面9个字节未定义,填0
  • e_type: ELF文件类型。1 可重定位文件,2 可执行文件, 3 共享目标文件
  • e_entry: ELF程序的入口虚拟地址,操作系统从这个地址开始执行进程的指令。可重定位文件一般没有入口地址,则这个值为0
  • e_shoff: 段表在文件中的偏移
  • e_ensize: ELF头本身的大小
  • e_shentsize: 段表描述符的大小,一般等于sizeof(Elf64_Shdr)
  • e_shnum: 段表描述符数量,这个值等于ELF文件中拥有的段的数量
  • e_shstrndx: 段表字符串表所在段在段表中的下标

3.2 段表

段表描述了ELF文件中各个段的信息,例如段名、段的长度、在文件中的偏移、读写权限等。段表中的每一项用一个结构体来描述,在64位机器中是Elf64_Shdr(32机器中是Elf32_Shdr),段表本质上就是一个数组,数组中的每一项是一个Elf64_Shdr(32机器中是Elf32_Shdr)结构体。

在这里插入图片描述

  • sh_name: 段名在段表字符串表.shstrtab中的偏移,段名是一个字符串,保存在名为.shstrtab的字符串表中
  • sh_type: 段的类型。NULL 0 无效段; PROGBITS 1 代码段、数据段;SYMTAB 2 符号表;STRTAB 3 字符串表;RELA 4 重定位表
  • sh_flags: 段的标志位,表示该段在进程虚拟地址空间中的属性。WRITE 1 可写;ALLOC 2 需要在进程空间中为该段分配空间;EXECINSTR 4 可执行
  • sh_addr: 如果该段可以被加载,则为该段被加载后在进程地址空间中的虚拟地址
  • sh_offset: 段偏移
  • sh_size: 段的长度
  • sh_link, sh_info: 段链接信息
$ readelf -S SimpleSection.o # 查看段表

3.3 重定位表

重定位表用来保存与重定位相关的信息,它在ELF文件中往往是一个或多个段,每个需要被重定位的ELF段都有一个对应的重定位表,如代码段.txt的重定位表为.rel.text。重定位表本质上是一个Elf64_Rel(32机器中是Elf32_Rel)结构的数组。Elf64_Rel的大小为16字节(Elf32_Rel为8字节),有两个成员变量:r_offset、r_info。

  • r_offset: 重定位入口的偏移。
  • r_info: 重定位入口的类型和符号。低32位表示重定位的入口类型,高32位表示重定位的入口在符号表中的下标(32位机器中分别为低8位,高24位)
$ objdump -r SimpleSection.o  # 查看目标文件中需要重定位的符号

3.4 字符串表

字符串表在ELF文件中也以段的形式保存,用来存储ELF文件中的段名、变量名等。常见的字符串表有两个,.strtab: 用来存储普通的字符串;.shstrtab: 用来保存段表中用到的字符串。在字符串表中,所有的字符串连续保存在表中,用空字符(’\0’)隔开,字符串表的第一个字符为空字符。在ELF文件中只需要给出字符串在字符串表中的首地址的索引,就能够在字符串表中获取到这个字符串。

3.5 符号表

符号表是ELF文件中的一个段,段名一般叫.symtab
在这里插入图片描述

  • st_name: 符号名在字符串表中的下标
  • st_info: 符号类型和绑定信息。低4位表示符号类型(NOTYPE 0 未知符号;OBJECT 1 变量、数组;FUNC 2 函数;SECTION 3 段;FILE 4 文件名)。高4位表示符号绑定信息(LOCAL 0 局部符号;GLOBAL 1 全局符号;WEAK 2 弱引用)
  • st_shndx: 符号所在段。若符号存在于本目标文件中, st_shndx表示符号所在段在段表中的下标。
  • st_value: 符号相对应的值。在目标文件中且该符号不是"COMMON块",st_value表示该符号在对应段中的偏移;在可执行文件中,st_value表示符号的虚拟地址
  • st_size: 符号大小

4. 程序头表

程序头表是ELF可执行文件中的一个段,用来保存"Segment"的信息。ELF目标文件中没有程序头表。程序头表本质上是一个数组,数组元素是一个Elf64_Phdr(32为机器为Elf32_Phdr)结构体。
在这里插入图片描述

  • p_type: Segment的类型。LOAD 1; DYNAMIC ; INTERP
  • p_offset: Segment在文件中的偏移
  • p_vaddr: Segment的第一个字节在进程虚拟地址空间的起始位置
  • p_paddr: Segment的物理装载地址,一般和p_vaddr相同
  • p_filesz: Segment在ELF文件中所占用的长度
  • p_memsz: Segment在进程虚拟地址空间中所占用的长度
  • p_flags: Segment的权限。可读 R;可写 W;可执行 X
  • p_align: Segment的对齐属性。按2的p_align次字节对齐

【参考资料】 《程序员的自我修养:链接、装载与库》、linux源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值