2014-11-02

ELF代表Executable and Linkable Forma,是一种对可执行文件、目标文件和库使用的文件格式,跟Windows下的PE文件格式类似。ELF格式是是UNIX系统实验室作为ABI(Application Binary Interface)而开发和发布的,早已经是Linux下的标准格式了。 本文使用如下的简单程序来具体讲述ELF文件的格式,建议对照着程序的二进制码阅读本文。

#include <stdio.h>

int add(int a,int  b)
{
    printf("Number are added together\n");
    return a + b;
}

int main()
{
    int a,b;
    a = 3;
    b = 4;
    int ret = add(a,b);
    printf("Result:%u\n",ret);
    exit(0);
}

gcc test.c -o test
gcc test.c -c -o test.o

一. ELF概述

ELF主要包括三种类型文件:

  • 可重定位文件(relocatable):编译器和汇编器产生的.o文件,被Linker所处理
  • 可执行文件(executable):Linker对.o文件进行处理输出的文件,进程映像
  • 共享对象文件(shared object):动态库文件.so

下面是三种类型的示例:

ELF的布局如下:

由图可以知道,ELF文件从概念上来说包括了5个部分:

  • ELF header,描述体系结构和操作系统等基本信息,指出section header table和program header table在文件的位置

  • program header table,这个是从运行的角度来看ELF文件的,主要给出了各个segment的信息,在汇编和链接过程中没用

  • section header table,这个保存了所有的section的信息,这是从编译和链接的角度来看ELF文件的

  • sections,就是各个节区

  • segments,就是在运行时的各个段

注意,经过上面解释我们可以看到,其实sections和segments占的一样的地方。这是从链接和加载的角度来讲的。左边是链接视图,右边是加载视图,sections是程序员可见的,是给链接器使用的概念,而segments是程序员不可见的,是给加载器使用的概念。一般是一个segment包含多个section。Windows的PE就没有这个program header table和section header table点都统一为section,只是在加载时会进行处理。所以program header table和section header table都是可选的。

二. ELF的组成结构

在介绍这部分之前,前把定义中的各个类型数据结构的大小放在这里。

(1) ELF header

ELF Header描述了体系结构和操作系统等基本信息,并指出Section Header Table和Program Header Table在文件中的什么位置,每个成员的解释参见注释。

#define EI_NIDENT 16
typedef struct{
    /*ELF的一些标识信息,固定值*/
    unsigned char e_ident[EI_NIDENT];
    /*目标文件类型:1-可重定位文件,2-可执行文件,3-共享目标文件等*/
    Elf32_Half e_type;
    /*文件的目标体系结构类型:3-intel 80386*/
    Elf32_Half e_machine;
    /*目标文件版本:1-当前版本*/
    Elf32_Word e_version;
    /*程序入口的虚拟地址,如果没有入口,可为0*/
    Elf32_Addr e_entry;
    /*程序头表(segment header table)的偏移量,如果没有,可为0*/
    Elf32_Off e_phoff;
    /*节区头表(section header table)的偏移量,没有可为0*/
    Elf32_Off e_shoff;
    /*与文件相关的,特定于处理器的标志*/
    Elf32_Word e_flags;
    /*ELF头部的大小,单位字节*/
    Elf32_Half e_ehsize;
    /*程序头表每个表项的大小,单位字节*/
    Elf32_Half e_phentsize;
    /*程序头表表项的个数*/
    Elf32_Half e_phnum;
    /*节区头表每个表项的大小,单位字节*/
    Elf32_Half e_shentsize;
    /*节区头表表项的数目*/
    Elf32_Half e_shnum;
    /*某些节区中包含固定大小的项目,如符号表。对于这类节区,此成员给出每个表项的长度字节数。*/
    Elf32_Half e_shstrndx;
}Elf32_Ehdr;

这里简单解释一下最后一个字段e_shstrndx的含义,“e_shstrndx”是Elf32_Ehdr的最后一个成员,它是“Section header string table index”的缩写。我们知道段表字符串表本身也是ELF文件中的一个普通的段,知道它的名字往往叫做“.shstrtab”。那么这个“e_shstrndx”就表示“.shstrtab”在段表中的下标,即段表字符串表在段表中的下标。

下面是test的ELF header结构各个数据成员对应的值:

