ELF文件格式(2):文件结构

前言

在前面我们已经大概看到过ELF文件的整体布局情况,本文将更详细地对ELF的文件结构进行阐述。一个典型的ELF文件结构如下图所示:
在这里插入图片描述

  • ELF文件头:出现在ELF文件的开头,描述了整个文件的基本属性,包括ELF文件类型、运行平台以及其它头部表的属性信息等等;
  • 程序头部表:对于可加载文件是必须的,对于可重定位文件是可选的;
  • 节区头部表:对于可重定位文件是必须的,对于可加载文件是可选的;
  • 节区或段:存储可装载的数据、重定位以及字符串和符号等信息。

链接与运行视图

ELF文件格式标准中提供了两种视图,分别是链接视图和执行视图。链接视图是以节(section)为单位,执行视图是以段(segment)为单位。顾名思义,链接视图就是在程序链接时用到的视图,而执行视图则是在程序被装载运行时用到的视图,二者分别对应于可重定位文件和可加载文件。
在这里插入图片描述

ELF文件头

ELF文件头(ELF Header)主要标记了ELF文件类型、结构和程序开始执行的入口地址,并提供了其他ELF头(如节头表和程序头表)在文件中的偏移位置。使用readelf工具来查看一个ELF文件的文件头:
在这里插入图片描述

ELF文件头的数据结构定义如下:

typedef struct {
   unsigned char e_ident[EI_NIDENT];
   uint16_t      e_type;        /* ELF文件类型 */
   uint16_t      e_machine;     /* 硬件平台 */
   uint32_t      e_version;     /* ELF头部版本 */
   ElfN_Addr     e_entry;       /* 程序执行的入口地址 */
   ElfN_Off      e_phoff;       /* 程序执行的入口地址 */
   ElfN_Off      e_shoff;       /* 节头表偏移量 */
   uint32_t      e_flags;       /* 处理器特定标志  */
   uint16_t      e_ehsize;      /* ELF头部长度 */
   uint16_t      e_phentsize;   /* 程序头表中一个条目的长度 */
   uint16_t      e_phnum;       /* 程序头表条目数目 */
   uint16_t      e_shentsize;   /* 节头表中一个条目的长度  */ 
   uint16_t      e_shnum;       /* 节头表条目个数 */
   uint16_t      e_shstrndx;    /* 节头表字符索引 */
} ElfN_Ehdr;

程序头部表

程序头部表(Program Header Table)是对二进制文件中段的描述,本质上是记录段表项的数组,是程序装载必需的一部分。段在程序被系统加载运行时被解析,其描述了磁盘上可执行文件的内存布局以及如何映射到内存中。ELF文件头中名为e_phoff(程序头部表偏移量)的偏移量记录了程序头部表的位置。使用readelf工具查看ELF文件中的程序头部表信息:
在这里插入图片描述

由上面的输出信息可以看出,程序头部表记录了ELF文件中所有段的属性信息,包括段的类型、文件偏移以及映射到内存时的地址信息。程序头部表本质上是段信息的数组,对于每个段(数组项),ELF使用如下数据结构进行描述:

 typedef struct {
    uint32_t   p_type;   /* 段的类型 */
    uint32_t   p_flags;  /* 段的标记 */ 
    Elf64_Off  p_offset; /* 段的位置相对于文件开始处的偏移 */
    Elf64_Addr p_vaddr;  /* 段在内存中的首字节地址,即虚拟地址 */
    Elf64_Addr p_paddr;  /* 段的物理地址 */
    uint64_t   p_filesz; /* 段在文件映像中的字节数 */
    uint64_t   p_memsz;  /* 段在内存映像中的字节数 */
    uint64_t   p_align;  /* 短在内存中对齐标记 */
} Elf64_Phdr;

段类型

ELF文件格式定义了5中不同类型的段,其描述如下:

  • PT_LOAD :描述可装载的段,该类型的段会被装载或被映射到内存中。典型的包括代码段和数据段;
  • PT_DYNAMIC :动态链接文件特有,包含了动态链接所必需的一些信息;
  • PT_NOTE:保存了与特定供应商或者系统相关的附加信息;
  • PT_INTERP:记录动态链接器的位置信息,本质是一个以null为终止符的字符串;
  • PT_PHDR :保存程序头部表本身的位置和大小。

节区头部表

节头表(Section Header Table)用于描述ELF文件的各个节区的位置和大小,主要用于链接和调试。须注意的是,节头对于程序的执行不是必需的,没有节头表,程序仍可以正常执行。使用readelf工具查看ELF文件的所有节:
在这里插入图片描述

与程序头部表类似,节区头部表是由节构成的数组。对于节的数据结构定义如下:

typedef struct {
    uint32_t   sh_name;     /* 小节名在字符表中的索引 */
    uint32_t   sh_type;     /* 小节的类型 */
    uint64_t   sh_flags;    /* 小节属性 */
    Elf64_Addr sh_addr;     /* 小节在运行时的虚拟地址 */ 
    Elf64_Off  sh_offset;   /* 小节的文件偏移 */
    uint64_t   sh_size;     /* 小节的大小.以字节为单位 */
    uint32_t   sh_link;     /* 链接的另外一小节的索引 */
    uint32_t   sh_info;     /* 附加的小节信息 */
    uint64_t   sh_addralign;    /* 小节的对齐 */
    uint64_t   sh_entsize;
} Elf64_Shdr;

ELF文件中定义的一些比较重要的节和节类型罗列如下:

节(section)类型描述
.text已编译程序的机器代码,即代码段
.rodata保存了只读的数据
.data已初始化的全局和静态C变量,即数据段
.bss未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量
.rel.text被模块引用或定义的外部函数的重定位信息,对应于.text
.rel.data被模块引用或定义的所有全局变量的重定位信息,对应于.data
.debug调试符号表,其条目是程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始的C源文件。只有以-g选项调用编译器驱动程序时,才会得到这张表
.line原始C源程序中行号和.text节中机器指令之间的映射,用于调试
.symtab符号表,存放在程序中定义和引用的函数和全局变量的信息
.strtab字符串表,包含.symtab和.debug节中的符号表,以及节头部的节名字。字符串表是以null结尾的字符串的序列
.shstrtab节名字符串表,在可执行文件是必须存在的
.ctors .dtorsC++构造函数和析构函数存放的段
.dynsym用于保存与动态链接相关的符号
.dynstr动态符号字符串表,与静态链接使用.strtab对应
.hash在动态链接的情况下,用于加快符号查找过程
.interp保存可执行文件使用的动态链接器的路径
.plt程序链接表,和.got.plt配合实现函数调用的延迟绑定
.got用于动态链接,保存外部全局变量引用的地址
.got.plt用于动态链接,用于保存外部函数引用的地址

参考

  • 《程序员的自我修养——链接、装载与库》
  • 《Linux二进制分析》
  • 《EFL Object File Format》
posted @ 2020-07-12 23:25  Aspiresky  阅读(44)  评论(0编辑  收藏  举报