首页 > 代码库 > ELF格式的组成结构

ELF格式的组成结构

LF指的是Executable and Linkable Format。最初是由UNIX系统实验室作为应用程序二进制接口开发和发行的,后来逐渐发展成为了可执行文件的格式标准,在很多操作系统和非操作系统环境中都有非常广泛的应用。完整的ELF格式标准涉及了三个方面的内容。在这里我们只需要关心一个方面,那就是一个ELF格式可执行程序的组成结构。

一个ELF可执行文件格式如图8-1所示。

像图8-1那样,一个ELF可执行文件包含了一个描述全局信息的ELF文件头、若干个Program头、若干个Segment以及若干个可有可无的Section头。在Segment中保存的正是程序的运行代码,而Program头描述了各个Segment和其他必要信息,如链接库信息、文件辅助信息等。在代码真正运行时,我们往往只关心实际的代码部分,而与运行无关的其他信息则可以忽略掉。

                                                                                                                        ELF文件头

                                                                                                                        program头表

                                                                                                                         segment 1

                                                                                                                        segment 2

                                                                                                                           .............

                                                                                                                    section头表(可选)



 
图8-1  ELF文件结构

当操作系统需要执行一个ELF格式的文件时,只需要分析ELF文件头,然后找到Program头的位置,依次分析这些Program头,找到代表代码和数据的Segment,最后将这些Segment复制到指定的地址处,便可以运行程序了。

一个ELF文件头由如下部分组成:

代码8-4

  1. typedef unsigned int elf32_addr;  
  2. typedef unsigned int elf32_word;  
  3. typedef signed int elf32_sword;  
  4. typedef unsigned short elf32_half;  
  5. typedef unsigned int elf32_off;  
  6.  
  7. struct elf32_ehdr{  
  8.     unsigned char e_ident[16];  
  9.     elf32_half  e_type;  
  10.     elf32_half  e_machine;  
  11.     elf32_word  e_version;  
  12.     elf32_addr  e_entry;  
  13.     elf32_off   e_phoff;  
  14.     elf32_off   e_shoff;  
  15.     elf32_word  e_flags;  
  16.     elf32_half  e_ehsize;  
  17.     elf32_half  e_phentsize;  
  18.     elf32_half  e_phnum;  
  19.     elf32_half  e_shentsize;  
  20.     elf32_half  e_shnum;  
  21.     elf32_half  e_shstrndx;  
  22. }; 

因为ELF可执行程序格式可以支持不同位数的处理器,所以ELF标准中自定义了一些专有的数据类型。无论是8位的处理器还是32位的处理器,这些数据类型的大小都是一致的,从而保证了文件与处理器格式的无关性。这些数据类型的大小和含义如表8-1所示。

表8-1  ELF格式中的数据规定

数据类型

大小

对齐

含义

elf32_addr

4

4

用于描述程序运行地址

elf32_word

4

4

描述无符号大整数

elf32_sword

4

4

描述有符号大整数

elf32_half

2

2

描述无符号中等大小的整数

elf32_off

4

4

描述无符号的文件偏移量

让我们结合表8-1的描述,逐个分析一下ELF文件中各部分的含义。

一个ELF文件头总是出现在文件的最开始处。其中,第一个成员是一个16个字节数组,里边记录了文件的标识、版本、编码格式等信息。

之后的两个字节是e_type成员,记录了目标文件属于ELF格式标准下的哪种类型,比如是可执行的文件还是可重定位文件,等等。根据ELF格式标准,这个值是2,代表了这个文件是一个可执行的文件,这也正是我们需要的。

接下来的两个字节e_machine描述的是该程序运行的硬件平台。在我们的例子中,这个值必须是40,表示这个应用程序是在ARM中运行的。

然后,4个字节的空间e_version用于描述应用程序的版本,通常可以是1。

接下来的4个字节就相当重要了,结构体成员e_entry记录了程序运行时的入口地址,也就是说,应用程序的第一条指令就应该出现在这个地址处。

e_phoff成员记录了第一个Program头在文件内的偏移。e_phentsize成员则代表了每一个Program头大小,再结合能够描述文件中共有多少个Program头的e_phnum成员,我们就可以遍历每一个Program头,并可以从中找到代码和数据在文件中的位置。

代码8-4中的其他成员与程序的执行关系不大,这里就不多介绍了。读者朋友们如果还对这些内容感兴趣,可以去查阅相关文档。

另外,还有一个问题需要解决。在遍历每一个Program头时,如何才能知道这个头信息所描述的Segment就是数据或代码,而不是与运行程序无关的其他信息呢?我们在Program头结构体中可以找到答案。

代码8-5

  1. struct elf32_phdr{  
  2.     elf32_word  p_type;  
  3.     elf32_off   p_offset;  
  4.     elf32_addr  p_vaddr;  
  5.     elf32_addr  p_paddr;  
  6.     elf32_word  p_filesz;  
  7.     elf32_word  p_memsz;  
  8.     elf32_word  p_flags;  
  9.     elf32_word  p_align;  
  10. }; 

代码8-5定义了elf32_phdr结构体用于描述Program头信息。在该结构体中,与段类型直接相关的是p_type成员,只有当该成员的值为1时,才表示该Segment是要运行的代码或数据,需要在执行时加载到内存中去。

一旦确定需要进行内存加载,之后要做的就是读取p_offset成员的值,它表示要加载到内存中的这个Segment在文件中的偏移量,再结合描述Segment大小的成员p_filesz,就可以精确地定位程序的代码和数据了。

最后,程序还要读取p_vaddr成员,它表示这个Segment应该出现在内存的哪个位置。这样就可以将该Segment从文件中复制到正确的内存地址处了。