ELF文件格式

ELF(Executable and Linking Format)是一种对象文件的格式,用于定义不同类型的对象文件(Object files)中都放了什么东西、以及都以什么样的格式去放这些东西。它自最早在 System V 系统上出现后,被 xNIX 世界所广泛接受,作为缺省的二进制文件格式来使用。可以说,ELF是构成众多xNIX系统的基础之一。如同PE文件格式之对于Windows系统,ELF文件格式之对于Linux系统。

ELF文件有三种类型:

Ø  可重定位的对象文件(Relocatable file)(*.o) 包含适合于与其他目标文件链接来创建可执行文件或者共享目标文件的代码和数据

Ø  可执行的对象文件(Executable file)(*.exe) 包含适合于执行的一个程序,此文件规定了exec() 如何创建一个程序的进程映像

Ø  可被共享的对象文件(Shared object file):(*.so)包含可在两种上下文中链接的代码和数据。首先链接编辑器可以将它和其它可重定位文件和共享目标文件一起处理,生成另外一个目标文件。其次,动态链接器(Dynamic Linker)可能将它与某个可执行文件以及其它共享目标一起组合,创建进程映像

ELF文件的总体格式如下图所示:

blob.png

目标文件格式

目标文件既要参与程序链接又要参与程序执行。出于方便性和效率考虑,目标文件格式提供了两种并行视图,分别反映了这些活动的不同需求。编译器,链接器把它看作是 sections 的集合,loader 把它看作是 segments 的集合。

blob.png

Object file format

一般的 ELF 文件包括三个索引表:ELF  headerProgram  header  tableSection header table

索引表

说明

ELF header

在文件的开始,保存了路线图(road  map),描述了该文件的组织情况。

Program header table

告诉系统如何创建进程映像。用来构造进程映像的目标文件必须具有程序头部表,可重定位文件不需要这个表。

Section header table

包含了描述文件段区的信息,每个段区在表中都有一项,每一项给出诸如段区名称、段区大小这类信息。用于链接的目标文件必须包含段区头部表,其他目标文件可以有,也可以没有这个表。

ELF header(文件头)

查看内核源码,在include/linux/elf.h文件定义了描述ELF header的结构体elf32_hdr。(这里我们以32位系统为例)

#define EI_NIDENT       16

 

