ELF format

来源:互联网 发布:php后台管理app 编辑:程序博客网 时间:2024/05/16 10:42

Refer to http://www.skyfree.org/linux/references/ELF_Format.pdf


ELF是Executable and Linking Format的缩写, 本来是UNIX系统实验室ABI(Application Binary Interface)的一部分. TIS(Tool Interface Standards) 委员会选择ELF标准作为一种可移植的目标文件(Object file)格式, 适用于32bit因特尔体系结构的各种操作系统. 这篇文基于EFL 1.1版本分析ELF文件的格式.


1. 介绍

目标文件有3种类型: 可重定位文件(relocatable file), 可执行文件(executable file), 以及共享目标文件(shared object file)

可重定位文件: 包含数据和代码, 可和其他一些目标文件一起进行连接, 生成可执行文件或者共享目标文件

可执行文件: 可执行的程序, 文件包含了exec建立程序进程内存映像所需的信息

共享目标文件: 包含进行两种不同链接的代码和数据. 1. 链接器(ld)可将共享目标文件和其他共享目标文件以及可重定位文件一起链接成另一个目标文件. 2. 动态链接器可根据此共享目标文件和一个可执行文件以及其他共享目标文件一起建立一个进程内存映像.


2. 文件格式

目标文件既参与程序链接又参与程序执行, 从便捷和效率方面考虑, 目标文件格式为不同目的(链接/执行)提供了不同的处理文件内容的角度. 图2.1分别从链接和执行的角度看ELF文件的格式.


图2.1 目标文件格式

ELF头(ELF header)位于文件开始,充当路线图描述文件结构。

节(Section)包含链接相关的目标文件信息:指令、数据、符号表、重定位信息等。

程序头表(Program header table)描述了怎样创建一个进程映像,重定向文件没有程序头表。

节头表(Section header table)包含描述文件所有节(Sections)的信息,每个节在Section header table里都有一个表项,表项指明了节的名字、空间大小等信息。用于链接的文件必须有一个节表头(Section Header table),其他目标文件可有可无。

注意:虽然上图中Program header table接在ELF header之后,section header table也刚好在sections之后,但是在真实的文件中可能并不是这样。并且sections和segments没有特定的排列顺序。只有ELF header是固定位于文件开始处。


3. 数据类型

由于目标文件格式支持从8bit到32bit体系结构的多种处理器,并且可以扩展到更大(或更小)的体系结构,因此目标文件使用了机器无关的格式来表示一些控制数据,通过这些机器无关的控制数据使用通用的方式来识别文件内容。这样一来,其他的数据就可以使用目标处理器上的编码方式了。


图3.1 数据类型


4. ELF头(ELF Header)

一些目标文件控制结构可以在原定义上扩展而不会影响到目标文件的解析,因为ELF header存有这些数据的实际大小。如果目标文件格式改变了,程序可能会遇到比预期的大一些或者小一些的控制结构。对于多出的数据,程序可能直接忽略,对于‘丢失’的信息,处理方法根据丢失的内容而定,如果定义了扩展,这些丢失的内容会被重新制定新值。



e_ident: 标识这是个目标文件,并且提供机器无关的数据来帮助解码文件内容。

e_type: 目标文件类型


e_machine: 支持的体系结构



e_version: 目标文件的版本,0表示无效的版本,1代表原始文件格式。如果有扩展,e_version会相应增加。

e_entry: 进程entry point的虚拟地址,如果文件没有entry point,则为0

e_phoff: program header table的偏移量,如果没有PHT则为0

e_shoff: section header table的偏移量,没有SHT则为0

e_flags: 处理器相关的标志,参见参考文献的"Machine Information"

e_ehsize: ELF header的大小(bytes)

e_phentsize: Program Header table中每个表项(program header)的大小,PHT中所有表项大小相同

e_phnum: Program header table中的表项数量

e_shentsize: Section header table中每个表项(section header)的大小,SHT中所有表项大小相同

e_shnum: Section header table中表项数量

e_shstrndx: Section header table中有一表项是section name string table(节名字表),这个变量记录了此表项在SHT中的位置,如果文件中没有这个表项,则变量值为SHN_UNDEF


4.1 ELF Identification

ELF是一个支持多处理器、多数据编码以及多机器类型的目标文件框架,为支持这些特性,ELF文件的最开始几个字节提供了一些处理器无关的信息来告诉程序怎样解析这个文件(主要是剩下的内容)。

ELF文件(同时也是ELF header)的最初几个字节对应的就是ELF header中的e_ident成员


图4.1 Identification indexex

