ELF文件格式的详解

2021-02-18 13:42发布

ELF文件格式的详解

  • 1.说明

  • 2.elf文件的基本格式

  • 3.elf文件的头部信息

  • 4.elf文件的节区(Section)

    • 4.1 节区的作用

    • 4.2 节区的组成

  • 5.elf文件的段(Segment)

  • 6.用python解析elf文件

  • 7.总结

1.说明

ELF的英文全称是The Executable and Linking Format,最初是由UNIX系统实验室开发、发布的ABI(Application Binary Interface)接口的一部分,也是Linux的主要可执行文件格式。

从使用上来说,主要的ELF文件的种类主要有三类:

  • 可执行文件(.out):Executable File,包含代码和数据,是可以直接运行的程序。其代码和数据都有固定的地址 (或相对于基地址的偏移 ),系统可根据这些地址信息把程序加载到内存执行。
  • 可重定位文件(.o文件):Relocatable File,包含基础代码和数据,但它的代码及数据都没有指定绝对地址,因此它适合于与其他目标文件链接来创建可执行文件或者共享目标文件。
  • 共享目标文件(.so):Shared Object File,也称动态库文件,包含了代码和数据,这些数据是在链接时被链接器(ld)和运行时动态链接器(ld.so.l、libc.so.l、ld-linux.so.l)使用的。

本文主要从elf文件的组成构造的角度来进行分析,将elf文件的解析通过一步一步的分析得到里面的信息,同时通过python脚本解析,可以直观的看到文件的信息,通过本文的阅读,将对elf文件格式有着更加深刻的理解。

2.elf文件的基本格式

elf文件是有一定的格式的,从文件的格式上来说,分为汇编器的链接视角与程序的执行视角两种去分析ELF文件。


从程序执行视角来说,这就是Linux加载器加载的各种Segment的集合。比如只读代码段、数据的读写段、符号段等等。而从链接的视角上来看,elf又分为各种的sections。

注意Section Header Table和Program Header Table并不是一定要位于文件开头和结尾的,其位置由ELF Header指出,上图这么画只是为了清晰。

为了彻底的弄清楚elf文件的内容,可以先从ELF文件的头部开始分析。

3.elf文件的头部信息

对于elf头部文件信息,首先可以可以查看一下内存的布局情况:

根据readelf可以得到该文件的头部信息的情况。

根据定义,elf32的结构体定义,在Linux上可以在/usr/include/elf.h中找到

#define EI_NIDENT (16)

typedef struct
{
  unsigned char e_ident[EI_NIDENT];     /* Magic number and other info */
  Elf32_Half    e_type;                 /* Object file type */
  Elf32_Half    e_machine;              /* Architecture */
  Elf32_Word    e_version;              /* Object file version */
  Elf32_Addr    e_entry;                /* Entry point virtual address */
  Elf32_Off     e_phoff;                /* Program header table file offset */
  Elf32_Off     e_shoff;                /* Section header table file offset */
  Elf32_Word    e_flags;                /* Processor-specific flags */
  Elf32_Half    e_ehsize;               /* ELF header size in bytes */
  Elf32_Half    e_phentsize;            /* Program header table entry size */
  Elf32_Half    e_phnum;                /* Program header table entry count */
  Elf32_Half    e_shentsize;            /* Section header table entry size */
  Elf32_Half    e_shnum;                /* Section header table entry count */
  Elf32_Half    e_shstrndx;             /* Section header string table index */
} Elf32_Ehdr;

上述的Elf32_Half定义

/* Type for a 16-bit quantity.  */
typedef uint16_t Elf32_Half;

其中Elf32_Word的定义

/* Types for signed and unsigned 32-bit quantities.  */
typedef uint32_t Elf32_Word;

然后Elf32_AddrElf32_Off定义

/* Type of addresses.  */
typedef uint32_t Elf32_Addr;

/* Type of file offsets.  */
typedef uint32_t Elf32_Off;

有了这些数据结构的信息,然后对应具体的数据细节如下:

  • e_ident[EI_NIDENT]

文件的标识以及标识描述了elf如何编码等信息。

Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00

关于该结构体的索引可以看下面的表格:

名称 取值 目的
EI_MAG0 0 文件标识(0x7f)
EI_MAG1 1 文件标识(E)
EI_MAG2 2 文件标识(L)
EI_MAG3 3 文件标识(F)
EI_CLASS 4 文件类
EI_DATA 5 数据编码
EI_VERSION 6 文件版本
EI_PAD 7 补齐字节开始处
EI_NIDENT 16 e_ident[]大小

EI_CLASS的内容,当取值为0时,是非法类别,1是32位的目标,2是64位的目标。这里是1所以程序是32位的目标。