typedef struct elf32_hdr{

    unsigned char  e_ident[EI_NIDENT];   /*EI_NIDENT:16*/

    Elf32_Half      e_type;

    Elf32_Half      e_machine;

    Elf32_Word     e_version;

    Elf32_Addr     e_entry;  /* Entry point */

    Elf32_Off       e_phoff;

    Elf32_Off       e_shoff;

    Elf32_Word     e_flags;

    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;

32位系统中,数据类型所占字节描述如下:

blob.png

通过计算可以得到结构体elf32_hdr占用52个字节,也就是说ELF文件的开头52个字节属于ELF header。我们可以用readelf工具读取ELF文件查看ELF header内容信息。如查看hello.oELF目标文件。

blob.png

上图是重定向文件hello.oELF header内的内容,这和结构体elf32_hdr的内容吻合。

结构体中第一个内容是名为e_ident[]的数组,该数组的下标是通过一组宏定义去索引的。

blob.png

Ø  e_ident数组

1~4项表示四个魔术数,标识该文件为ELF文件,所以任何ELF文件该四项是相同的。我们看到这四个字节:7f 45 4c 46。其中0x45 0x4c 0x46 à E L F

5项的一个字节标识目标文件类型,其值如下所示:

blob.png

6项的一个字节标识数据编码方式。其值如下:

blob.png

7项的一个字节标识ELF文件的版本,其值如下:

blob.png

Ø  e_type2字节,标识文件类型,即可重定向、可执行、共享目标文件等。其值如下:

blob.png

Ø  e_machine2字节,标识目标体系结构体系。03 00Intel 80386

Ø  e_version4字节,标识ELF文件版本。01 00 00 00:目前版本

Ø  e_entry4字节,标识程序入口的虚拟地址。00 00 00 00:无程序入口

Ø  e_phoff4字节,标识Program Header Table的偏移量。00 00 00 00:偏移量为0

Ø  e_shoff4字节,标识Section Header Table的偏移量。e8 00 00 00:偏移量为0xe8=232

Ø  e_flag4字节,保存与文件相关的,特定于处理器的标志。00 00 00 00:未指定

Ø  e_ehsize2字节,标识ELF header的大小。34 00ELF header大小为0x34=52

Ø  e_phentsize2字节,标识每个Program header大小。00 00Program header的大小为0

Ø  e_phnum2字节,标识Program header的总数。00 00Program header的总数为0

Ø  e_shnum2字节,标识Section header的总数。0b 00Section header的总数为0x0b=11

Ø  e_shstrndx2字节,Section header string table index标识section的字符串表在section header table的索引值。08 00:索引值为8

我们可以看到hello.ostart of Program headerssize of Program headers都为0,说明不是所有的ELF文件都有program header table

Section

目标文件的section header table存放着所有的section。它是一个Elf32_Shdr结构的数组,通过下表来索引。ELF header中的e_shoff成员给出了section header table在文件中的偏移,e_shnum给出了表中共含有多少表项,e_shentsize给出了每个表项的大小。

Section header table索引定义如下:

blob.png

l  SHN_UNDEF:表示一个未定义的、丢失的、不相关的、或无意义的section引用。比如对应于SHN_UNDEF的“defined”符号是一个未定义的符号。

注:虽然索引0代表未定义,section 头部表中仍会包含一个索引为0的表项。这就意味着,如果ELF头部中的e_shnum规定section 头部表中含有6个表项,它们的索引取值是05。索引为0的表项稍后会介绍。

l  SHN_LORESERVE:保留索引值的下限。

l  SHN_LOPROCSHN_HIPROC:这个范围内的取值留作处理器相关的语义。

l  SHN_ABS:对应的引用是一个绝对的取值。比如对应为SHN_ABS的符号,是一个绝对值,不受重定位影响。

l  SHN_COMMON:这个section定义的符号是通用符号,比如FORTRAN COMMON和未分配的C external变量。

l  SHN_HIRESERVE:保留索引值的上限。系统保留了SHN_LORESERVESHN_HIRESERVE(含)之间的索引值。这些值不会对应到section 头部表,即表中不会含有是这些索引值的表项。

Section header的相关信息如下所示

blob.png

l  sh_namesection的名字。它的值是section头部表的索引,是一个以空字符结尾的字符串的位置。

l  sh_typesection的类型。

l  sh_flagssection标志位,描述各种section属性。

l  sh_addr:如果section将会出现在进程的内存映像中,这个属性给出了section首个字节的地址。否则,此成员的取值为0

l  sh_offsetsection在文件中的偏移。有一种SHT_NOBITS类型的section,不占用文件中的字节,此成员仅代表概念上的偏移。

l  sh_sizesection的大小。除非section类型是SHT_NOBITS,否则section将占用文件中的sh_size个字节。SHT_NOBITS类型的section可能其sh_size不为0,但是它不占用文件中的任何空间。

l  sh_link section header table的索引。不同类型的section,它有不同的含义。

l  sh_info:辅助信息。不同类型的section,它有不同的含义。

l  sh_addralign:有些section有地址对齐方面的约束条件。比如section中有个doubleword型的数据,系统就必须保证整个sectiondoubleword对齐的,即sh_addrsh_addralign取模为0。目前,此成员的取值只允许是02的幂数。取值01代表section没有对齐方面的约束。

l  sh_entsize:有些section存放的表,其表项大小是固定的,比如符号表。对于这种表,此成员给出了表项的大小。如果表项大小不固定,此成员取值为0

section的类型sh_type定义如下:

blob.png

sh_type

说明

SHT_NULL

表示此section头部是不活动的,不对应一个sectionSection头部中其他成员的取值均未定义。

SHT_PROGBITS

section存放的数据由程序定义,它们的格式和含义也都由程序决定。

SHT_SYMTAB

SHT_DYNSYM

section存放的是一张符号表。目前,一个目标文件只能包含一个此类型的section,但是这一限制以后可能放宽。通常情况下,SHT_SYMTAB提供了链接编辑时用到的符号,同时它也可能被动态链接用到。作为一个完整的符号表,它可能包含许多动态链接用不到的符号。因此,目标文件还可能包含一个SHT_DYNSYM section,用于存放动态链接用到的符号,以节省空间。

SHT_STRTAB

section存放的是一个字符串表。一个目标文件可以有多个字符串表section

SHT_RELA

section通过显式的加数存放重定位条目。比如ELF32_Rela对应32位的目标文件类型。一个目标文件可以有多个重定位section

SHT_HASH

section存放的是一个符号哈希表。所有参与动态链接的对象必须包含一张符号哈希表。目前一个目标文件只能包含一张哈希表,但这个限制以后可能放宽。

SHT_DYNAMIC

section存放动态链接的相关信息。目前,一个目标文件只能有一个此类型的section,但是这个限制以后可能放宽。

SHT_NOTE

section存放文件的一些注释信息。

SHT_NOBITS

此类型的section不占用文件的空间,其他方面类似于SHT_PROGBITS。虽然这种section不包含字节,sh_offset成员仍规定了概念上的文件偏移。

SHT_REL

section存放没有显示加数的重定位条目。比如ELF32_Rel对应32位的目标文件。一个目标文件可能有多个重定位的section

SHT_SHLIB

保留section,未定义其含义。含有这种section的程序不符合ABI规范。

SHT_LOPROC

SHT_HIPROC

这个范围内的取值留作处理器相关的语义。

SHT_LOUSER

留给应用程序使用的保留索引值的下限。

SHT_HIUSER

留给应用程序使用的保留索引值的上限。应用程序可以使用SHT_LOUSERSHT_HIUSER之间的section类型,而不会和目前或未来系统定义的section类型冲突。

 

sh_flag描述了section的属性,其取值如下,其他的为保留值。

blob.png

sh_flag

说明

SHF_WRITE

section包含的数据在进程运行期是可写的。

SHF_ALLOC

section在进程运行期占用内存。某些控制用section并不占用目标文件在内存中的映像,它们的这一属性是关闭的状态。

SHF_EXECINSTR

section包含可执行的机器指令。

SHF_MASKPROC

这个掩码中的所有比特位留作处理器相关的语义。

不同的section保存着不同程序或控制信息。下表列出了系统中使用的section,并注明了其类型和属性。

Name

Type

Attributes

.bss

SHT_NOBITS

SHF_ALLOC+SHF_WRITE

.comment

SHT_PROGBITS

0

.data

SHT_PROGBITS

SHF_ALLOC+SHF_WRITE

.data1

SHT_PROGBITS

SHF_ALLOC+SHF_WRITE

.debug

SHT_PROGBITS

0

.dynamic

SHT_DYNAMIC

SHF_ALLOC+SHF_WRITE

.dynstr

SHT_STRTAB

SHF_ALLOC

.dynsym

SHT_DYNSYM

SHF_ALLOC

.fini

SHT_PROGBITS

SHF_ALLOC+SHF_EXECINSTR

.fini_array

SHT_FINI_ARRAY

SHF_ALLOC+SHF_WRITE

.hash

SHT_HASH

SHF_ALLOC

.init

SHT_PROGBITS

SHF_ALLOC+SHF_EXECINSTR

.init_array

SHT_INIT_ARRAY

SHF_ALLOC+SHF_WRITE

.interp

SHT_PROGBITS

SHF_ALLOC

.line

SHT_PROGBITS

0

.note

SHT_NOTE

0

.preinit_array

SHT_PREINIT_ARRAY

SHF_ALLOC+SHF_WRITE

.rodata

SHT_PROGBITS

SHF_ALLOC

.rodata1

SHT_PROGBITS

SHF_ALLOC

.shstrtab

SHT_STRTAB

0

.strtab

SHT_STRTAB

SHF_ALLOC

.symtab

SHT_SYMTAB

SHF_ALLOC

.tbss

SHT_NOBITS

SHF_ALLOC+SHF_WRITE+SHF_TLS

.tdata

SHT_PROGBITS

SHF_ALLOC+SHF_WRITE+SHF_TLS

.text

SHT_PROGBITS

SHF_ALLOC+SHF_EXECINSTR

l  .bss:保存程序内存映像中未初始化的数据。当程序开始运行时,系统把这些数据初始化为0。这个sectionSHT_NOBITS类型的,即不占用目标文件空间。

l  .comment:保存版本控制信息。

l  .data.data1:保存程序内存映像中已经初始化的数据。

l  .debug:保存符号的调试信息。其内容并未规定。

l  .dynamic:保存动态链接信息。其属性包含SHF_ALLOC比特位。SHF_WRITE比特位是否被置位与处理器相。

l  .dynstr:保存动态链接需要的字符串。通常,这些字符串代表符号表表项的名字。

l  .fini:保存进程退出代码所执行的指令。即当一个程序正常退出时,系统调用此section中的代码。

l  .got:保存全局偏移表。

l  .hash:保存符号哈希表。

l  .init:保存进程初始化代码所执行的指令。即当一个程序开始运行时,在main程序入口(main for C programs)之前,系统调用此section中的代码。

l  .interp:保存程序解释器的路径名。如果文件存在包含此section的可加载的segmentsection的属性将包含SHF_ALLOC比特位。否则该比特位是关闭状态。

l  .line:保存符号调试用到的行号,指明了源代码和机器代码之间的对应关系。其内容未规定。

l  .note:按照一定格式保存注释信息。

l  .plt:保存过程链接表。

l  .relname.relaname:保存重定位的相关信息。如果文件存在包含此section的可加载的segmentsection的属性将包含SHF_ALLOC比特位,否则该比特位将是关闭状态。通常情况下,name是此重定位section对应的原section的名字。比如一个重定位section对应.text,那么它的名字通常为.rel.text.rela.text

l  .rodata.rodata1:保存进程映像中不可写segment里的只读数据。

l  .shstrtab:保存section名字的字符串表section

l  .strtab:保存字符串表,这些字符串通常代表符号表中表项的名字。如果文件存在包含符号字符串表的可加载的segment,此section的属性应该包含SHF_ALLOC比特位,否则该位为关闭状态。

l  .symtab:保存一张符号表。参见后面“符号表”一节。如果文件存在包含符号表的可加载的segment,此section的属性应该包含SHF_ALLOC比特位,否则该位为关闭状态。

l  .text:保存程序中的文本,和其他可执行的指令。

Section名字前面的“.”前缀代表此section是系统保留的。如果这些section的功能满足需要,程序可以直接使用它们。程序也可以定义自己的section名字,不要加这个前缀,以免和系统section冲突。目标文件是允许自定义上面列表之外的section的。一个目标文件可能包含具有相同名字的多个section

readelf命令加参数-S可以查看一个ELF文件的所有section

blob.png

Relocation

重定位是将符号引用和符号定义链接到一起的过程。比如,当一个程序调用一个函数,相关的调用指令在执行时必须把控制权传递到正确的目的地址。换句话说,就是可重定位文件必须包含一些信息,用来描述怎样修改它们的section内容,从而使可执行文件和共享目标文件掌握正确的信息用于创建进程的程序映像。重定位表项的数据结构如下所示:

blob.png

blob.png

l  r_offset:指出重定位操作的位置,对于可重定位文件,它的值是从section的开始处到重定位作用的存储单元的字节偏移量。对于可执行文件或共享目标文件,它的值是重定位作用的存储单元的虚拟地址

l  r_info:指出重定位作用的符号在符号表中的索引和重定位的类型。比如,一个调用指令的重定位表项将保存被调用函数的符号表索引,如果索引是STN_UNDEF,即未定义的符号表索引,那么重定位使用0作为该符号的值。重定位的类型是处理器相关的。当代码访问重定位表项的重定位类型和符号表索引时,需要将ELF32_R_TYPE宏和ELF32_R_SYM宏分别作用于表项的r_info成员。blob.png

l  r_addend:指定一个常量加数(addend),用于计算可重定位字段存储的值。

下面举一个转换重定位文件到可执行文件或共享目标文件的例子。从概念上来讲,链接编辑器链接一个或多个可重定位文件形成一个输出文件时,它首先决定如何组合并定位输入的文件,然后更新符号值,最后进行重定位。重定位作用于可执行文件和共享目标文件时,过程与之类似,并且输出同样的结果。

Program

可执行文件和共享目标文件都是静态的表示程序。为了执行这样的程序,系统使用这些文件来创建动态的程序表示,即进程映像。一个进程映像通过segment保存文本、数据、堆栈等。下面将主要讨论以下内容:

l  程序头部:介绍目标文件结构中与程序执行直接相关的部分。主要的数据结构是程序头部表,保存着文件中的segment映像,以及创建程序内存映像时使用的其他一些必要信息。

l  程序加载:系统必须将目标文件加载到内存中才能使程序运行。

l  动态链接:系统加载完程序后,必须解析目标文件中所有的符号引用,才能组成一个完整的进程映像,从而构建一个进程。

1Program header

可执行文件和共享目标文件的程序头部表(program header table)是一个结构数组,每个元素描述了一个segment,以及系统准备执行程序时所需的其他信息。目标文件的segment包含一个或多个section,后面会介绍。程序头部只对可执行文件和共享目标文件有意义。目标文件通过ELF头部中的e_phentsizee_phnum成员指定了程序头部表的大小。Program header结构定义如下:

blob.png

l  p_type:说明了这个数组元素描述了一个什么种类的segment,以及如何解释这个数组元素的信息。

l  p_offset:从文件开头到segment第一个字节的偏移。

l  p_vaddrsegment第一个字节在内存中的虚拟地址。

l  p_paddr:在某些系统上此成员保存segment的物理地址。由于System V忽略应用程序的物理地址,这个成员的取值在可执行文件和共享目标文件中没有定义。

l  p_fileszsegment在文件映像中的字节数,可能为0

l  p_memsz  segment在内存映像中的字节数,可能为0

l  p_flags:与segment相关的标志。

l  p_align:可加载的进程segmentp_vaddrp_offset必须是模页面大小同余的。这个成员给出了segment在内存和文件中的对齐值。取值为01表示没有对齐方面的要求。否则,p_align应该是一个正整数,并且是2的幂数。p_vaddrp_offset应该是模p_align同余的。

有的表项描述了进程segment,其他的则描述了一些进程映像中并不包含的辅助信息。Segment表项除了有明确规定的,可以采用任意的顺序。下面列出了segment类型的取值,其他的取值留作以后使用。

blob.png

l  PT_NULL:表示此数组元素未被使用,其他成员的值未定义。这个类型使程序头部表可以包含一些无关的表项。

l  PT_LOAD:表示这是一个可加载的segment,通过p_fileszp_memsz描述。目标文件中的字节将被映射到内存segment的开始部分去。如果segment的内存大小(p_memsz)大于文件大小(p_filesz),在segment已初始化数据区后面多出来的部分将填充值为0的字节。文件大小可以小于内存大小。程序头部表中可加载的segment表项依据p_vaddr成员按升序排列。

l  PT_DYNAMIC:保存了动态链接的相关信息。

l  PT_INTERP:指定了一个以空字符结尾的字符串的位置和长度,这个字符串是需调用的解释器的路径。此类型的segment只对可执行文件有效(虽然也可能存在于共享目标文件中)。在一个文件中只能有一个此类型的segment,如果有,那么它必须位于所有可加载的segment表项之前。

l  PT_NOTE:指定了辅助信息的位置和大小。。

l  PT_SHLIB:保留的,未定义语义。包含此类型segment头部的程序不符合ABI规范。

l  PT_PHDR:如果有指定了文件和内存映像中程序头部表的位置和大小。文件中只能有一个此类型的程序头部。而且,仅当程序头部表是程序在内存中映像的一部分时,才会出现。此类型的表项必须位于所有表项之前。

l  PT_LOPROCPT_HIPROC:此范围内的取值留作处理器相关的语义。

注:除非在其他地方有特殊的需求,所有程序头部的segment类型都是可选的,即文件的程序头部表可能只包含与其内容有关的程序头部。

2Program Loading

系统创建或增补一个进程映像时只是逻辑上的拷贝文件中的segment到虚拟内存中的segment。系统何时、是否在物理上访问这个文件,取决于程序的执行行为,比如系统加载等。执行进程的过程中,只有引用到逻辑页时才会请求一个物理页。进程中通常会有很多未引用的页,这种延迟物理读的方式就将这些未引用的页排除了,提高了系统的性能。在实际应用中,如果想实现这种方式,就必须使可执行文件和共享目标文件的segment映像在文件中的偏移及其虚拟地址是模页面大小同余的。

System V体系结构而言,其segment的虚拟地址和文件偏移是模4KB(或更大的2的幂数)同余的。由于4KB是最大的页面大小,目标文件适用于分页,而不用考虑物理页面的大小。

3Dynamic Link

Program Interpreter

可执行文件可能包含一个PT_INTERP类型的程序头部,如下图所示。在exec(BA_OS)期间,系统从PT_INTERP segment中检索一个路径名,并利用解释器文件的segment创建初始的进程映像。也就是说系统并没有使用最初的可执行文件的segment映像,而是创建了一个解释器的内存映像。然后解释器负责从系统接收控制权并为应用程序提供运行环境。

blob.png

解释器可通过两种方式获得控制权。第一种方式,它得到一个文件描述符以读取可执行文件,从文件的开始部分开始读。利用这个文件描述符读取、映射可执行文件的segment到内存中。第二种方式,通过解析可执行文件格式,系统可能已经将可执行文件加载进内存,而不是将已经打开的文件描述符交给解释器。由于文件描述符可能存在异常,解释器的初始进程状态也就与可执行文件已接收的状态保持一致。解释器本身可能不需要另外一个解释器。解释器可能是一个共享目标文件或可执行文件:

l  共享目标文件(通常情况):以位置无关方式加载,即不同的进程使用不同的地址。系统在mmap(KE_OS)和相关服务使用的动态segment区创建了解释器的segment。因此共享目标解释器通常不会和要处理的可执行文件的segment发生地址冲突。

l  可执行文件:被加载到固定地址,系统使用程序头部表中的虚拟地址创建它的segment。因此,可执行文件解释器的虚拟地址可能和要处理的可执行文件的地址发生冲突,解释器负责解决这个冲突。

Dynamic Linker

当用动态链接技术构建一个可执行文件时,链接编辑器会在可执行文件中添加一个PT_INTERP类型的程序头部,告知系统调用指定的动态链接器作为程序解释器。

注:系统提供动态链接器的位置是处理器相关的。

Exec(BA_OS)和动态链接器相互配合为程序创建了进程映像,步骤如下:

l  把可执行文件的内存segment添加到进程映像中。

l  把共享目标的内存segment添加到进程映像中。

l  重定位可执行文件及其共享目标。

l  如果动态链接器收到了用于读取可执行文件的文件描述符,则关闭它。

l  把控制权传递给程序,这看起来好像是程序已经直接从exec(BA_OS)接收了控制权。

链接编辑器构建了多种数据,来帮助动态链接器处理可执行文件和共享目标文件。这些数据驻留在可加载的segment中,执行程序时可以访问它们。(再一次重申,调用这些segment的内容是处理器相关的,参见处理器的说明文档以获得更完整的信息。)

SHT_DYNAMIC类型的.dynamic section保存这些数据。此section的存放的是动态链接表,保存动态链接时用到的地址信息。

SHT_HASH类型的.hash section保存一张符号哈希表。

SHT_PROGBITS类型的.got.plt section保存两张独立的表:全局偏移表和过程链接表。后面将讲解动态链接器如何使用、更新这些表,从而创建目标文件的内存映像。

由于每个符合ABI规范的程序都引用了共享目标库中的基本系统服务,动态链接器也就参与了每个符合ABI规范的程序的执行过程。

正如“程序加载”一节中关于处理器相关补充内容所描述的那样,共享目标在内存中的虚拟地址可能与文件程序头部表中的地址不同。动态链接器会重定位内存映像,在应用程序获得控制权之前更新绝对地址。如果加载库时使用的是程序头部表中指定的地址,那么这些绝对地址就能够正常工作,不过通常不是这样。

如果进程的环境变量包含名叫LD_BIND_NOW的变量,并且值非空,动态链接器在将控制权交给程序之前,就会执行所有重定位操作。比如,下面列出的环境变量都指定了这个行为。

LD_BIND_NOW=1

LD_BIND_NOW=on

LD_BIND_NOW=off

否则的话,如果环境变量中不包含LD_BIND_NOW,或者它的值为空,那么将允许动态链接器惰性的(lazily)处理过程链接表。从而避免了为那些还没有调用到的函数解析符号和重定位。

 

使用readelf命令加参数-l查看ELF文件的程序头信息:

blob.png

Program header描述了segment,我们可以看到ELF文件hello9program header。其中有两个LOAD类型和一个DYNAMIC类型的。第一个LOAD类型的program header描述的是代码segment,具有可读可执行权限,被映射到地址为0x08048000,大小为0x5bc字节的虚拟内存区域中;第二个LOAD类型描述的是数据segment,就有可读可写权限,被映射到地址为0x08049f08,大小为0x11c字节的虚拟内存区域中;DYNAMIC类型的描述了动态链接器所需要的基本信息,具有可读可写权限,被映射到地址为0x08049f1c,大小为0xe8字节的虚拟内存区域中。

从上图中我们还可以看到segmentsection的关系。总的来说,section面向链接器,segment面向加载器。有section to segment mapping部分,我们可以知道哪些segment载入了哪个segment中。如DYNAMIC segment中只映射有.dynamic section

 

 

参考:

ELF format官方文档

Elf格式总结

一个链接器的实现

Elf文件格式

Readelf elf文件格式分析

http://www.cnblogs.com/brianhxh/archive/2009/07/04/1517020.html

ELF special sections

浅析Linux计算机进程地址空间与内核装载ELF

ELF中文版