图4.1是e_ident中各个位置(index)上数据的解释

EI_MAG0 - EI_MAG3: ELF文件头4个字节是一个“魔法数字”,表明此文件是一个ELF对象文件


EI_CLASS: e_ident[EI_CLASS]指明ELF文件的类别。ELFCLASS32用于支持小于等于32bit的体系结构,ELFCLASS64是预留给64bit体系结构的

EI_DATA: e_ident[EI_DATA]指明数据编码类型。ELFDATA2LSB表示使用LSB编码(低地址对应低有效位),ELFDATA2MSB表示使用MSB编码(高地址对应低有效位)

EI_VERSION: ELF header的版本信息(EV_CURRENT),随着扩展会变大

EI_PAD: e_ident预留空间的开始,其后都填充为0


5. Sections(节)

通过目标文件的section header table,我们可以找到所有的sections。section header table是一个类型为Elf32_Shdr(section header结构如图5.1)的数组。ELF header成员e_shoff指明了section header table据文件开始处的字节偏移量;成员e_shnum表明了table中有多少个表项;成员e_shentsize表示每个表项的大小(byte)。

section header table中某些表项位置(index)是被预留的,但是目标文件中其实没有这些预留indexes相应的sections。


SHN_UNDEF: 此表项标志一个未定义、丢失、无关或者其他无意义的section引用。也就是说如果某个section引用到了SHN_UNDEF,则这个section是个未定义的section

SHN_LORESERVE: 预留indexes范围的下界

SHN_LOPROC to SHN_HIPROC: 此范围中的indexes被预留给处理器相关的语义

SHN_ABS: 相应引用的绝对值

SHN_COMMON: 定义在这个section的符号是通用符号

SHN_HIRESERVE: 预留indexes范围的上界


Sections包含目标文件中除ELF header、program header table以及section header table以外的所有信息,目标文件的sections满足如下条件:

目标文件中每一个section都有唯一一个对应的section header来描述,但是有些section header可能没有对应的section存在(比如reserved section indexes里的section headers)

每个section占据文件中一段连续的空间(可能是空的)

section之间不能交叉,同一个byte不能同时被一个以上section拥有

目标文件可能包含闲置的空间,文件中的headers、sections可能没有占据目标文件中的每个字节,这些闲置空间中的数据是未赋值的。


5.1 Section Header(节头)

Section header结构如下


图5.1 Section Header结构

sh_name: section名字,但不是直接存储的名字,而是一个指向section header string table的一个index

sh_type: section内容和语义类别

sh_flags: 1-bit标志描述各种属性

sh_addr: 如果这个section会被加载到内存,这个成员就是此section在内存中的地址,否则为0

sh_offset: 此section相对于文件开始的偏移量(字节)

sh_size: section所占的空间大小(字节)

sh_link: 一个section header table index链接,含义取决于section类型

sh_info: 额外信息,含义取决于section类型

sh_addralign: 有些sections有地址对齐的限制,sh_addr模sh_addralign必须为0。 0和1都表示不用地址对齐

sh_entsize: 有些sections由一些固定大小的表项组成的表(比如符号表),对于这些sections,这个成员记录了每个表项的大小。对于不是这种类型的section,值为0


5.1.1 sh_type

section header成员sh_type指明这个section的语义


SHT_NULL: 如果sh_type为SHT_NULL,表明此section header是闲置的,文件中没有对应的section,并且section header中的其成员值未定

SHT_PROGBITS: 此section header指向的section存储了程序定义的信息,section的格式和意义都是程序自定义的

SHT_SYMTAB 和 SHT_DYNSYM: 存符号表的sections,SHT_SYMTAB section存所有的符号(包括用于动态链接的符号),SHT_DYNSYM section存有用于动态链接所需的最小符号表集(用于动态链接是节省空间)

SHT_STRTAB: section存的是字符串表(string table),一个目标文件可能有多个字符串表

SHT_RELA: 这种section存储具有明确附数(addends)的重定位条目(entries),比如Elf32_Rela,一个目标文件可能包含多个重定位sections

SHT_HASH: 这种section存储一个符号哈希表,参与动态链接的对象必须有一个符号哈希表

SHT_DYNAMIC: 存储动态链接信息的section

SHT_NOTE: 存储了标记此文件的某种信息

SHT_NOBITS: 说明此section不占任何空间,其他方面和SHT_PROGBITS类似。section虽不占空间,但是section header成员sh_offset可能不为0

SHT_REL: 这种section存储不具有明确附数(addends)的重定位条目(entries),比如Elf32_Rel, 一个目标文件可能包含多个重定位sections

SHT_SHLIB: 预留的section类型

SHT_LOPROC和SHT_HIPROC: 这两个值之间的类型是为处理器相关的语义预留的

SHT_LOUSER: 为应用程序预留的类型范围的下界

SHT_HIUSER:  为应用程序预留的类型范围的上界


5.1.2 预留的SHN_UNDEF section header table entry:Index 0

上面提到过,section header table中有一些预留的表项,其中第一个(index 0)为SHN_UNDEF,这个表项指向了没有定义的section(不存在)。此表项内容如下:



5.1.3 sh_flags

sh_flags用标志位描述section的属性


SHF_WRITE: 程序执行时,此section中的数据是可写的

SHF_ALLOC: 此section在程序运行时会占用内存。反过来说,某些sections不会被加载到程序内存映像中,这些sections的SHF_ALLOC属性是关闭的(为0)

SHF_EXECINSTR: 此section包含可执行的机器指令

SHF_MASKPROC: 这个值所指定的bits是为处理器相关的语义预留的


5.1.4 sh_link 和 sh_info

sh_link和sh_info根据section类型(sh_type)的不同,存储特殊信息



6. 特殊Sections

ELF文件中有多个包含程序和控制信息的sections,以下是一些系统使用的特殊sections,这些sections的类型和属性都是固定的



.bss: 这个section包含一些用于程序内存映像的未初始化数据。当程序开始执行时,系统会将这部分数据初始化为0,此section在ELF文件内不占任何空间(其sh_type为SHT_NOBITS)

.comment: 这个section包含版本控制信息

.data和.data1: 这两个sections包含用于程序内存映像的已初始化数据

.debug: 包含符号调试相关信息,其中内容未事先指定

.dynamic: 包含动态链接信息,这个section的属性包括了SHF_ALLOC

.dynstr: 包含用于动态链接的字符串信息,通常这些字符串是与符号表项相关联的名字

.dynsym: 动态链接符号表

.fini: 包含程序结束时执行的指令,如果程序正常退出,则系统会执行这个section中的指令

.got: 包含全局偏移量表

.hash: 符号哈希表

.init: 程序初始化时执行的指令,程序开始从main函数执行前需要执行的初始化代码

.interp: 程序解释器的路径名

.line: 包含用于符号调试的行号(line number)信息,描述源程序和机器指令直接的对应关系,内容未事先指定

.note: note section,之后介绍

.plt: 程序连接表(Procedure Linkage Table)

.relname和.relaname: 重定位信息,如果文件有一个包含重定位的可装载的段(segment),那这些sections的属性就会包含SHF_ALLOC,否则SHF_ALLOC关闭。name是可变的,根据此重定位应用的section的不同而不同,比如对于.text的重定位section,通常这个section的名字会叫.rel.text或者.rela.text.

.rodata和.rodata1: 这些sections包含一些只读数据,通常这些数据对应于程序内存映像中的不可写段(non-writable segment)

.shstrtab: section名字

.strtab: 字符串,通常这些字符串代表了与符号表项相关联的名字。如果文件中有一个可装载的段包含了这个符号字符串表,则此section的属性包括SHF_ALLOC

.symtab: 符号表,如果文件中有一个可装载的段包含了这个符号表,则此section的属性有SHF_ALLOC

.text: 包含程序的"文本“或者可执行指令


以点(.)为前缀的section名字是预留给系统sections的,当然如果section里的数据满足应用程序要求,应用也可以使用。应用可以使用不包含前缀的section名字,以避免与系统sections冲突。目标文件格式允许定义不在以上列表中的sections,一个目标文件可以包含多个同名的sections。

为处理器体系结构预留的section名字由体系机构缩写加上对应的section名字组成。


7. 字符串表(String Table)

字符串表sections包含了以’\0'结尾的字符串。目标文件使用这些字符串表示符号和section名字,其他section使用字符串表section中的index来引用一个字符串。第一个字节(index 0)被指定用来存储一个空字符‘\0',同样字符串表的最后一个字节也用来存放'\0',以保证所有字符串以'\0'结尾。index为0的字符串根据上下文表示没有名字或者名字为空。允许存在空的字符串section,空section的section header的sh_size为0.

每个section header成员sh_name都包含一个section header字符串表section内的index,这个section(section header string table section)可以从ELF header的e_shstrndx中获得。


8. 符号表(Symbol Table)

目标文件的符号表包含了用于定位和重定位程序符号定义和引用的信息,符号表的第一个表项(index 0)既表示第一个表项,也表示未定义的符号索引。符号表项的结构如下


st_name: 符号名字,通过目标文件的符号字符串表中的index表示,0表示没有名字

st_value: 关联符号的值,根据上下文不同,取值可能是一个绝对值,一个地址等

st_size: 很多符号具有相关联的大小,如一个对象的大小就是这个对象包含的字节的数量

st_info: 符号的类型(Type)和绑定Binding属性,高4位存Binding属性,低4位存类型属性

st_other: 目前为0

st_shndx: 每个符号表项都会关联到其他section,这个成员就存储了此符号表项关联的section在section header table中的index


8.1 Symbol Binding(符号绑定)

符号绑定属性决定了链接的可见性(visibility)和行为


STB_LOCAL: 本地符号,只在目标文件内部可见,同名的本地符号可在多个文件内共存且相互无关连

STB_GLOBAL: 全局符号,对所有参与链接(或生成程序内存映像)的目标文件都可见,其他文件可以引用此文件中的全局符号

STB_WEAK: 弱符号有点像全局符号,区别后面解释

STB_LOPROC - STB_HIPROC: 这个范围的值是预留给处理器相关语义的


全局和弱符号有两大不同之处:

I. 当链接器链接多个可重定位目标文件时,不允许存在多个同名的全局符号定义。相反,如果有一个全局符号了,与之同名的弱符号是合法的,链接器会忽略弱符号。类似的,如果有一个常规符号(common symbol,就是sh_shndx成员为SHN_COMMON),同名的弱符号也是合法的。

II. 当链接器搜索库文件时,会从中提取出包含未定义全局符号的成员,但是不会提取文档成员来解析未定义的弱符号。未解析的弱符号值为0。


所有本地符号都会先于弱符号和全局符号绑定,在’Sections‘中讲到过,符号表section的section header成员sh_info存有指向第一个非局部符号的符号表索引。


8.2 符号类型(Symbol Types)


STT_NOTYPE: 符号的类型不定

STT_OBJECT: 数据对象相关符号,比如一个变量、数组等

STT_FUNC: 函数或其他可执行代码

STT_SECTION: 此符号与一个section关联,这种类型的符号表项主要用于重定位,通常binding为STB_LOCAL

STT_FILE: 如果是文件类型,则此符号的名字就是这个目标文件对应的源文件的名字,文件符号binding为STB_LOCAL,它的section index为SHN_ABS,并且这个local符号通常为目标文件中第一个STB_LOCAL符号。

STT_LOPROC - STT_HIPROC: 预留给处理器相关语义


共享目标文件(shared object,比如.so文件)中的函数符号(STT_FUNC)具有特别的意义,当一个目标文件引用一个共享目标文件中的函数时,链接器会为这个引用符号自动创建一个程序链接表项(procedure linkage table entry)。共享目标文件中的非函数符号(不是STT_FUNC)不能通过程序链接表自动索引。

如果符号内容引用到了某section中的特定位置,那么此符号的section index成员st_shndx将存储指向此section对应的section header table index。当重定向改变section的位置时,此符号的内容随之更改以指向相同的内容。

一些特殊的section index值具有其他含义:

SHN_ABS: 具有绝对值的符号内容重定位时不会改变

SHN_COMMON: 标记一个还没分配的通用存储块,符号的内容需要对齐,有点像section的sh_addralign成员。链接器会在特定地址为这个符号分配一个大小为st_value整数倍的空间,符号的st_size指定size大小。

SHN_UNDEF: 符号未定义,当链接器将此目标文件与另一个定义了特定符号的目标文件组合到一起时,此文件中引用到那个符号的地方会链接到那个实际的符号定义。


前面提到过,符号表中第一个表项(index 0)是预留的,其内容如下:



8.3 符号值(Symbol Values)

符号表项成员st_value的意义对于不同目标文件类型来说有微小差别

重定位文件:对于section index(st_shndx)为SHN_COMMON的符号来说,st_value存储对齐条件

重定位文件:对于其他的st_shndx值(此时st_shndx指向一个section header entry),st_value存储的是某个已定义的符号在st_shndx指定的section中的偏移

可执行/共享目标文件:st_value是一个虚拟地址,主要用于动态链接。


9. 重定位(Relocation)

重定位就是将符号引用链接到实际的符号定义的过程。比如,当程序调用一个函数时,相关的调用指令必须转移到函数开始执行的地址。也就是说,重定位文件必须有足够的信息描述怎样改变他们的section内容,以使得可执行文件和共享目标文件具有正确的信息来创建程序内存映像。重定位条目(Relocation Entries)有如下结构:


r_offset: 此成员提供进行重定位的位置。对于重定位文件,这个值就是从文件开始到此位置的字节数偏移量。对于可执行或者共享目标文件,这个值是被重定位影响到得存储单元的虚拟地址。

r_info: 这个成员指定了必须进行重定位的符号表索引(symbol table index)以及重定位的类型。低8位存储重定位类型(relocation type),其他高位存符号表索引。

r_addend: 存储一个附加常量,用于计算存入可重定位域(relocatable field)的值


Elf32_Rela有一个r_addend成员,实际上Elf32_Rel也有一个隐含的r_addend值,根据处理器体系结构不同从这个两个结构中选一个来实现。


一个重定位Section会引用另外两个sections:一个符号表和一个要修改的section(a section to modify),section header成员sh_info和sh_link指定他们之间的关系。对于不同目标文件,重定位表项中的r_offset稍有差别:

重定位文件:r_offset存储一个section中的偏移,重定位section描述了怎样修改文件中的另一section,重定位偏移量指定了被修改section中的一个存储单元。

可执行 /共享目标文件:r_offset存储一个虚拟地址,为了使这些文件中的重定位条目(relocation entries)更有利于动态链接,这个值提供了一个从section偏移到虚拟地址的转换方法。


9.1 重定位类型(Relocation Types)

重定位条目描述怎样修改如下指令和数据域(0 - 31 bit)


word32: 表示一个占用4字节的32bit域,字节序域32bit因特尔体系结构一样


以下都是描述将重定位文件转变为可执行或共享目标文件的计算行为,链接器合并多个重定位文件生成结果,首先连接器会判定合并以及定位这些输入文件的方法,然后更新符号值,最后执行重定位。应用在可执行或者共享目标文件上的重定位也类似。以下是不同重定位计算行为的描述:

A: 使用附加数(addend)计算重定位域(relocatable field)的值

B: 执行期间一个共享对象被加载进内存的基地址,通常共享目标文件建立在0基虚拟地址上,但是执行的地址是不同的

G: 全局偏移表(Global offset table)中的一个偏移,指出重定位条目的符号在执行期间的地址

GOT: 全局偏移表(Global offset table)的地址

L: 符号所在的进程链接表项(procedure linkage table entry)的位置(section偏移或者地址),进程链接表项将函数调用重定向到合适的目的地址。链接器构建初始的进程链接表,动态链接器在执行期间会进行修改。

P: 被重定位的存储单元的位置(section偏移或地址),使用r_offset计算

S: 符号的值,此符号的index位于重定位项(relocation entry)中


重定位项(relocation entry)成员r_offset的值指明了被重定位的存储单元的第一个字节的偏移或者虚拟地址,重定位类型决定了改变哪些bits以及改变成什么值。SYSTEM V体系结构只使用Elf32_Rel结构体,被重定位的域持有附加数(addend)。以下是重定位类型:


某些重定位类型的计算方式比较复杂

R_386_GOT32: 计算从全局偏移表(global offset table)的基地址到该符号的全局偏移表项的距离,此外还指示链接器创建一个全局偏移表

R_386_PLT32: 计算此符号的进程链接表项(procedure linkage table entry),另外还指示链接器构建进程链接表

R_386_COPY: 链接器为动态链接创建这种重定位类型,它的r_offset成员指向一个可写段里的某个位置。符号表索引(symbol table index)指定了一个既存在于当前目标文件中也存在于某个共享目标文件中的符号。动态链接器会在执行期间将这个共享目标文件的符号内容拷贝到偏移所指定的位置。

R_386_GLOB_DAT: 生成一个全局偏移表条目(global offset table entry)指向指定符号的地址。这种特殊的重定位类型允许指定符号和全局偏移表条目间的对应关系

R_3862_JMP_SLOT: 同样为动态链接而创建,r_offset成员指明了进程链接表条目(procedure linkage table entry)的位置。动态链接器修改进程链接表条目将控制转交到特定符号地址

R_386_RELATIVE: 为动态链接创建,r_offset成员提供一个共享目标文件中的位置,此位置中包含了表示相对地址的内容。动态链接器通过计算这个相对地址已经这个共享目标文件被加载的虚拟地址的和得到相应的虚拟地址

R_386_GOTOFF: 计算符号值以及全局偏移表(global offset table)的差异,另外还指示连接器创建全局偏移表

R_386_GOTPC: 与R_386_PC32类似,不同之处是这个类型使用全局偏移表地址来计算,通常这种类型的重定位引用的符号为_GLOBAL_OFFSET_TABLE_,此外它也指示链接器创建全局偏移表。

0 0
原创粉丝点击