可以看到这个ELF的基本信息,比如,体系结构和操作系统,Section header table中有30个section,从4420开始,每个40个字节,Program header table中有9个segment,每个32字节。下面再从字节码上面看看具体的。标出了某些结构,可以对照上面的结构看。

(2) program header table与grogram header entry

程序头表是从加载的角度来看ELF文件的,目标文件没有该表,每一个表项提供了各段在虚拟地址空间和物理地址空间的大小、位置、标志、访问权限和对其方面的信息。从上面知道,test中有9个segment,如下图:

下面对其中的一些进行简单的介绍。

  • PHDR保存程序头表
  • INTERP指定在程序已经从可执行文件映射到内存之后,必须调用的解释器。在这里,解释器并不意味着二进制文件的内容必须由另一个程序解释。它指的是这样一个程序:通过链接其他库,来满足未解决的引用。通常/lib/ld-linux.so.2、/lib/ld-linux-ia-64.so.2等库,用于在虚拟地址空间中插入程序运行所需要的动态库。对几乎所有的程序来说,可能C标准库都是必须映射的。还需要添加的各种库包括,GTK、数学库、libjpeg等等
  • LOAD表示一个需要从二进制文件映射到虚拟地址空间的段。其中保存了常量数据(如字符串),程序的目标代码等。
  • DYNAMIC段保存了由动态链接器(即,INTERP中指定的解释器)使用的信息。
  • NOTE保存了专有信息

一个entry对应一个segment,由如下的数据结构表示

typedef struct
{
    /*segment的类型:PT_LOAD= 1 可加载的段*/
    Elf32_Word p_type;
    /*从文件头到该段第一个字节的偏移*/
    Elf32_Off p_offset;
    /*该段第一个字节被放到内存中的虚拟地址*/
    Elf32_Addr p_vaddr;
    /*在linux中这个成员没有任何意义,值与p_vaddr相同*/
    Elf32_Addr p_paddr;
    /*该段在文件映像中所占的字节数*/
    Elf32_Word p_filesz;
    /*该段在内存映像中占用的字节数*/
    Elf32_Word p_memsz;
    /*段标志*/
    Elf32_Word p_flags;
    /*p_vaddr是否对齐*/
    Elf32_Word p_align;
} Elf32_phdr;

(3) section header table与section header entry

节表头包含了文件中的各个节,每个节都指定了一个类型,定义了节数据的语义。各节都指定了大小和在二进制文件内部的偏移。从上面知道,test中有30个section,如下图:

下面对其中的一些进行简单的介绍:

  • .interp保存了解释器的文件名,这是一个ASCII字符串
  • .data保存初始化的数据,这是普通程序数据一部分,可以再程序运行时修改
  • .rodata保存了只读数据,可以读取但不能修改。例如,编译器将出现在printf语句中的所有静态字符串封装到该节
  • .init和.fini保存了进程初始化和结束所用的代码,这两个节通常都是由编译器自动添加
  • .gnu.hash是一个散列表,允许在不对全表元素进行线性搜索的情况下,快速访问所有的符号表项

section的结构定义如下:

typedef struct{
/*节区名称*/
Elf32_Word sh_name;
/*节区类型:PROGBITS-程序定义的信息,NOBITS-不占用文件空间(bss),REL-重定位表项*/
Elf32_Word sh_type;
/*每一bit位代表一种信息,表示节区内的内容是否可以修改,是否可执行等信息*/
Elf32_Word sh_flags;
/*如果节区将出现在进程的内存影响中,此成员给出节区的第一个字节应处的位置*/
Elf32_Addr sh_addr;
/*节区的第一个字节与文件头之间的偏移*/
Elf32_Off sh_offset;
/*节区的长度,单位字节,NOBITS虽然这个值非0但不占文件中的空间*/
Elf32_Word sh_size;
/*节区头部表索引链接*/
Elf32_Word sh_link;
/*节区附加信息*/
Elf32_Word sh_info;
/*节区带有地址对齐的约束*/
Elf32_Word sh_addralign;
/*某些节区中包含固定大小的项目,如符号表,那么这个成员给出其固定大小*/
Elf32_Word sh_entsize;
}Elf32_Shdr;

这就是ELF的大致结构了,有时间再对几个比较重要的节表进行总结。



blog comments powered by Disqus