EI_DATA表示数据的编码,当为0时,表示非法数据编码,1表示高位在前,2表示低位在前。

EL_VERSION表示了elf的头部版本号码。

前面四个基本上确定的,内容第一个字符为7f,后面用ELF字符串表示该文件为ELF格式。

  • e_type

该数据类型是uint16_t数据类型的,占两个字节。通过字段查看,可以看到这个值为00 02。表格定义如下:

名称 取值 含义
ET_NONE 0x0000 未知目标文件格式
ET_ERL 0x0001 可重定位文件
ET_EXEC 0x0002 可执行文件
ET_DYN 0x0003 共享目标文件
ET_CORE 0x0004 Core文件(转储格式)
ET_LOPROC 0xff00 特定处理器文件
ET_HIPROC 0xffff 特定处理器文件

对应表格内容,可以看到类型为EXEC即可执行文件类型。

  • e_machine

由字段可以看到为00 28,关于这个字段的解析,基本上就是表示该elf文件是针对哪个处理器架构的。

下面只列出几个常见的架构的序号

名称 取值 含义
EM_NONE 0 No machine
EM_SPARC 2 SPARC
EM_386 3 Intel 80386
EM_MIPS 8 MIPS I Architecture
EM_PPC 0x14 PowerPC
EM_ARM 0x28 Advanced RISC Machines ARM

通过上述的表格,可以看到该架构是ARM处理器上运行的程序。

  • e_version

该字段占四个字节,表示当前文件版本的信息。现在取值为00 00 00 01。从取值上来看

名称 取值 含义
EV_NONE 0 非法版本
EV_CURRENT 1 当前版本
  • e_entry

这里表示程序的入口地址,目前为四字节,所以通过字段解析到的内容为00 00 80 00。得到可执行程序的入口地址为0x8000

  • e_phoff

该字段表示程序表头偏移。占四个字节,根据字段解析,可以查看当前的偏移量为00 00 00 34。也就是实际的偏移量为52个字节。这52个字节其实就是头部的信息数据结构体的大小。

  • e_shoff

该区域比较重要,记录了section的偏移地址。为四字节,解析出来的字段为0x00 04 24 5c。所以得到地址为0x4245c。

根据这个偏移得到section的内容:

通过readelf -t也可以得到类似的结果。

关于节区如何解析。后面再进行描述。

  • e_flags

特定处理器格式的标志,这里的字段解析为05 00 02 00。与特定的处理器相关。

  • e_ehsize

elf文件的头部大小。该取值与头文件结构体的大小相关,目前为52字节,即00 34

  • e_phentsize

程序头部表项大小,当前取值为00 20,为32个字节,这里表示

关于程序表项的解析,后面再进行具体分析。

  • e_phnum

目前取值为00 01,这里表示程序头的个数当前只有一个程序头,如果有多个程序头表,那么会在elf头文件之后,也就是52个字节之后,依次向下排列。因为这里是1,所以只有1个程序头。

  • e_shentsize

表示节区头部表格大小,解析字段为00 28,也就是第一个节区的大小为40个字节的偏移处。根据e_shoff可以知道。

将从e_shoff的区域向后面偏移40个字节,得到第一个节区的内容。

  • e_shnum

节区的数量,由字段解析得到数据为00 11。此时得到节区的数量为17个。通过readelf -t也可以解析到节区的数量为17个。

bigmagic@bigmagic:~/work/python_elf/elf$ readelf -t rtthread.elf 
There are 17 section headers, starting at offset 0x4245c:
  • e_shstrndx

标记字符串节区的索引。当前的解析为00 0e。也就是14个节区为字符节区。

到这里,头部信息的相关字段就解析完成了。

4.elf文件的节区(Section)

elf文件中的节是从编译器链接角度来看文件的组成的。从链接器的角度上来看,包括指令、数据、符号以及重定位表等等。

4.1 节区的作用

在可从定位的可执行文件中,节区描述了文件的组成,节的位置等信息。通过readelf -s可以查看信息。

这些节信息通过特定的地址偏移组成了一个elf文件的整体。

4.2 节区的组成

关于理解ELF中的Section。首先需要知道程序的链接视图,在编译器将一个一个.o文件链接成一个可以执行的elf文件的过程中,同时也生成了一个表。这个表记录了各个Section所处的区域。在程序中,程序的section header有多个,但是大小是一样。拿elf32文件来说

typedef struct
{

  Elf32_Word sh_name;  /* Section name (string tbl index) */
  Elf32_Word sh_type;  /* Section type */
  Elf32_Word sh_flags;  /* Section flags */
  Elf32_Addr sh_addr;  /* Section virtual addr at execution */
 
标签: