ELF⽂件格式详解
ARM的可执⾏⽂件的格式是ELF格式⽂件,下⽂对ELF格式做个详细的介绍。
序⾔
⽂件
导⾔
ELF头(ELFHeader)
Sections
String表(StringTable)
Symbol表(SymbolTable)
重定位(Relocation)
2.程序装载与动态连接
导⾔
Program头(ProgramHeader)
Program装载(ProgramLoading)
Dynamic连接(DynamicLinking)
RY
CLibrary
________________________________________________________________
导⾔
________________________________________________________________
ELF:可执⾏连接格式
可执⾏连接格式是UNIX系统实验室(USL)作为应⽤程序⼆进制接⼝
(ApplicationBinaryInterface(ABI)⽽开发和发布的。⼯具接⼝标准委
员会(TIS)选择了正在发展中的ELF标准作为⼯作在32位INTEL体系上不同操
作系统之间可移植的⼆进制⽂件格式。
假定开发者定义了⼀个⼆进制接⼝集合,ELF标准⽤它来⽀持流线型的软件
发展。应该减少不同执⾏接⼝的数量。因此可以减少重新编程重新编译的
代码。
关于这⽚⽂档
这篇⽂档是为那些想创建⽬标⽂件或者在不同的操作系统上执⾏⽂件的开发
着准备的。它分以下三个部分:
*第⼀部分,“⽬标⽂件ObjectFiles”描述了ELF⽬标⽂件格式三种主要
的类型。
*第⼆部分,“程序转载和动态连接”描述了⽬标⽂件的信息和系统在创建
运⾏时程序的⾏为。
*第三部分,“C语⾔库”列出了所有包含在libsys中的符号,标准的ANSIC
和libc的运⾏程序,还有libc运⾏程序所需的全局的数据符号。
注意:参考的X86体系已经被改成了Intel体系。
________________________________________________________________
1.⽬标⽂件(Objectfile)
________________________________________________________________
=========================序⾔=========================
第⼀部分描述了iABI的object⽂件的格式,被称为ELF(Executable
andLinkingFormat).在object⽂件中有三种主要的类型。
*⼀个可重定位(relocatable)⽂件保存着代码和适当的数据,⽤来和其他的
object⽂件⼀起来创建⼀个可执⾏⽂件或者是⼀个共享⽂件。
*⼀个可执⾏(executable)⽂件保存着⼀个⽤来执⾏的程序;该⽂件指出了
exec(BA_OS)如何来创建程序进程映象。
*⼀个共享object⽂件保存着代码和合适的数据,⽤来被下⾯的两个链接器
链接。第⼀个是连接编辑器[请参看ld(SD_CMD)],可以和其他的可重定位和
共享object⽂件来创建其他的object。第⼆个是动态链接器,联合⼀个
可执⾏⽂件和其他的共享object⽂件来创建⼀个进程映象。
⼀个object⽂件被汇编器和联接器创建,想要在处理机上直接运⾏的object
⽂件都是以⼆进制来存放的。那些需要抽象机制的程序,⽐如象shell脚本,
是不被接受的。
在介绍性的材料过后,第⼀部分主要围绕着⽂件的格式和关于如何建⽴程序。
第⼆部分也描述了object⽂件的⼏个组成部分,集中在执⾏程序所必须的信息上。
⽂件格式
Object⽂件参与程序的联接(创建⼀个程序)和程序的执⾏(运⾏⼀个程序)。
object⽂件格式提供了⼀个⽅便有效的⽅法并⾏的视⾓看待⽂件的内容,
在他们的活动中,反映出不同的需要。例1-1图显⽰了⼀个object⽂件的
组织图。
+图1-1:Object⽂件格式
Linking视⾓Execution视⾓
==========================
ELFheaderELFheader
Programheadertable(optional)Programheadertable
Section1Segment1
...Segment2
SectionheadertableSectionheadertable(optional)
⼀个ELF头在⽂件的开始,保存了路线图(roadmap),描述了该⽂件的组织情况。
ctions保存着object⽂件的信息,从连接⾓度看:包括指令,数据,
符号表,重定位信息等等。特别ctions的描述会出项在以后的第⼀部分。
第⼆部分讨论了段和从程序的执⾏⾓度看⽂件。
假如⼀个程序头表(programheadertable)存在,那么它告诉系统如何来创建⼀
个进程的内存映象。被⽤来建⽴进程映象(执⾏⼀个程序)的⽂件必须要有⼀个程
序头表(programheadertable);可重定位⽂件不需要这个头表。⼀个
ction头表(ctionheadertable)包含了描述⽂件ctions的信息。每个
ction在这个表中有⼀个⼊⼝;每个⼊⼝给出了该ction的名字,⼤⼩,
等等信息。在联接过程中的⽂件必须有⼀个ction头表;其他object⽂件可要
可不要这个ction头表。
注意:虽然图显⽰出程序头表⽴刻出现在⼀个ELF头后,ction头表跟着其他
ction部分出现,事实是的⽂件是可以不同的。此外,ctions和段(gments)
没有特别的顺序。只有ELF头(elfheader)是在⽂件的固定位置。
数据表⽰
object⽂件格式⽀持8位、32位不同的处理器。不过,它试图努⼒的在更⼤
或更⼩的体系上运⾏。因此,object⽂件描绘⼀些控制数据需要⽤与机器
⽆关的格式,使它尽可能的⽤⼀般的⽅法甄别object⽂件和描述他们的内容。
在object⽂件中剩余的数据使⽤⽬标处理器的编码⽅式,不管⽂件是在哪台
机⼦上创建的。
+图1-2:32-BitDataTypes
NameSizeAlignmentPurpo
========================
Elf32_Addr44Unsignedprogramaddress
Elf32_Half22Unsignedmediuminteger
Elf32_Off44Unsignedfileofft
Elf32_Sword44Signedlargeinteger
Elf32_Word44Unsignedlargeinteger
unsignedchar11Unsignedsmallinteger
所有的object⽂件格式定义的数据结构是⾃然⼤⼩(naturalsize),为相关
的类型调整指针。如果需要,数据结构中明确的包含了确保4字节对齐的填
充字段。来使结构⼤⼩是4的倍数。数据从⽂件的开始也有适当的对齐。
例如,⼀个包含了Elf32_Addr成员的结构将会在⽂件内对齐到4字节的边界上。
因为移植性的原因,ELF不使⽤位字段。
==========================ELFHeader==========================
⼀些object⽂件的控制结构能够增长的,所以ELF头包含了他们⽬前的⼤⼩。
假如object⽂件格式改变,程序可能会碰到或⼤或⼩他们不希望的控制结构。
程序也有可能忽略额外(extra)的信息。
对待来历不明(missing)的信息依靠上下⽂来解释,假如扩展被定义,它们
将会被指定。
+图1-3:ELFHeader
#defineEI_NIDENT16
typedefstruct{
unsignedchare_ident[EI_NIDENT];
Elf32_Halfe_type;
Elf32_Halfe_machine;
Elf32_Worde_version;
Elf32_Addre_entry;
Elf32_Offe_phoff;
Elf32_Offe_shoff;
Elf32_Worde_flags;
Elf32_Halfe_ehsize;
Elf32_Halfe_phentsize;
Elf32_Halfe_phnum;
Elf32_Halfe_shentsize;
Elf32_Halfe_shnum;
Elf32_Halfe_shstrndx;
}Elf32_Ehdr;
*e_ident
这个最初的字段标⽰了该⽂件为⼀个object⽂件,提供了⼀个机器⽆关
的数据,解释⽂件的内容。在下⾯的ELF的鉴别(ELFIdentification)
部分有更详细的信息。
*e_type
该成员确定该object的类型。
NameValueMeaning
================
ET_NONE0Nofiletype
ET_REL1Relocatablefile
ET_EXEC2Executablefile
ET_DYN3Sharedobjectfile
ET_CORE4Corefile
ET_LOPROC0xff00Processor-specific
ET_HIPROC0xffffProcessor-specific
虽然CORE的⽂件内容未被指明,类型ET_CORE是保留的。
值从ET_LOPROC到ET_HIPROC(包括ET_HIPROC)是为特殊的处理器保留的。
如有需要,其他保留的变量将⽤在新的object⽂件类型上。
*e_machine
该成员变量指出了运⾏该程序需要的体系结构。
NameValueMeaning
================
EM_NONE0Nomachine
EM_M321AT&TWE32100
EM_SPARC2SPARC
EM_3863Intel80386
EM_68K4Motorola68000
EM_88K5Motorola88000
EM_8607Intel80860
EM_MIPS8MIPSRS3000
如有需要,其他保留的值将⽤到新的机器类型上。特殊处理器名使⽤机器名来
区别他们。例如,下⾯将要被提到的成员flags使⽤前缀EF_;在⼀台EM_XYZ机器
上,flag称为WIDGET,那么就称为EF_XYZ_WIDGET。
*e_version
这个成员确定object⽂件的版本。
NameValueMeaning
================
EV_NONE0Invalidversion
EV_CURRENT1Currentversion
值1表⽰原来的⽂件格式;创建新版本就⽤>1的数。EV_CURRENT值(上⾯给
出为1)如果需要将指向当前的版本号。
*e_entry
该成员是系统第⼀个传输控制的虚拟地址,在那启动进程。假如⽂件没有
如何关联的⼊⼝点,该成员就保持为0。
*e_phoff
该成员保持着程序头表(pr众的成语 ogramheadertable)在⽂件中的偏移量(以字节计数)。
假如该⽂件没有程序头表的的话,该成员就保持为0。
*e_shoff
该成员保持着ction头表(ctionheadertable)在⽂件中的偏移量(以字节
计数)。假如该⽂件没有ction头表的的话,该成员就保持为0。
*e_flags
该成员保存着相关⽂件的特定处理器标志。
flag的名字来⾃于EF_
部分的flag的定义。
*e_ehsize
该成员保存着ELF头⼤⼩(以字节计数)。
*e_phentsize
该成员保存着在⽂件的程序头表(programheadertable)中⼀个⼊⼝的⼤⼩
(以字节计数)。所有的⼊⼝都是同样的⼤⼩。
*e_phnum
该成员保存着在程序头表中⼊⼝的个数。因此,e_phentsize和e_phnum
的乘机就是表的⼤⼩(以字节计数).假如没有程序头表(programheadertable),
e_phnum变量为0。
*e_shentsize
该成员保存着ction头的⼤⼩(以字节计数)。⼀个ction头是在ction
头表(ctionheadertable)的⼀个⼊⼝;所有的⼊⼝都是同样的⼤⼩。
*e_shnum
该成员保存着在ctionheadertable中的⼊⼝数⽬。因此,e_shentsize和
e_shnum的乘积就是ction头表的⼤⼩(以字节计数)。
假如⽂件没有ction头表,e_shnum值为0。
*e_shstrndx
该成员保存着跟ction名字字符表相关⼊⼝的ction头表(ctionheader
table)索引。假如⽂件中没有ction名字字符表,该变量值为SHN_UNDEF。
更详细的信息看下⾯“Sections”和字符串表(“StringTable”)。
ELF鉴别(Identification)
在上⾯提到的,ELF提供了⼀个object⽂件的框架结构来⽀持多种处理机,多
样的数据编码⽅式,多种机器类型。为了⽀持这个object⽂件家族,最初的⼏
个字节指定⽤来说明如何解释该⽂件,独⽴于处理器,与⽂件剩下的内容⽆关。
ELF头(也就是object⽂件)最初的⼏个字节与成员e_ident相⼀致。
+图1-4:e_ident[]IdentificationIndexes
NameValuePurpo
================
EI_MAG00Fileidentification
EI_MAG11Fileidentification
EI_MAG22Fileidentification
EI_MAG33Fileidentification
EI_CLASS4Fileclass
EI_DATA5Dataencoding
EI_VERSION6Fileversion
EI_PAD7Startofpaddingbytes
EI_NIDENT16Sizeofe_ident[]
通过索引访问字节,以下的变量被定义。
*EI_MAG0toEI_MAG3
⽂件的前4个字符保存着⼀个魔术数(magicnumber),⽤来确定该⽂件是否
为ELF的⽬标⽂件。
NameValuePosition
=================
ELFMAG00x7fe_ident[EI_MAG0]
ELFMAG1'E'e_ident[EI_MAG1]
ELFMAG2'L'e_ident[EI_MAG2]
ELFMAG3'F'e_ident[EI_MAG3]
*EI_CLASS
接下来的字节是e_ident[EI_CLASS],⽤来确定⽂件的类型或者说是能⼒。
NameValueMeaning
================
ELFCLASSNONE0Invalidclass
ELFCLASS32132-bitobjects
ELFCLASS64264-bitobjects
⽂件格式被设计成在不同⼤⼩机器中可移植的,在⼩型机上的不⽤⼤型机上
的尺⼨。类型ELFCLASS32⽀持虚拟地址空间最⼤可达4GB的机器;使⽤上⾯
定义过的基本类型。
类型ELFCLASS64为64位体系的机器保留。它的出现表明了object⽂件可能
改变,但是64位的格式还没有被定义。如果需要,其他类型将被定义,会
有不同的类型和不同⼤⼩的数据尺⼨。
*EI_DATA
字节e_ident[EI_DATA]指定了在object⽂件中特定处理器数据的编码
⽅式。当前定义了以下编码⽅式。
NameValueMeaning
================
ELFDATANONE0Invaliddataencoding
ELFDATA2LSB1Seebelow
ELFDATA2MSB2Seebelow
更多的关于编码的信息出现在下⾯。其他值保留,将被分配⼀个新的编码
⽅式,当然如果必要的话。
*EI_VERSION
字节e_ident[EI_VERSION]表明了ELF头的版本号。
现在这个变量的是⼀定要设为EV_CURRENT,作为上⾯e_version的解释。
*EI_PAD
该变量标识了在e_ident中开始的未使⽤的字节。那些字节保留并被设置为
0;程序把它们从object⽂件中读出但应该忽略。假如当前未被使⽤的字节
有了新的定义,EI_PAD变量将来会被改变。
⼀个⽂件的数据编码指出了如何来解释⼀个基本的object⽂件。在上述的
描述中,类型ELFCLAS32⽂件使⽤占⽤1,2和4字节的⽬标⽂件。下⾯定义的
编码⽅式,⽤下⾯的图来描绘。数据出现在左上⾓。
ELFDATA2LSB编码指定了2的补数值。
最⼩有意义的字节占有最低的地址。
+图1-5:DataEncodingELFDATA2LSB
0------+
0x0102|01|
+------+
0------1------+
0x010204|02|01|
+------+------+
0------1------2------3------+
0x01020304|04|03|02|01|
+------+------+------+------+
ELFDATA2LSB编码指定了2的补数值。
最⼤有意义的字节占有最低的地址。
+图1-6:DataEncodingELFDATA2MSB
0------+
0x0102|01|
+------+
0------1------+
0x010204|01|02|
+------+------+
0------1------2------3------+
0x01020304|01|02|03|04|
+------+------+------+------+
机器信息
为了确定⽂件,32位Intel体系结构的需要以下的变量。
+图1-7:32-bitIntelArchitectureIdentification,e_ident
PositionValue
=============
e_ident[EI_CLASS]ELFCLASS32
e_ident[EI_DATA]ELFDATA2LSB
处理器确认ELF头⾥的e_machine成员,该成员必须为EM_386。
ELF报头⾥的e_flags成员保存了和⽂件相关的位标记。32位Intel体系上未
定义该标记;所以这个成员应该为0;
===========================Sections===========================
⼀个object⽂件的ctionheadertable可以让我们定位所有的ctions。
ctionheadertable是个Elf32_Shdr结构的数组(下⾯描述)。⼀个ction
报头表(ctionheadertable)索引是这个数组的下标。ELFheadertable
的e_shoff成员给出了ction报头表的偏移量(从⽂件开始的计数)。e_shnum
告诉我们ction报头表中包含了多少个⼊⼝;e_shentsize给出了每个
⼊⼝的⼤⼩。
⼀些ction报头表索引是保留的;那些特别的索引在⼀个object⽂件中
将没有与之对应ctions。
+图1-8:SpecialSectionIndexes
NameValue
=========
SHN_UNDEF0
SHN_LORESERVE0xff00
SHN_LOPROC0xff00
SHN_HIPROC0xff1f
SHN_ABS0xfff1
SHN_COMMON0xfff2
SHN_HIRESERVE0xffff
*SHN_UNDEF
该值表明没有定义,缺少,不相关的或者其他涉及到的⽆意义的ction。
例如,标号“defined”相对于ction索引号SHN_UNDEF是⼀个没有被
定义的标号。
注意:虽然索引0保留作为未定义的值,ction报头表包含了⼀个索引0的
⼊⼝。因此,假如ELF报头说⼀个⽂件的ction报头表中有6个ction⼊⼝
的话,e_shnum的值应该是从0到5。最初的⼊⼝的内容以后在这个ction中
被指定。
*SHN_LORESERVE
该值指定保留的索引范围的最⼩值。
*SHN_LOPROCthroughSHN_HIPROC
该值包含了特定处理器语意的保留范围。
*SHN_ABS
该变量是相对于相应参考的绝对地址。
例如,ction号的标号是绝对地址,不被重定位影响。
*SHN_COMMON
该ction的标号是⼀个公共(common)的标号,就象FORTRANCOMMON
或者不允许的C扩展变量。
*SHN_HIRESERVE
该值指定保留的索引范围的上限。系统保留的索引值是从SHN_LORESERVE
到SHN_HIRESERVE;该变量不涉及到ction报头表(ctionheadertable)。
因此,ction报头表不为保留的索引值包含⼊⼝。
ctions包含了在⼀个object⽂件中的所有信息,除了ELF报头,程序报头
表(programheadertable),和ction报头表(ctionheadertable)。
此外,object⽂件的ctions满⾜⼏天条件:
*每个在object⽂件中的ction都有⾃⼰的⼀个ction的报头来描述它。
ction头可能存在但ction可以不存在。
*每个ction在⽂件中都占有⼀个连续顺序的空间(但可能是空的)。
*⽂件中的Sections不可能重复。⽂件中没有⼀个字节既在这个ction中
⼜在另外的⼀个ction中。
*object⽂件可以有"⾮活动的"空间。不同的报头和ctions可以不覆盖到
object⽂件中的每个字节。"⾮活动"数据内容是未指定的。
⼀个ction头有如下的结构。
+图1-9:SectionHeader
typedefstruct{
Elf32_Wordsh_name;
Elf32_Wordsh_type;
Elf32_Wordsh_flags;
Elf32_Addrsh_addr;
Elf32_Offsh_offt;
Elf32_Wordsh_size;
Elf32_Wordsh_link;
Elf32_Wordsh_info;
Elf32_Wordsh_addralign;
Elf32_Wordsh_entsize;
}Elf32_Shdr;
*sh_name
该成员指定了这个ction的名字。它的值是ction报头字符表ction的
索引。[看以下的“StringTable”],以NULL空字符结束。
*sh_type
该成员把ctions按内容和意义分类。ction的类型和他们的描述在下⾯。
*sh_flags
ctions⽀持位的标记,⽤来描述多个属性。
Flag定义出现在下⾯。
*sh_addr
假如该ction将出现在进程的内存映象空间⾥,该成员给出了⼀个该ction
在内存中的位置。否则,该变量为0。
*sh_offt
该成员变量给出了该ction的字节偏移量(从⽂件开始计数)。SHT_NOBITS
类型的ction(下⾯讨论)在⽂件中不占空间,它的sh_offt成员定位在
⽂件中的概念上的位置。
*sh_size
该成员给你了ction的字节⼤⼩。除⾮这个ction的类型为SHT_NOBITS,
否则该ction将在⽂件中将占有sh_size个字节。SHT_NOBITS类型的ction
可能为⾮0的⼤⼩,但是不占⽂件空间。
*sh_link
该成员保存了⼀个ction报头表的索引连接,它的解释依靠该ction的
类型。以下⼀个表描述了这些值。
*sh_info
该成员保存着额外的信息,它的解释依靠该ction的类型。以下⼀个表描
述了这些值。
*sh_addralign
⼀些ctions有地址对齐的约束。例如,假如⼀个ction保存着双字,系统
就必须确定整个ction是否双字对齐。所以sh_addr的值以sh_addralign的值
作为模,那么⼀定为0。当然的,仅仅0和正的2的次⽅是允许的。值0和1意味
着该ction没有对齐要求。
*sh_entsize
⼀些ctions保存着⼀张固定⼤⼩⼊⼝的表,就象符号表。对于这样⼀个
ction来说,该成员给出了每个⼊⼝的字节⼤⼩。如果该ction没有
保存着⼀张固定⼤⼩⼊⼝的表,该成员就为0。
ction头成员sh_type指出了ction的语意。
+图1-10:SectionTypes,sh_type
NameValue
=========
SHT_NULL0
SHT_PROGBITS1
SHT_SYMTAB2
SHT_STRTAB3
SHT_RELA4
SHT_HASH5
SHT_DYNAMIC6
SHT_NOTE7
SHT_NOBITS8
SHT_REL9
SHT_SHLIB10
SHT_DYNSYM11
SHT_LOPROC0x70000000
SHT_HIPROC0x7fffffff
SHT_LOUSER0x80000000
SHT_HIUSER0xffffffff
*SHT_NULL
该值表明该ction头是⽆效的;它没有相关的ction。
该ction的其他成员的值都是未定义的。
*SHT_PROGBITS
该ction保存被程序定义了的⼀些信息,它的格式和意义取决于程序本⾝。
*SHT_SYMTABandSHT_DYNSYM
那些ctions保存着⼀个符号表(symboltable)。⼀般情况下,⼀个
object⽂件每个类型ction仅有⼀个,但是,在将来,这个约束可能被放宽。
典型的是,SHT_SYMTAB为连接器提供标号,当然它也有可能被动态连接时使⽤。
作为⼀个完整的符号表,它可能包含了⼀些动态连接时不需要的标号。
因此,⼀个object⽂件可能也包含了⼀个SHT_DYNSYM的ction,它保存着
⼀个动态连接时所需最⼩的标号集合来节省空间。
看下⾯符号表“SymbolTable”的细节。
*SHT_STRTAB
该ction保存着⼀个字符串表。⼀个object⽂件可以包含多个字符串表的
ction。看下⾯字符串表“StringTable”的细节。
*SHT_RELA
该ction保存着具有明确加数的重定位⼊⼝。就象object⽂件32位的
Elf32_Rela类型。⼀个object⽂件可能有多个重定位的ctions。具体细节
看重定位``Relocation''部分。
*SHT_HASH
该标号保存着⼀个标号的哈希(hash)表。所有的参与动态连接的object
⼀定包含了⼀个标号哈希表(hashtable)。当前的,⼀个object⽂件
可能只有⼀个哈希表。详细细节看第⼆部分的哈希表"HashTable"。
*SHT_DYNAMIC
该ction保存着动态连接的信息。当前的,⼀个object可能只有⼀个动态
的ction,但是,将来这个限制可能被取消。详细细节看第⼆部分的动态
ction(“DynamicSection”)。
*SHT_NOTE
该ction保存着其他的⼀些标志⽂件的信息。详细细节看第⼆部分的“Note
Section”。
*SHT_NOBITS
该类型的ction在⽂件中不占空间,但是类似SHT_PROGBITS。尽管该ction
不包含字节,sh_offt成员包含了概念上的⽂件偏移量。
*SHT_REL
该ction保存着具有明确加数的重定位的⼊⼝。
就象object⽂件32位类型Elf32_Rel类型。⼀个object⽂件可能有多个
重定位的ctions。具体细节看重定位``Relocation''部分。
*SHT_SHLIB
该ction类型保留但语意没有指明。包含这个类型的ction的程序
是不符合ABI的。
*SHT_LOPROCthroughSHT_HIPROC
在这范围之间的值为特定处理器语意保留的。
*SHT_LOUSER
该变量为应⽤程序保留的索引范围的最⼩边界。
*SHT_HIUSER
该变量为应⽤程序保留的索引范围的最⼤边界。在SHT_LOUSER和HIUSER的
ction类型可能被应⽤程序使⽤,这和当前或者将来系统定义的ction
类型是不⽭盾的。
其他ction类型的变量是保留的。前⾯提到过,索引0(SHN_UNDEF)的ction
头存在的,甚⾄索引标记的是未定义的ction引⽤。这个⼊⼝保存着以下的
信息。
+图1-11:SectionHeaderTableEntry:Index0
NameValueNote
=============
sh_name0Noname
sh_typeSHT_NULLInactive
sh_flags0Noflags
sh_addr0Noaddress
sh_offt0Nofileofft
sh_size0Nosize
sh_linkSHN_UNDEFNolinkinformation
sh_info0Noauxiliaryinformation
sh_addralign0Noalignment
sh_entsize0Noentries
⼀个ction报头(ctionheadertable)的sh_flags成员保存着1位标记,
⽤来描述ction的属性。以下是定义的值;其他的值保留。
+图1-12:SectionAttributeFlags,sh_flags
NameValue
=========
SHF_WRITE0x1
SHF_ALLOC0x2
SHF_EXECINSTR0x4
SHF_MASKPROC0xf0000000
假如在sh_flags中的⼀个标记位被设置,该ction相应的属性也被打开。
否则,该属性没有被应⽤。未明的属性就设为0。
*SHF_WRITE
该ction包含了在进程执⾏过程中可被写的数据。
*SHF_ALLOC
该ction在进程执⾏过程中占据着内存。⼀些控制ction不存在⼀个
object⽂件的内存映象中;对于这些ctions,这个属性应该关掉。
*SHF_EXECINSTR
该ction包含了可执⾏的机器指令。
*SHF_MASKPROC
所有的包括在这掩码中的位为特定处理语意保留的。
在ction报头中,两个成员sh_link和sh_info的解释依靠该ction的类型。
+图1-13:sh_linkandsh_infoInterpretation
sh_typesh_linksh_info
=====================
SHT_DYNAMICThectionheaderindexof0
thestringtableudby
entriesinthection.
SHT_HASHThectionheaderindexof0
thesymboltabletowhichthe
hashtableapplies.
SHT_REL,ThectionheaderindexofThectionheaderindexof
SHT_tiontowhichthe
relocationapplies.
SHT_SYMTAB,ThectionheaderindexofOnegreaterthanthesymbol
SHT_ndexofthelastlocal
symbol(bindingSTB_LOCAL).
otherSHN_UNDEF0
SpecialSections特殊的Sections
不同的ctions保存着程序和控制信息。下⾯列表中的ction被系统使⽤,
指⽰了类型和属性。
+图1-14:SpecialSections
NameTypeAttributes
==================
.bssSHT_NOBITSSHF_ALLOC+SHF_WRITE
.commentSHT_PROGBITSnone
.dataSHT_PROGBITSSHF_ALLOC+SHF_WRITE
.data1SHT_PROGBITSSHF_ALLOC+SHF_WRITE
.debugSHT_PROGBITSnone
.dynamicSHT_DYNAMICebelow
.dynstrSHT_STRTABSHF_ALLOC
.dynsymSHT_DYNSYMSHF_ALLOC
.finiSHT_PROGBITSSHF_ALLOC+SHF_EXECINSTR
.gotSHT_PROGBITSebelow
.hashSHT_HASHSHF_ALLOC
.initSHT_PROGBITSSHF_ALLOC+SHF_EXECINSTR
.interpSHT_PROGBITSebelow
.lineSHT_PROGBITSnone
.noteSHT_NOTEnone
.pltSHT_PROGBITSebelow
.rel
.rela
.rodataSHT_PROGBITSSHF_ALLOC
.rodata1SHT_PROGBITSSHF_ALLOC
.shstrtabSHT_STRTABnone
.strtabSHT_STRTABebelow
.symtabSHT_SYMTABebelow
.textSHT_PROGBITSSHF_ALLOC+SHF_EXECINSTR
*.bss
该ctiopn保存着未初始化的数据,这些数据存在于程序内存映象中。
通过定义,当程序开始运⾏,系统初始化那些数据为0。该ction不占
⽂件空间,正如它的ction类型SHT_NOBITS指⽰的⼀样。
*.comment
该ction保存着版本控制信息。
*.1
这些ctions保存着初始化了的数据,那些数据存在于程序内存映象中。
*.debug
该ction保存着为标号调试的信息。该内容是未指明的。
*.dynamic
该ction保存着动态连接的信息。该ction的属性将包括SHF_ALLOC位。
是否需要SHF_WRITE是跟处理器有关。第⼆部分有更详细的信息。
*.dynstr
该ction保存着动态连接时需要的字符串,⼀般情况下,名字字符串关联着
符号表的⼊⼝。第⼆部分有更详细的信息。
*.dynsym
该ction保存着动态符号表,如“SymbolTable”的描述。第⼆部分有更
详细的信息。
*.fini
该ction保存着可执⾏指令,它构成了进程的终⽌代码。
因此,当⼀个程序正常退出时,系统安排执⾏这个ction的中的代码。
*.got
该ction保存着全局的偏移量表。看第⼀部分的“SpecialSections”和
第⼆部分的“GlobalOfftTable”获得更多的信息。
*.hash
该ction保存着⼀个标号的哈希表。看第⼆部分的“HashTable”获得更多
的信息。
*.init
该ction保存着可执⾏指令,它构成了进程的初始化代码。
因此,当⼀个程序开始运⾏时,在main函数被调⽤之前(c语⾔称为main),
系统安排执⾏这个ction的中的代码。
*.interp
该ction保存了程序的解释程序(interpreter)的路径。假如在这个ction
中有⼀个可装载的段,那么该ction的属性的SHF_ALLOC位将被设置;否则,
该位不会被设置。看第⼆部分获得更多的信息。
*.line
该ction包含编辑字符的⾏数信息,它描述源程序与机器代码之间的对于
关系。该ction内容不明确的。
*.note
该ction保存⼀些信息,使⽤“NoteSection”(在第⼆部分)中提到的格式。
*.plt
该ction保存着过程连接表(ProcedureLinkageTable)。看第⼀部分的
``SpecialSections''和第⼆部分的“ProcedureLinkageTable”。
*.rel
这些ction保存着重定位的信息,看下⾯的``Relocation''描述。
假如⽂件包含了⼀个可装载的段,并且这个段是重定位的,那么该ction的
属性将设置SHF_ALLOC位;否则该位被关闭。按照惯例,
的ction来提供。因此,⼀个重定位的ction适⽤的是.text,那么该名字
就为.或者是.。
*.1
这些ction保存着只读数据,在进程映象中构造不可写的段。看第⼆部分的
``ProgramHeader''获得更多的资料。
*.shstrtab
该ction保存着ction名称。
*.strtab
该ction保存着字符串,⼀般地,描述名字的字符串和⼀个标号的⼊⼝相关
联。假如⽂件有⼀个可装载的段,并且该段包括了符号字符串表,那么ction
的SHF_ALLOC属性将被设置;否则不设置。
*.symtab
该ction保存着⼀个符号表,正如在这个ction⾥``SymbolTable''的
描述。假如⽂件有⼀个可装载的段,并且该段包含了符号表,那么ction
的SHF_ALLOC属性将被设置;否则不设置。
*.text
该ction保存着程序的``text''或者说是可执⾏指令。
前缀是点(.)的ction名是系统保留的,尽管应⽤程序可以⽤那些保留的
ction名。应⽤程序可以使⽤不带前缀的名字以避免和系统的ctions
冲突。object⽂件格式可以让⼀个定义的ction部分不出现在上⾯的列
表中。⼀个object⽂件可以有多个同样名字的ction。
为处理器体系保留的ction名的形成是通过置换成⼀个体系名的缩写。
该名字应该取⾃体系名,e_machine使⽤的就是。例如,.就是
在FOO体系上定义的名字。
现存的扩展名是历史遗留下来的。
Pre-existingExtensions
=======================
.
.4
.o
.t
.conflict
===================StringTable字符串表=========================
Stringtablections保存着以NULL终⽌的⼀系列字符,⼀般我们称为字
符串。object⽂件使⽤这些字符串来描绘符号和ction名。⼀个字符串的
参考是⼀个stringtablection的索引。第⼀个字节,即索引0,被定义保
存着⼀个NULL字符。同样的,⼀个stringtable的最后⼀个字节保存着⼀个
NULL字符,所有的字符串都是以NULL终⽌。索引0的字符串是没有名字或者说
是NULL,它的解释依靠上下⽂。⼀个空的stringtablection是允许的;
它的ctionheader的成员sh_size将为0。对空的stringtable来说,⾮0的
索引是没有⽤的。
⼀个ttion头的sh_name成员保存了⼀个对应于该tion头字符表部分
的索引(就象ELF头的e_shstrndx成员所特指的那样。下表列出了⼀个有25字节
的字符串表(这些字符串和不同的索引相关联):
Index+0+1+2+3+4+5+6+7+8+9
=========================
0/0name./0Var
10iable/0able
20/0/0xx/0
+Figure1-15:StringTableIndexes
IndexString
===========
0none
1"name."
7"Variable"
11"able"
16"able"
24nullstring
如上所⽰,⼀个字符串表可能涉及该ction中的任意字节。⼀个字符串可能
引⽤不⽌⼀次;引⽤⼦串的情况是可能存在的;⼀个字符串也可能被引⽤若⼲次;⽽
不被引⽤的字符串也是允许存在的。
====================SymbolTable符号表=========================
⼀个object⽂件的符号表保存了⼀个程序在定位和重定位时需要的定义和引⽤的信息。
⼀个符号表索引是相应的下标。0表项特指了该表的第⼀个⼊⼝,就象未定义的符号
索引⼀样。初始⼊⼝的内容在该ction的后续部分被指定。
NameValue
=========
STN_UNDEF0
⼀个符号表⼊⼝有如下的格式:
+Figure1-16:SymbolTableEntry
typedefstruct{
Elf32_Wordst_name;
Elf32_Addrst_value;
Elf32_Wordst_size;
unsignedcharst_info;
unsignedcharst_other;
Elf32_Halfst_shndx;
}Elf32_Sym;
*st_name
该成员保存了进⼊该object⽂件的符号字符串表⼊⼝的索引(保留了符号名的表达字符)。
如果该值不为0,则它代表了给出符号名的字符串表索引。否则,该符号⽆名。
注意:ExternalC符号和object⽂件的符号表有相同的名称。
*st_value
该成员给出了相应的符号值。它可能是绝对值或地址等等(依赖于上下⽂);
细节如下所述。
*st_size
许多符号和⼤⼩相关。⽐如,⼀个数据对象的⼤⼩是该对象所包含的字节数⽬。
如果该符号的⼤⼩未知或没有⼤⼩则这个成员为0。
*st_info
成员指出了符号的类型和相应的属性。相应的列表如下所⽰。下⾯的代码说明了
如何操作该值。
#defineELF32_ST_BIND(i)((i)>>4)
#defineELF32_ST_TYPE(i)((i)&0xf)
#defineELF32_ST_INFO(b,t)(((b)<<4)+((t)&0xf))
*st_other
该成员⽬前为0,没有含义。
*st_shndx
每⼀个符号表的⼊⼝都定义为和某些ction相关;该成员保存了相关的ction
头索引。就象Figure1-8{*}和相关的⽂字所描述的那样,某些ction索引
指出了特殊的含义。
⼀个符号的属性决定了可链接性能和⾏为。
+Figure1-17:SymbolBinding,ELF32_ST_BIND
NameValue
=========
STB_LOCAL0
STB_GLOBAL1
STB_WEAK2
STB_LOPROC13
STB_HIPROC15
*STB_LOCAL
在包含了其定义的object⽂件之外的局部符号是不可见的。不同⽂件中的具有相同
名称的局部符号并不相互妨碍。
*STB_GLOBAL
全局符号是对所有的object⽬标⽂件可见的。⼀个⽂件中的全局符号的定义可以
满⾜另⼀个⽂件中对(该⽂件中)未定义的全局符号的引⽤。
*STB_WEAK
弱符号相似于全局符号,但是他们定义的优先级⽐较低⼀些。
*STB_LOPROCthroughSTB_HIPROC
其所包含范围中的值由相应的处理器语义所保留。
全局符号和弱符号的区别主要在两个⽅⾯。
*当链接器链接⼏个可重定位的⽬标⽂件时,它不允许STB_GLOBAL符号的同名
多重定义。另⼀⽅⾯,如果⼀个全局符号的定义存在则具有相同名称的弱符号名不会
引起错误。链接器将认可全局符号的定义⽽忽略弱符号的定义。与此相似,如果有⼀个
普通符号(⽐如,⼀个符号的st_shndx域包含SHN_COMMON),则⼀个同名的弱符号
不会引起错误。链接器同样认可普通符号的定义⽽忽略弱符号。
*当链接器搜索档案库的时候,它选出包含了未定义的全局符号的存档成员。该成员
的定义或者是全局的或者是⼀个弱符号。链接器不会为了解决⼀个未定义的弱符号
选出存档成员。未定义的弱符号具有0值。
在每⼀个符号表中,所有具有STB_LOCAL约束的符号优先于弱符号和全局符号。
就象上⾯"ctions"中描述的那样,⼀个符号表部分的sh_info头中的成员
保留了第⼀个⾮局部符号的符号表索引。
符号的类型提供了⼀个为相关⼊⼝的普遍分类。
+Figure1-18:SymbolTypes,ELF32_ST_TYPE
NameValue
=========
STT_NOTYPE0
STT_OBJECT1
STT_FUNC2
STT_SECTION3
STT_FILE4
STT_LOPROC13
STT_HIPROC15
*STT_NOTYPE
该符号的类型没有指定。
*STT_OBJECT
该符号和⼀个数据对象相关,⽐如⼀个变量、⼀个数组等。
*STT_FUNC
该符号和⼀个函数或其他可执⾏代码相关。
*STT_SECTION
该符号和⼀个ction相关。这种类型的符号表⼊⼝主要是为了重定位,⼀般的
具有STB_LOCAL约束。
*STT_FILE
按惯例⽽⾔,该符号给出了和⽬标⽂件相关的源⽂件名称。⼀个具有STB_LOCAL
约束的⽂件符号,其ction索引为SHN_ABS,并且它优先于当前对应该⽂件的
其他STB_LOCAL符号。
*STT_LOPROCthroughSTT_HIPROC
该范围中的值是为处理器语义保留的。
共享⽂件中的函数符号(具有STT_FUNC类型)有特殊的意义。当其他的⽬标⽂件
从⼀个共享⽂件中引⽤⼀个函数时,链接器⾃动的为引⽤符号创建⼀个链接表。除了
STT_FUNC之外,共享的⽬标符号将不会⾃动的通过链接表引⽤。
如果⼀个符号涉及到⼀个ction的特定定位,则其ction索引成员st_shndx
将保留⼀个到该ction头的索引。当该ction在重定位过程中不断
移动⼀样,符号的值也相应变化,⽽该符号的引⽤在程序中指向同样的定位。某些
特殊的ction索引有其他的语义。
*SHN_ABS
该符号有⼀个不会随重定位变化的绝对值。
*SHN_COMMON
该符号标识了⼀个没有被分配的普通块。该符号的值给出了相应的系统参数,就象
⼀个ction的sh_addralign成员。也就是说,链接器将分配⼀个地址给
该符号,地址的值是st_value的倍数。该符号的⼤⼩指出了需要的字节数。
*SHN_UNDEF
该ction表索引表明该符号是未定义的。当链接器将该⽬标⽂件和另⼀个定义
该符号的⽂件相装配的时候,该⽂件内对该符号的引⽤将链接到当前实际的定义。
如上所述,符号表的0索引(STN_UNDEF)是保留的,它包含了如下内容:
+Figure1-19:SymbolTableEntry:Index0
NameValueNote
=============
st_name0Noname
st_value0Zerovalue
st_size0Nosize
st_info0Notype,localbinding
st_other0
st_shndxSHN_UNDEFNoction
SymbolValues(符号值)
符号表⼊⼝对于不同的⽬标⽂件⽽⾔对st_value成员有⼀些不同的解释。
*在可重定位⽂件中,st_value保存了ction索引为SHN_COMMON符号
的强制对齐值。
*在可重定位⽂件中,st_value保存了⼀个符号的ction偏移。也就是说,
st_value是从st_shndx定义的ction开头的偏移量。
*在可执⾏的和可共享的⽬标⽂件中,st_value保存了⼀个虚拟地址。为了使
这些⽂件符号对于动态链接器更为有效,⽂件层⾯上的ction偏移让位于内存
层⾯上的虚拟地址(ction编号⽆关的)。
尽管符号表值对于不同的⽬标⽂件有相似的含义,相应的程序还是可以有效地访问数据。
======================Relocation(重定位)==========================
重定位是连接符号引⽤和符号定义的过程。⽐如,当⼀个程序调⽤⼀个函数的时候,
相关的调⽤必须在执⾏时把控制传送到正确的⽬标地址。换句话说,重定位⽂件应当
包含有如何修改他们的ction内容的信息,从⽽允许可执⾏⽂件或共享⽬标⽂件
为⼀个进程的程序映像保存正确的信息。重定位⼊⼝就是这样的数据。
+Figure1-20:RelocationEntries
typedefstruct{
Elf32_Addrr_offt;
Elf32_Wordr_info;
}Elf32_Rel;
typedefstruct{
Elf32_Addrr_offt;
Elf32_Wordr_info;
Elf32_Swordr_addend;
}Elf32_Rela;
*r_offt
该成员给出了应⽤重定位⾏为的地址。对于⼀个重定位⽂件⽽⾔,该值是从该
ction开始处到受到重定位影响的存储单位的字节偏移量。对⼀个可执⾏⽂件
或⼀个共享⽬标⽽⾔,该值是受到重定位影响的存储单位的虚拟地址。
*r_info
该成员给出了具有受重定位影响因素的符号表索引和重定位应⽤的类型。⽐如,
⼀个调⽤指令的重定位⼊⼝应当包含被调⽤函数的符号索引。如果该索引是
STN_UNDEF(未定义的符号索引),重定位将使⽤0作为该符号的值。重定位
类型是和处理器相关的。当正⽂(text)引⽤到⼀个重定位⼊⼝的重定位类型或符
号表索引,它表明相应的应⽤ELF32_R_TYPE或ELF32_R_SYM于⼊⼝的r_info
成员。
#defineELF32_R_SYM(i)((i)>>8)
#defineELF32_R_TYPE(i)((unsignedchar)(i))
#defineELF32_R_INFO(s,t)((s)<<8+(unsignedchar)(t))
*r_addend
该成员指定⼀个常量加数(⽤于计算将要存储于重定位域中的值)。
如上所述,只有Elf32_Rela⼊⼝包含⼀个明确的加数。Elf32_Rel类型
的⼊⼝在可以修改的地址中存储⼀个隐含的加数。依赖于处理器结构,⼀种形式
或其他形式也许是必须的或更为⽅便的。因此,特定机器的应⽤应当使⽤⼀种排他
性的形式或依赖于上下⽂的另⼀种形式。
⼀个重定位ction关联了两个其他的ction:⼀个符号表和⼀个可修改
的ction。该ction头的成员sh_info和sh_link(在上⽂中的
“ction”部分中有描述)指⽰了这种关系。重定位⼊⼝中的成员r_offt
对于不同的⽬标⽂件有少许差异。
*在可重定位⽂件中,r_offt表⽰了⼀个ction偏移。也就是说,重定位
ction⾃⼰描述了如何修改其他在⽂件中的其他ction;重定位偏移量指
明了⼀个在第⼆个ction中的存储器单元。
*在可执⾏和共享的⽬标⽂件中,r_offt表⽰⼀个虚拟地址。为了使得这些
⽂件的重定位⼊⼝更为有⽤(对于动态链接器⽽⾔),该ction偏移(⽂件
中)应当让位于⼀个虚拟地址(内存中的)。
尽管为了允许相关的程序更为有效的访问⽽让r_offt的解释对于不同的⽬标
⽂件有所不同,重定位类型的含义是相同的。
RelocationTypes(重定位类型)
重定位⼊⼝描述了怎样变更下⾯的指令和数据域(位数在表的两边⾓下)。
+Figure1-21:RelocatableFields
+---------------------------+
|word32|
31---------------------------0
*word32
指定⼀个以任意字节对齐⽅式占⽤4字节的32位域。这些值使⽤与32位Intel
体系相同的字节顺序。
3------2------1------0------+
0x01020304|01|02|03|04|
31------+------+------+------0
下⾯的计算假设正在将⼀个可重定位⽂件转换为⼀个可执⾏或共享的⽬标⽂件。
从概念上来说,链接器合并⼀个或多个可重定位⽂件来组成输出。它⾸先决定
怎样合并、定位输⼊⽂件,然后更新符号值,最后进⾏重定位。对于可执⾏⽂件
和共享的⽬标⽂件⽽⾔,重定位过程是相似的并有相同的结果。下⾯的描述使⽤
如下的约定符号。
*A
表⽰⽤于计算可重定位的域值的加数。
*B
表⽰了在执⾏过程中⼀个共享⽬标被加载到内存时的基地址。⼀般情况下,⼀个
共享object⽂件使⽤的基虚地址为0,但是⼀个可执⾏地址就跟共享object⽂件
不同了。
*G
表⽰了在执⾏过程中重定位⼊⼝符号驻留在全局偏移表中的偏移。请参阅
第⼆部分中的“GlobalOfftTable(全局偏移表)”获得更多
的信息。
*GOT
表⽰了全局偏移表的地址。请参阅第⼆部分中的“GlobalOfftTable
(全局偏移表)”获得更多的信息。
*L
表⽰⼀个符号的过程链接表⼊⼝的位置(ction偏移或地址)。⼀个过程
链接表⼊⼝重定位⼀个函数调⽤到正确的⽬的单元。链接器创建初始的链接表,
⽽动态链接器在执⾏中修改⼊⼝。
请参阅第⼆部分中的“ProcedureLinkageTable(过程链接表)”获得更多
的信息
*P
表⽰(ction偏移或地址)被重定位的存储单元位置(使⽤r_offt计算的)。
*S
表⽰索引驻留在重定位⼊⼝处的符号值。
⼀个重定位⼊⼝的r_offt值指定了受影响的存储单元的⾸字节的偏移
或虚拟地址。重定位类型指定了哪⼀位(bit)将要改变,以及怎样计算它们的值。
在SYSTEMV体系中仅仅使⽤Elf32_Rel重定位⼊⼝,将要被重定位的域中
保留了加数。在所有的情况下,加数和计算结果使⽤相同字节顺序。
+Figure1-22(表1-22):R水煮鳝鱼 elocationTypes(重定位类型)
NameValueFieldCalculation
=========================
R_386_NONE0nonenone
R_386_321word32S+A
R_386_PC322word32S+A-P
R_386_GOT323word32G+A-P
R_386_PLT324word32L+A-P
R_386_COPY5nonenone
R_386_GLOB_DAT6word32S
R_386_JMP_SLOT7word32S
R_386_RELATIVE8word32B+A
R_386_GOTOFF9word32S+A-GOT
R_386_GOTPC10word32GOT+A竹子的画 -P
有的重定位类型有不同于简单计算的语义。
*R_386_GOT32
这种重定位类型计算全局偏移表基地址到符号的全局偏移表
⼊⼝之间的间隔。这样另外通知了linkeditor建⽴⼀个全局偏移表。
*R_386_PLT32
这种重定位类型计算符号的过程链接表⼊⼝地址,并另外通知链接器建⽴⼀个
过程链接表。
*R_386_COPY
链接器创建该重定位类型⽤于动态链接。它的偏移成员涉及⼀个可写段中的⼀个
位置。符号表索引指定⼀个可能存在于当前objectfile或在⼀个sharedobject
中的符号。在执⾏过程中,动态链接器把和sharedobject符号相关的数据
拷贝到该偏移所指定的位置。
*R_386_GLOB_DAT
这种重定位类型⽤于设置⼀个全局偏移表⼊⼝为指定符号的地址。该特定的重定位
类型允许你决定符号和全局偏移表⼊⼝之间的⼀致性。
*R_386_JMP_SLOT{*}
链接器创建该重定位类型⽤于动态链接。其偏移成员给出了⼀个过程链接表⼊⼝的
位置。动态链接器修改该过程链接表⼊⼝以便向特定的符号地址传递控制。
[参阅第⼆部分中的"ProcedureLinkageTable(过程链接表)"]
*R_386_RELATIVE
链接器创建该重定位类型⽤于动态链接。其偏移成员给出了包含表达相关地址值
的⼀个sharedobject中的位置。动态链接器计算相应的虚拟地址(把该
sharedobject装载地址和相对地址相加)。该类型的重定位⼊⼝必须为
符号表索引指定为0。
*R_386_GOTOFF
这种重定位类型计算符号值和全局偏移表地址之间的不同。另外还通知链接器
建⽴全局偏移表(GOT)。
*R_386_GOTPC
这种重定位类型类似于R_386_PC32,不同的是它在计算中使⽤全局偏移表。
这种重定位中引⽤的符号通常是_GLOBAL_OFFSET_TABLE_,该符号通知了
链接器建⽴全局偏移表(GOT)。
________________________________________________________________
MLOADINGANDDYNAMICLINKING
程序装⼊和动态链接
________________________________________________________________
========================Introduction(介绍)=========================
第⼆部分描述了objectfile信息和创建运⾏程序的系统⾏为。其中部分信息
适合所有的系统,其他信息是和特定处理器相关的。
可执⾏和共享的objectfile静态的描绘了程序。为了执⾏这样的程序,系统
⽤这些⽂件创建动态的程序表现,或进程映像。⼀个进程映像有⽤于保存其代码、
数据、堆栈等等的段。这个部分的主要章节讨论如下的内容。
*程序头(Programheader)。该章节补充第⼀部分,描述和程序运⾏相关的
objectfile结构。即⽂件中主要的数据结构、程序头表、定位段映像,也
包含了为该程序创建内存映像所需要的信息。
*载⼊程序(Programloading)。在给定⼀个objectfile时,系统为了
让它运⾏必须将它载⼊内存。
*动态链接(Dynamiclinking)。在载⼊了程序之后,系统必须通过解决组
成该进程的objectfile之间的符号引⽤问题来完成进程映像的过程。
注意:指定了处理器范围的ELF常量是有命名约定的。⽐如,DT_,PT_,
⽤于特定处理器扩展名,组合了处理器的名称(如DT_M32_SPECIAL)。
没有使⽤这种约定但是预先存在的处理器扩展名是允许的。
Pre-existingExtensions
(预先存在的扩展名)
=======================
DT_JMP_REL
======================ProgramHeader(程序头)======================
⼀个可执⾏的或共享的objectfile的程序头表是⼀个结构数组,每⼀个
结构描述⼀个段或其他系统准备执⾏该程序所需要的信息。⼀个objectfile
段包含⼀个或多个部分(就象下⾯的“段⽬录”所描述的那样)。程序头仅仅对于
可执⾏或共享的objectfile有意义。⼀个⽂件使⽤ELF头的e_phentsize
和e_phnum成员来指定其拥有的程序头⼤⼩。[参阅第⼀部分中的"ELF头"]
+Figure2-1:ProgramHeader
typedefstruct{
Elf32_Wordp_type;
Elf32_Offp_offt;
Elf32_Addrp_vaddr;
Elf32_Addrp_paddr;
Elf32_Wordp_filesz;
Elf32_Wordp_memsz;
Elf32_Wordp_flags;
Elf32_Wordp_align;
}Elf32_Phdr;
*p_type
该成员指出了这个数组的元素描述了什么类型的段,或怎样解释该数组元素的信息。
类型值和含义如下所述。
*p_offt
该成员给出了该段的驻留位置相对于⽂件开始处的偏移。
*p_vaddr
该成员给出了该段在内存中的⾸字节地址。
*p_paddr
在物理地址定位有关联的系统中,该成员是为该段的物理地址⽽保留的。由于
SystemV忽略了应⽤程序的物理地址定位,该成员对于可执⾏⽂件和共享的
object⽽⾔是未指定内容的。
*p_filesz
该成员给出了⽂件映像中该段的字节数;它可能是0。
*p_memsz
该成员给出了内存映像中该段的字节数;它可能是0。
*p_flags
该成员给出了和该段相关的标志。定义的标志值如下所述。
*p_align
就象在后⾯“载⼊程序”部分中所说的那样,可载⼊的进程段必须有合适的
p_vaddr、p_offt值,取页⾯⼤⼩的模。该成员给出了该段在内存和
⽂件中排列值。0和1表⽰不需要排列。否则,p_align必须为正的2的幂,
并且p_vaddr应当等于p_offt模p_align。
某些⼊⼝描述了进程段;其他的则提供补充信息并且⽆益于进程映像。已经
定义的⼊⼝可以以任何顺序出现,除⾮是下⾯明确声明的。后⾯是段类型值;
其他的值保留以便将来⽤于其他⽤途。
+Figure2-2:SegmentTypes,p_type
NameValue
=========
PT_NULL0
PT_LOAD1
PT_DYNAMIC2
PT_INTERP3
PT_NOTE4
PT_SHLIB5
PT_PHDR6
PT_LOPROC0x70000000
PT_HIPROC0x7fffffff
*PT_NULL
该数组元素未使⽤;其他的成员值是未定义的。这种类型让程序头表忽略⼊⼝。
*PT_LOAD
该数组元素指定⼀个可载⼊的段,由p_filesz和p_memsz描述。⽂件中
字节被映射到内存段中。如果该段的内存⼤⼩(p_memsz)⽐⽂件⼤⼩(p_filesz)
要⼤,则多出的字节将象段初始化区域那样保持为0。⽂件的⼤⼩不会⽐内存⼤⼩值⼤。
在程序头表中,可载⼊段⼊⼝是以p_vaddr的升序排列的。
*PT_DYNAMIC
该数组元素指定动态链接信息。参阅后⾯的“动态部分”以获得更多信息。
*PT_INTERP
该数组元素指定⼀个null-terminated路径名的位置和⼤⼩(作为解释程序)。
这种段类型仅仅对可执⾏⽂件有意义(尽管它可能发⽣在⼀个共享object上);
它在⼀个⽂件中只能出现⼀次。如果它出现,它必须先于任何⼀个可载⼊段⼊⼝。
参阅后⾯的“程序解释器”(ProgramInterpreter)以获得更多的信息。
*PT_NOTE
该数组元素指定辅助信息的位置和⼤⼩。参阅后⾯的“注意部分”以获得细节。
*PT_SHLIB
该段类型保留且具有未指定的语义。具有⼀个这种类型数组元素的程序并不
遵守ABI。
*PT_PHDR
该数组元素(如果出现),指定了程序头表本⾝的位置和⼤⼩(包括在⽂件中
和在该程序的内存映像中)。更进⼀步来说,它仅仅在该程序头表是程序内存映像
的⼀部分时才有效。如果它出现,它必须先于任何可载⼊段⼊⼝。参阅后⾯的
“程序解释器”(ProgramInterpreter)以获得更多的信息。
*PT_LOPROCthroughPT_HIPROC
该范围中的值保留⽤于特定处理器的语义。
注意:除⾮在别处的特殊要求,所有的程序头的段类型是可选的。也就是说,
⼀个⽂件的程序头表也许仅仅包含和其内容相关的元素。
BaAddress(基地址)
可执⾏和共享的objectfile有⼀个基地址,该基地址是与程序的objectfile
在内存中映像相关的最低虚拟地址。基地址的⽤途之⼀是在动态链接过程中重定位
该程序的内存映像。
⼀个可执⾏的objectfile或⼀个共享的objectfile的基地址是在
执⾏的时候从三个值计算⽽来的:内存载⼊地址、页⾯⼤⼩的最⼤值和程序可
载⼊段的最低虚拟地址。就象在“程序载⼊”中所描述的那样,程序头中的虚拟地址
也许和程序的内存映像中实际的虚拟地址并不相同。为了计算基地址,必须确定与
PT_LOAD段p_vaddr的最⼩值相关的内存地址。获得基地址的⽅法是将内存
地址截去最⼤页⾯⼤⼩的最接近的整数倍。由于依赖载⼊内存中的⽂件类型,
该内存地址和p_vaddr值可能匹配也可能不匹配。
就象在第⼀部分中"Section"中描述的那样,.bssction具有SHT_NOBITS
的类型。尽管在⽂件中不占⽤空间,它在段的内存映像中起作⽤。通常,没有初始化
的数据驻留在段尾,因此使得在相关的程序头元素中的p_memsz⽐p_filesz⼤。
NoteSection(注解部分)
有的时候供应商或系统设计者需要⽤特定的信息标记⼀个
objectfile以便其他程序检查其兼容的⼀致性,等等此类。SHT_NOTE
类型的ction和PT_NOTE类型的程序头元素能够被⽤于此⽬的。ction
和程序头中的注解信息包含了任意数⽬的⼊⼝,每⼀个⼊⼝的格式都是对应于特定
处理器格式的4-字节数组。下⾯的标签有助于解释注释信息的组织形式,但是这些
标签不是规格说明的⼀部分。
+Figure2-3:NoteInformation
namesz
descsz
type
*nameszandname
名字中namesz的第⼀个字节包含了⼀个null-terminated字符
表达了该⼊⼝的拥有者或始发者。没有正式的机制来避免名字冲突。从
惯例来说,供应商使⽤他们⾃⼰的名称,⽐如"XYZComputerCompany",
作为标志。如果没有提供名字,namesz值为0。如果有必要,确定
描述信息4-字节对齐。这样的填充信息并不包含在namesz中。
*descszanddesc
desc中descsz的⾸字节包含了注解描述符。ABI不会在⼀个描述符内容中
放⼊任何系统参数。如果没有描述符,descsz将为0。如果有必要,确定
描述信息4-字节对齐。这样的填充信息并不包含在descsz中。
*type
该word给出了描述符的解释。每⼀个创造着(originator)控制着⾃⼰的类型;
对于单单⼀个类型值的多种解释是可能存在的。因此,⼀个程序必须辨认出该名字
和其类型以便理解⼀个描述符。这个时候的类型必须是⾮负的。ABI没有定义
描述符的含义。
为了举例说明,下⾯的解释段包含两个⼊⼝。
+Figure2-4:ExampleNoteSegment
+0+1+2+3
-------------------
namesz7
descsz0Nodescriptor
type1
nameXYZspc
Co/0pad
namesz7
descsz8
type3
nameXYZspc
Co/0pad
descword0
word1
注意:系统保留的注解信息没有名字(namesz==0),有⼀个零长度的名字
(name[0]=='/0')现在还没有类型为其定义。所有其他的名字必须⾄少有
⼀个⾮空的字符。
注意:注解信息是可选的。注解信息的出现并不影响⼀个程序的ABI⼀致性,
前提是该信息不影响程序的执⾏⾏为。否则,该程序将不遵循ABI并将出现
未定义的⾏为。
=====================ProgramLoading(程序载⼊)=====================
当创建或增加⼀个进程映像的时候,系统在理论上将拷贝⼀个⽂件的段到⼀个虚拟
的内存段。系统什么时候实际地读⽂件依赖于程序的执⾏⾏为,系统载⼊等等。⼀个
进程仅仅在执⾏时需要引⽤逻辑页⾯的时候才需要⼀个物理页⾯,实际上进程通常会
留下许多未引⽤的页⾯。因此推迟物理上的读取常常可以避免这些情况,改良系统的
特性。为了在实践中达到这种效果,可执⾏的和共享的objectfile必须具有
合适于页⾯⼤⼩取模值的⽂件偏移和虚拟地址这样条件的段映像。
虚拟地址和⽂件偏移在SYSTEMV结构的段中是模4KB(0x1000)或⼤的2的幂。
由于4KB是最⼤的页⾯⼤⼩,因此⽆论物理页⾯⼤⼩是多少,⽂件必须去适合页⾯。
+Figure2-5:ExecutableFile
FileOfftFileVirtualAddress
==============================
0ELFheader
Programheadertable
Otherinformation
0x100Textgment0x8048100
...
0x2be00bytes0x8073eff
0x2bf00Datagment0x8074f00
...
0x4e00bytes0x8079cff
0x30d00Otherinformation
...
+Figure2-6:ProgramHeaderSegments(程序头段)
MemberTextData
==============
p_typePT_LOADPT_LOAD
p_offt0x1000x2bf00
p_vaddr0x80481000x8074f00
p_paddrunspecifiedunspecified
p_filesz0x2be000x4e00
p_memsz0x2be000x5e24
p_flagsPF_R+PF_XPF_R+PF_W+PF_X
p_align0x10000x1000
尽管⽰例中的⽂件偏移和虚拟地址在⽂本和数据两⽅⾯都适合模4KB,但是还有
4个⽂件页⾯混合了代码和数据(依赖于页⾯⼤⼩和⽂件系统块的⼤⼩)。
*第⼀个⽂本页⾯包含了ELF头、程序头以及其他信息。
*最后的⽂本页包含了⼀个数据开始的拷贝。
*第⼀个数据页⾯有⼀个⽂本结束的拷贝。
*最后的数据页⾯也许会包含与正在运⾏的进程⽆关的⽂件信息。
理论上,系统强制内存中段的区别;段地址被调整为适应每⼀个逻辑页⾯在地址空间
中有⼀个简单的准许集合。在上⾯的⽰例中,包含⽂本结束和数据开始的⽂件区域将
被映射两次:在⼀个虚拟地址上为⽂本⽽另⼀个虚拟地址上为数据。
数据段的结束处需要对未初始化的数据进⾏特殊处理(系统定义的以0值开始)。
因此如果⼀个⽂件包含信息的最后⼀个数据页⾯不在逻辑内存页⾯中,则⽆关的
数据应当被置为0(这⾥不是指未知的可执⾏⽂件的内容)。在其他三个页⾯中
"Impurities"理论上并不是进程映像的⼀部分;系统是否擦掉它们是未指定的。
下⾯程序的内存映像假设了4KB的页⾯。
+Figure2-7:ProcessImageSegments(进程映像段)
VirtualAddressContentsSegment
==============================
0x8048000HeaderpaddingText
0x100bytes
0x8048100Textgment
...
0x2be00bytes
0x8073f00Datapadding
0x100bytes
0x8074000TextpaddingD宝宝发烧了怎样退烧快 ata
0xf00bytes
0x8074f00Datagment
...
0x4e00bytes
0x8079d00Uninitializeddata
0x1024zerobytes
0x807ad24Pagepadding
0x2dczerobytes
可执⾏⽂件和共享⽂件在段载⼊⽅⾯有所不同。典型地,可执⾏⽂件段包含了
绝对代码。为了让进程正确执⾏,这些段必须驻留在建⽴可执⾏⽂件的虚拟地址
处。因此系统使⽤不变的p_vaddr作为虚拟地址。
另⼀⽅⾯,共享⽂件段包含与位置⽆关的代码。这让不同进程的相应段虚拟地址
各不相同,且不影响执⾏。虽然系统为各个进程选择虚拟地址,它还要维护各个
段的相对位置。因为位置⽆关的代码在段间使⽤相对定址,故⽽内存中的虚拟地址
的不同必须符合⽂件中虚拟地址的不同。下表给出了⼏个进程可能的共享对象虚拟
地址的分配,演⽰了不变的相对定位。该表同时演⽰了基地址的计算。
+Figure2-8:ExampleSharedObjectSegmentAddress
SourcTextDataBaAddress
=========================
File0x2000x2a4000x0
Process10x800002000x8002a4000x80000000
Process20x800812000x800ab4000x80081000
Process30x900c02000x900ea4000x900c0000
Process40x900c62000x900f04000x900c6000
====================DynamicLinking(动态链接)=====================
⼀个可执⾏⽂件可能有⼀个PT_INTERP程序头元素。在exec(BA_OS)的
过程中,系统从PT_INTERP段中取回⼀个路径名并由解释器⽂件的段创建初始的
进程映像。也就是说,系统为解释器“编写”了⼀个内存映像,⽽不是使⽤原始
的可执⾏⽂件的段映像。此时该解释器就负责接收系统来的控制并且为应⽤程序
提供⼀个环境变量。
解释器使⽤两种⽅法中的⼀种来接收系统来的控制。⾸先,它会接收⼀个⽂件描述符
来读取该可执⾏⽂件,定位于开头。它可以使⽤这个⽂件描述符来读取并且(或者)
映射该可执⾏⽂件的段到内存中。其次,依赖于该可执⾏⽂件的格式,系统会载⼊
这个可执⾏⽂件到内存中⽽不是给该解释器⼀个⽂件描述符。伴随着可能的⽂件描述符
异常的情况,解释器的初始进程声明应匹配该可执⾏⽂件应当收到的内容。解释器本⾝
并不需要第⼆个解释器。⼀个解释器可能是⼀个共享对象也可能是⼀个可执⾏⽂件。
*⼀个共享对象(通常的情况)在被载⼊的时候是位置⽆关的,各个进程可能不同;
系统在mmap(KE_OS)使⽤的动态段域为它创建段和相关的服务。因⽽,⼀个
共享对象的解释器将不会和原始的可执⾏⽂件的原始段地址相冲突。
*⼀个可执⾏⽂件被载⼊到固定地址;系统使⽤程序头表中的虚拟地址为其创建段。
因⽽,⼀个可执⾏⽂件解释器的虚拟地址可能和第⼀个可执⾏⽂件相冲突;这种
冲突由解释器来解决。
DynamicLinker(动态链接器)
当使⽤动态链接⽅式建⽴⼀个可执⾏⽂件时,链接器把⼀个PT_INTERP类型
的元素加到可执⾏⽂件中,告诉系统把动态链接器做为该程序的解释器。
注意:由系统提供的动态链接器是和特定处理器相关的。
Exec(BA_OS)和动态链接器合作为程序创建进程,必须有如下的动作:
*将可执⾏⽂件的内存段加⼊进程映像中;
*将共享对象的内存段加⼊进程映像中;
*为可执⾏⽂件和它的共享对象进⾏重定位;
*如果有⼀个⽤于读取可执⾏⽂件的⽂件描述符传递给了动态链接器,那么关闭它。
*向程序传递控制,就象该程序已经直接从exec(BA_OS)接收控制⼀样。
链接器同时也为动态链接器构建各种可执⾏⽂件和共享对象⽂件的相关数据。就象
在上⾯“程序头”中说的那样,这些数据驻留在可载⼊段中,使得它们在执⾏过程
中有效。(再⼀次的,要记住精确的段内容是处理器相关的。可以参阅相应处理器
的补充说明来获得详尽的信息。)
*⼀个具有SHT_DYNAMIC类型的.dynamicction包含各种数据。驻留在
ction开头的结构包含了其他动态链接信息的地址。
*SHT_HASH类型的.hashction包含了⼀个symbolhashtable.
*SHT_PROGBITS类型的.got和.pltction包含了两个分离的table:
全局偏移表和过程链接表。下⾯的ction演⽰了动态链接器使⽤和改变
这些表来为objectfile创建内存映像。
由于每⼀个遵循ABI的程序从⼀个共享对象库中输⼊基本的系统服务,因此动态
链接器分享于每⼀个遵循ABI的程序的执⾏过程中。
就象在处理器补充说明的“程序载⼊”所解释的那样,共享对象也许会占⽤与记录在
⽂件的程序头表中的地址不同的虚拟内存地址。动态链接器重定位内存映像,在应⽤程序
获得控制之前更新绝对地址。尽管在库被载⼊到由程序头表指定的地址的情况下绝对地址
应当是正确的,通常的情况却不是这样。
如果进程环境[eexec(BA_OS)]包含了⼀个⾮零的LD_BIND_NOW变量,
动态链接器将在控制传递到程序之前进⾏所有的重定位。举例⽽⾔,所有下⾯的
环境⼊⼝将指定这种⾏为。
*LD_BIND_NOW=1
*LD_BIND_NOW=on
*LD_BIND_NOW=off
其他情况下,LD_BIND_NOW或者不在环境中或者为空值。动态链接器可以不急于
处理过程链接表⼊⼝,因⽽避免了对没有调⽤的函数的符号解析和重定位。参阅
"ProcedureLinkageTable"获取更多的信息。
DynamicSection(动态ction)
假如⼀个object⽂件参与动态的连接,它的程序头表将有⼀个类型为PT_DYNAMIC
的元素。该“段”包含了.dynamicction。⼀个_DYNAMIC特别的符号,表明了
该ction包含了以下结构的⼀个数组。
+Figure2-9:DynamicStructure
typedefstruct{
Elf32_Swordd_tag;
union格式合同 {
Elf32_Swordd_val;
Elf32_Addrd_ptr;
}d_un;
}Elf32_Dyn;
externElf32_Dyn_DYNAMIC[];
对每⼀个有该类型的object,d_tag控制着d_un的解释。
*d_val
那些Elf32_Wordobject描绘了具有不同解释的整形变量。
*d_ptr
那些Elf32_Wordobject描绘了程序的虚拟地址。就象以前提到的,在执⾏时,
⽂件的虚拟地址可能和内存虚拟地址不匹配。当解释包含在动态结构中的地址
时是基于原始⽂件的值和内存的基地址。为了⼀致性,⽂件不包含在
重定位⼊⼝来纠正在动态结构中的地址。
以下的表格总结了对可执⾏和共享object⽂件需要的tag。假如tag被标为
mandatory,ABI-conforming⽂件的动态连接数组必须有⼀个那样的⼊⼝。
同样的,“optional”意味着⼀个可能出现tag的⼊⼝,但是不是必须的。
+Figure2-10:DynamicArrayTags,d_tag
NameValued_unExecutableSharedObject
====================================
DT_NULL0ignoredmandatorymandatory
DT_NEEDED1d_valoptionalop乡村田园风景画 tional
DT_PLTRELSZ2d_valoptionaloptional
DT_PLTGOT3d_ptroptionaloptional
DT_HASH4d_ptrmandatorymandatory
DT_STRTAB5d_ptrmandatorymandatory
DT_SYMTAB6d_ptrmandatorymandatory
DT_RELA7d_ptrmandatoryoptional
DT_RELASZ8d_valmandatoryoptional
DT_RELAENT9d_valmandatoryoptional
DT_STRSZ10d_valmandatorymandatory
DT_SYMENT11d_valmandatorymandatory
DT_INIT12d_ptroptionaloptional
DT_FINI13d_ptroptionaloptional
DT_SONAME14d_valignoredoptional
DT_RPATH15d_valoptionalignored
DT_SYMBOLIC16ignoredignoredoptional
DT_REL17d_ptrmandatoryoptional
DT_RELSZ18d_valmandatoryoptional
DT_RELENT19d_valmandatoryoptional
DT_PLTREL20d_valoptionaloptional
DT_DEBUG21d_ptroptionalignored
DT_TEXTREL22ignoredoptionaloptional
DT_JMPREL23d_ptroptionaloptional
DT_LOPROC0x70000000unspecifiedunspecifiedunspecified
DT_HIPROC0x7fffffffunspecifiedunspecifiedunspecified
*DT_NULL
⼀个DT_NULL标记的⼊⼝表⽰了_DYNAMIC数组的结束。
*DT_NEEDED
这个元素保存着以NULL结尾的字符串表的偏移量,那些字符串是所需库的名字。
该偏移量是以DT_STRTAB为⼊⼝的表的索引。看“SharedObjectDependen夜晚的萤火虫 cies”
关于那些名字的更多信息。动态数组可能包含了多个这个类型的⼊⼝。那些
⼊⼝的相关顺序是重要的,虽然它们跟其他⼊⼝的关系是不重要的。
*DT_PLTRELSZ
该元素保存着跟PLT关联的重定位⼊⼝的总共字节⼤⼩。假如⼀个⼊⼝类型
DT_JMPREL存在,那么DT_PLTRELSZ也必须存在。
*DT_PLTGOT
该元素保存着跟PLT关联的地址和(或者)是GOT。具体细节看处理器补充
(processorsupplement)部分。
*DT_HASH
该元素保存着符号哈希表的地址,在“哈希表”有描述。该哈希表指向
被DT_SYMTAB元素引⽤的符号表。
*DT_STRTAB
该元素保存着字符串表地址,在第⼀部分有描述,包括了符号名,库名,
和⼀些其他的在该表中的字符串。
*DT_SYMTAB
该元素保存着符号表的地址,在第⼀部分有描述,对32-bit类型的⽂件来
说,关联着⼀个Elf32_Sym⼊⼝。
*DT_RELA
该元素保存着重定位表的地址,在第⼀部分有描述。在表中的⼊⼝有明确的
加数,就象32-bit类型⽂件的Elf32_Rela。⼀个object⽂件可能好多个重定位
ction。当为⼀个可执⾏和共享⽂件建⽴重定位表的时候,连接编辑器连接
那些ction到⼀个单⼀的表。尽管在object⽂件中那些ction是保持独⽴的。
动态连接器只看成是⼀个简单的表。当动态连接器为⼀个可执⾏⽂件创建⼀个
进程映象或者是加⼀个共享object到进程映象中,它读重定位表和执⾏相关的
动作。假如该元素存在,动态结构必须也要有DT_RELASZ和DT_RELAENT元素。
当⽂件的重定位是mandatory,DT_RELA或者DT_REL可能出现(同时出现是
允许的,但是不必要的)。
*DT_RELASZ
该元素保存着DT_RELA重定位表总的字节⼤⼩。
*DT_RELAENT
该元素保存着DT_RELA重定位⼊⼝的字节⼤⼩。
*DT_STRSZ
该元素保存着字符串表的字节⼤⼩。
*DT_SYMENT
该元素保存着符号表⼊⼝的字节⼤⼩。
*DT_INIT
该元素保存着初始化函数的地址,在下⾯“初始化和终⽌函数”中讨论。
*DT_FINI
该元素保存着终⽌函数的地址,在下⾯“初始化和终⽌函数”中讨论。
*DT_SONAME
该元素保存着以NULL结尾的字符串的字符串表偏移量,那些名字是共享
object的名字。偏移量是在DT_STRTAB⼊⼝记录的表的索引。关于那些名字看
SharedObjectDependencies部分获得更多的信息。
*DT_RPATH
该元素保存着以NULL结尾的搜索库的搜索⽬录字符串的字符串表偏移量。
在共享object依赖关系(SharedObjectDependencies)中有讨论
*DT_SYMBOLIC
在共享object库中出现的该元素为在库中的引⽤改变动态连接器符号解析的算法。
替代在可执⾏⽂件中的符号搜索,动态连接器从它⾃⼰的共享object开始。假如
⼀个共享的object提供引⽤参考失败,那么动态连接器再照常的搜索可执⾏⽂件
和其他的共享object。
*DT_REL
该元素相似于DT_RELA,除了它的表有潜在的加数,正如32-bit⽂件类型的
Elf32_Rel⼀样。假如这个元素存在,它的动态结构必须也同时要有DT_RELSZ
和DT_RELENT的元素。
*DT_RELSZ
该元素保存着DT_REL重定位表的总字节⼤⼩。
*DT_RELENT
该元素保存着DT_RELENT重定为⼊⼝的字节⼤⼩。
*DT_PLTREL
该成员指明了PLT指向的重定位⼊⼝的类型。适当地,d_val成员保存着
DT_REL或DT_RELA。在⼀个PLT中的所有重定位必须使⽤相同的转换。
*DT_DEBUG
该成员被调试使⽤。它的内容没有被ABI指定;访问该⼊⼝的程序不是
ABI-conforming的。
*DT_TEXTREL
如在程序头表中段许可所指出的那样,这个成员的缺乏代表没有重置⼊
⼝会引起⾮写段的修改。假如该成员存在,⼀个或多个重定位⼊⼝可能
请求修改⼀个⾮写段,并且动态连接器能因此有准备。
*DT_JMPREL
假如存在,它的⼊⼝d_ptr成员保存着重定位⼊⼝(该⼊⼝单独关联着
PLT)的地址。假如lazy⽅式打开,那么分离它们的重定位⼊⼝让动态连接
器在进程初始化时忽略它们。假如该⼊⼝存在,相关联的类型⼊⼝DT_PLTRELSZ
和DT_PLTREL⼀定要存在。
*DT_LOPROCthroughDT_HIPROC
在该范围内的变量为特殊的处理器语义保留。除了在数组末尾的DT_NULL元素,
和DT_NEEDED元素相关的次序,⼊⼝可能出现在任何次序中。在表中不出
现的Tag值是保留的。
SharedObjectDependencies(共享Object的依赖关系)
当连接器处理⼀个⽂档库时,它取出库中成员并且把它们拷贝到⼀个输出的
object⽂件中。当运⾏时没有包括⼀个动态连接器的时候,那些静态的连接服
务是可⽤的。共享object也提供服务,动态连接器必须把正确的共享object
⽂件连接到要实⾏的进程映象中。因此,可执⾏⽂件和共享的object⽂件之间
存在着明确的依赖性。
当动态连接器为⼀个object⽂件创建内存段时,依赖关系(在动态结构的
DT_NEEDED⼊⼝中记录)表明需要哪些object来为程序提供服务。通过
重复的连接参考的共享object和他们的依赖关系,动态连接器可以建造⼀个
完全的进程映象。当解决⼀个符号引⽤的时候,动态连接器以宽度优先搜索
(breadth-first)来检查符号表,换句话说,它先查看⾃⼰的可实⾏程序
中的符号表,然后是顶端DT_NEEDED⼊⼝(按顺序)的符号表,再接下来是
第⼆级的DT_NEEDED⼊⼝,依次类推。共享object⽂件必须对进程是可读的;
其他权限是不需要的。
注意:即使当⼀个共享object被引⽤多次(在依赖列关系表中),动态连接器
只把它连接到进程中⼀次。
在依赖关系列表中的名字既被DT_SONAME字符串拷贝,⼜被建⽴object⽂件
时的路径名拷贝。例如,动态连接器建⽴⼀个可执⾏⽂件(使⽤带DT_SONAME
⼊⼝的lib1共享⽂件)和⼀个路径名为/usr/lib/lib2的共享object库,
那么可执⾏⽂件将在它⾃⼰的依赖关系列表中包含lib1和/usr/bin/lib2。
假如⼀个共享object名字有⼀个或更多的反斜杠字符(/)在这名字的如何地⽅,
例如上⾯的/usr/lib/lib2⽂件或⽬录,动态连接器把那个字符串⾃⼰做为路径名。
假如名字没有反斜杠字符(/),例如上⾯的lib1,三种⽅法指定共享⽂件的
搜索路径,如下:
*第⼀,动态数组标记DT_RPATH保存着⽬录列表的字符串(⽤冒号(:)分隔)。
例如,字符串/home/dir/lib:/home/dir2/lib:告诉动态连接器先搜索
/home/dir/lib,再搜索/home/dir2/lib,再是当前⽬录。
*第⼆,在进程环境中(eexec(BA_OS)),有⼀个变量称为LD_LIBRARY_PATH
可以保存象上⾯⼀样的⽬录列表(随意跟⼀个分号(;)和其他⽬录列表)。
以下变量等于前⾯的例⼦:
LD_LIBRARY_PATH=/home/dir/lib:/home/dir2/lib:
LD_LIBRARY_PATH=/home/dir/lib;/home/dir2/lib:
LD_LIBRARY_PATH=/home/dir/lib:/home/dir2/lib:;
所以的LD_LIBRARY_PATH⽬录在DT_RPATH指向的⽬录之后被搜索。尽管⼀些
程序(例如连接编辑器)不同的处理分号前和分号后的⽬录,但是动态连接
不会。不过,动态连接器接受分号符号,具体语意在如上⾯描述。
*最后,如果上⾯的两个⽬录查找想要得到的库失败,那么动态连接器搜索
/usr/lib.
注意:出于安全考虑,动态连接器忽略t-ur和t-group的程序的
LD_LIBRARY_PATH所指定的搜索⽬录。但它会搜索DT_RPATH指明的⽬录和
/usr/lib。
GlobalOfftTable(GOT全局偏移量表)
⼀般情况下,位置⽆关的代码不包含绝对的虚拟地址。全局偏移量表在私有数据
中保存着绝对地址,所以应该使地址可⽤的,⽽不是和位置⽆关性和程序代码段
共享能⼒妥协。⼀个程序引⽤它的GOT(全局偏移量表)来使⽤位置⽆关的地址并且
提取绝对的变量,所以重定位位置⽆关的参考到绝对的位置。
初始时,GOT(全局偏移量表)保存着它重定位⼊⼝所需要的信息[看第⼀部分的
“Relocation”]。红头文件怎么制作 在系统为⼀个可装载的object⽂件创建内存段以后,动态
连接器处理重定位⼊⼝,那些类型为R_386_GLOB_DAT的指明了GOT(全局偏移量表)。
动态连接器决定了相关的标号变量,计算他们的绝对地址,并且设置适当的内存
表⼊⼝到正确的变量。虽然当连接编辑器建造object⽂件的时候,绝对地址
是不知道,连接器知道所以内存段的地址并且能够因此计算出包含在那⾥的
标号地址。
假如程序需要直接访问符号的绝对地址,那么这个符号将有⼀个GOT(全局偏移量表)
⼊⼝。因为可执⾏⽂件和共享⽂件有独⽴的GOT(全局偏移量表),⼀个符号地址
可能出现在不同的⼏个表中。在交给进程映象的代码控制权以前,动态连接器处
理所有的重定位的GOT(全局偏移量表),所以在执⾏时,确认绝对地址是可⽤的。
该表的⼊⼝0是为保存动态结构地址保留的(参考_DYNAMIC标号)。这允许
象动态连接程序那样来找出他们⾃⼰的动态结构(还没有处理他们的重
定向⼊⼝)。这些对于动态连接器是重要的,因为它必要初始化⾃⼰⽽不
能依赖于其他程序来重定位他们的内存映象。在32位Interl系统结构中,在
GOT中的⼈⼝1和2也是保留的,具体看以下的过程连接表(ProcedureLinkage
Table)。
系统可以为在不同的程序中相同的共享object选择不同的内存段;它甚⾄可以
为相同的程序不同的进程选择不同的库地址。虽然如此,⼀旦进程映象被建⽴
以后,内存段不改变地址。只要⼀个进程存在,它的内存段驻留在固定的虚拟
地址。
GOT表的格式和解释是处理器相关的。在32位Intel体系结构下,标号
_GLOBAL_OFFSET_TABLE_可能被⽤来访问该表。
+Figure2-11:GlobalOfftTable
externElf32_Addr_GLOBAL_OFFSET_TABLE_[];
标号_GLOBAL_OFFSET_TABL酸奶品牌 E_可能驻留在.gotction的中间,允许负的和⾮负
的下标索引这个数组。
ProcedureLinkageTable(PLT过程连接表)
就象GOT重定位把位置⽆关的地址计算成绝对地址⼀样,PLT过程连接表重定位
位置⽆关的函数调⽤到绝对的地址。从⼀个可执⾏或者共享的object⽂件到另外的,
连接编辑器不解析执⾏的传输(例如函数的调⽤)。因此,连接编辑器安排程序
的传递控制到PLT中的⼊⼝。在SYSTEMV体系下,PLT存在共享⽂本中,但是它们
使⽤的地址是在私有的GOT中。符号连接器决定了⽬标的绝对地址并且修改GOT的
内存映象。因此,在没有危及到位置⽆关、程序⽂本的共享能⼒的情况下。动态
连接器能重定位⼈⼝。
+Figure2-12:AbsoluteProcedureLinkageTable{*}
绝对的过程连接表
.PLT0:pushlgot_plus_4
jmp*got_plus_8
nop;nop
nop;nop
.PLT1:jmp*name1_in_GOT
pushl$offt
jmp
.PLT2:jmp*name2_in_GOT
pushl$offt
jmp
...
+Figure2-13:Position-IndependentProcedureLinkageTable
位置⽆关(或者说位置独⽴)的过程连接表
.PLT0:pushl4(%ebx)
jmp*8(%ebx)
nop;nop
nop;nop
.PLT1:jmp)
pushl$offt
jmp
.PLT2:jmp)
pushl$offt
jmp
...
注意:如图所⽰,PLT的指令使⽤了不同的操作数地址⽅式,对绝对代码和
对位置⽆关的代码。但是,他们的界⾯对于动态连接器是相同的。
以下的步骤,动态连接器和程序协作(cooperate)通过PLT和GOT来解析符号
引⽤。
1.当第⼀次创建程序的内存映象时,动态连接器为在GOT中特别的变量设置
第⼆次和第三次的⼊⼝。下⾯关于那些变量有更多的解释。
2.假如PLT是位置⽆关的,那么GOT的地址⼀定是保留在%ebx中的。每个在进程
映象中共享的object⽂件有它⾃⼰的PLT,并且仅仅在同⼀个object⽂件中,
控制传输到PLT⼊⼝。从⽽,要调⽤的函数有责任在调⽤PLT⼊⼝前,设置PLT
地址到寄存器中。
3.举例说明,假如程序调⽤函数name1,它的传输控制到标号.PLT1.
4.第⼀个指令跳到在GOT⼊⼝的name1地址。初始话时,GOT保存着紧跟着的push1
指令的地址,⽽不是真实的name1的地址。
5.因此,程序在堆栈中压⼊(push)⼀个重定位的偏移量。重定位的偏移量是
⼀个32位,⾮负的字节偏移量(从定位表算起)。指派的重定位⼊⼝将是
⼀个R_386_JMP_SLOT类型,它的偏移量指明了GOT⼊⼝(在前⾯的jmp指令中
被使⽤)。该重定位⼊⼝也包含⼀个符号表的索引,因此告诉动态连接器
哪个符号要被引⽤,在这⾥是name1。
6.在压⼊(push)⼀个重定位的偏移量后,程序跳到.PLT0,在PLT中的第⼀个⼊⼝。
push1指令在堆栈中放置第⼆个GOT⼊⼝(got_plus_4or4(%ebx))的值,
因此,给动态连接器⼀个word的鉴别信息。然后程序跳到第三个GOT⼊⼝
(got_plus_8or8(%ebx)),它传输控制到动态连接器。
7.当动态连接器接到控制权,它展开堆栈,查看指派的重定位⼊⼝,寻找符号的
值,在GOT⼊⼝中存储真实的name1地址,然后传输控制想要⽬的地。
⼊⼝的并发执⾏将直接传输控制到name1,⽽不⽤第⼆次调⽤动态连接器
了。所以,在.PLT1中的jmp指令将转到name1,代替“fallingthrough”
转到pushl指令。
LD_BIND_NOW环境变量能改变动态连接器的⾏为。假如这个变量为⾮空,动态
连接器在传输控制到程序前计算PLT⼊⼝。换句话说,动态连接器处理重定位
类型为R_386_JMP_SLOT的⼊⼝在进程初始化时。否则,动态连接器计算PLT⼊⼝
懒惰的,推迟到符号解析和重定位直到⼀个表⼊⼝的第⼀次执⾏。
注意:⼀般来说,以懒惰(Lazy)⽅式绑定是对全应⽤程序执⾏的改进。
因为不使⽤的符号就不会招致动态连接器做⽆⽤功。然⽽,对⼀些应⽤程序,
两种情况使⽤懒惰(Lazy)⽅式是不受欢迎的。
第⼀初始的引⽤⼀个共享object函数⽐后来的调⽤要花的时间长,因为动
态连接器截取调⽤来解析符号。⼀些应⽤程序是不能容忍这样的。
第⼆假如这个错误发⽣并且动态连接器不能解析该符号,动态连接器将终⽌
程序。在懒惰(Lazy)⽅式下,这可能发⽣在任意的时候。⼀再的,⼀
些应⽤程序是不能容忍这样的。通过关掉懒惰(Lazy)⽅式,在应⽤程
序接到控制前,当在处理初始话时发⽣错误,动态连接器强迫程序,使
之失败。
HashTable(哈希表)
Elf32_Wordobject的哈希表⽀持符号表的访问。
标号出现在下⾯帮助解释哈希表的组织,但是它们不是规范的⼀部分。
+Figure2-14:SymbolHashTable
nbucket
nchain
bucket[0]
...
bucket[nbucket-1]
chain[0]
...
chain[nchain-1]
bucket数组包含了nbucket⼊⼝,并且chain数组包含了nchain个⼊⼝;索引从0开始。
bucket和chain保存着符号表的索引。Chain表⼊⼝类似于符号表。符号表⼊⼝的
数⽬应该等于nchain;所以符号表的索引也选择chain表的⼊⼝。
⼀个哈希函数(如下的)接受⼀个符号名并且返回⼀个可以被计算机使⽤的bucket索引
的值。因此,假如⼀个哈希函数返回⼀些名字的值为X,那么bucket[x%nbucket]
将给出⼀个索引y(既是符号表和chain表的索引)。假如符号表⼊⼝不是期望的,
chain[y]给出下⼀个符号表的⼊⼝(使⽤相同的哈希变量)。可以沿着chain
链直到选择到了期望名字的符号表⼊⼝或者是碰到了STN_UNDEF的⼊⼝。
+Figure2-15:HashingFunction
unsignedlong
elf_hash(constunsignedchar*name)
{
unsignedlongh=0,g;
while(*name){
h=(h<<4)+*name++;
if(g=h&0xf0000000)
h^=g>>24;
h&=~g;
}
returnh;
}
InitializationandTerminationFunctions
初始化和终⽌函数
在动态连接妻建⽴进程映象和执⾏重定位以后,每⼀个共享object得到适当
的机会来执⾏⼀些初始话代码。初始化函数不按特别的顺序被调⽤,但是
所有的共享object初始化发⽣在执⾏程序获得控制之前。
类似地,共享的object可能包含终⽌函数,它们在进程本⾝开始它的终⽌之后
被执⾏(以atexit(BA_OS)的机制)。
共享object通过设置在动态结构中的DT_INIT和DT_FINI⼊⼝来指派它们的初始化
和终⽌函数,如上动态ction(DynamicSection)部分描述。典型的,那些函数
代码存在.init和.finiction中,第⼀部分的“ction”已经提到过。
注意:尽管atexit(BA_OS)的终⽌处理⼀般可可正常完成,但是不保证在死进程上
被执⾏。特别的,假如_exit被调⽤(看exit(BA_OS))或者假如进程死掉,那么
进程是不执⾏终⽌处理的。因为它收到⼀个信号,该信号可捕获或忽略。
________________________________________________________________
RY
________________________________________________________________
==========================CLibrary===========================
C库,libc,包含了所有的符号(包含在libsys),另外,包含在在下⾯两个
表中列出的运⾏函数。第⼀个表中的运⾏函数是ANSIC标准的。
+Figure3-1:libcContents,NameswithoutSynonyms
abortfputcisprintputcstrncmp
absfputsispunctputcharstrncpy
asctimefreadisspaceputsstrpbrk
atoffreopenisupperqsortstrrchr
atoifrexpisxdigitraistrspn
atolfscanflabsrandstrstr
barchfekldexprewindstrtod
clearerrftposldivscanfstrtok
clockftelllocaltimetbufstrtol
ctimefwritelongjmptjmpstrtoul
difftimegetcmblentvbuftmpfile
divgetcharmbstowcssprintftmpnam
fclogetenvmbtowcsrandtolower
feofgetsmemchrsscanftoupper
ferrorgmtimememcmpstrcatungetc
fflushisalnummemcpystrchrvfprintf
fgetcisalphamemmovestrcmpvprintf
fgetposiscntrlmemtstrcpyvsprintf
fgetsisdigitmktimestrcspnwcstombs
fopenisgraphperrorstrlenwctomb
fprintfislowerprintfstrncat
再加上,libc保存着以下的服务。
+Figure3-2:libcContents,NameswithSynonyms
__asrtgetdatelockf**sleeptell**
cfgetispeedgetoptlarchstrduptempnam
cfgetospeedgetpassmemccpyswabtfind
cftispeedgetsuboptmkfifotcdraintoascii
cftospeedgetwmktemptcflow_tolower
ctermidhcreatemonitortcflushtarch
curidhdestroynftwtcgetattr_toupper
dup2harchnl_langinfotcgetpgrptwalk
fdopenisasciipclotcgetsidtzt
__filbufisattypopentcndbreak_xftw
filenoisnanputenvtctattr
__flsbufisnand**putwtctpgrp
fmtmsg**lfindtlabeltdelete
**=FunctionisatLevel2intheSVIDIssue3andthereforeat
Level2int篮球大前锋 heABI.
包括上⾯同义(Synonyms)表列出的标号,对于
形式(带⼀个下划线,上⾯没有列出来)优先权⾼于它们的名字。所以,例如,
libc同时包含了getopt和_getopt。
在常规的上列中,其他地⽅以下没有被定义。
int__filbuf(FILE*f);
Thisfunctionreturnsthenextinputcharacterforf,filling
rnsEOFifanerroroccurs.
int__flsbuf(intx,FILE*f);
Thisfunctionflushestheoutputcharactersforfasif
putc(x,f)hadbeencalledandthenappendsthevalueofxto
rnsEOFifanerroroccurs
andxotherwi.
int_xftw(int,char*,int(*)(char*,structstat*,int),int);
Callstotheftw(BA_LIB)functionaremappedtothisfunction
nctionisidenticalto
ftw(BA_LIB),exceptthat_xftw()takesaninterpodfirst
argument,whichmusthavethevalue2.
要了解更多的关于SVID,ANSIC,POSIX的知识,可看该章节其他的库ction部分。
该节“SystemDataInterfaces”后有更多的描述。
GlobalDataSymbols
全局数据符号
libc库需要⼀些外部的全局数据符号(为了它⾃⼰的常规⼯作⽽定义的)。
所有向libsys库请求的数据符号⼀定要让libc提供,就象下⾯表中的数据符号。
正式定义的数据object被他们的符号描述,看SystemV接⼝定义,第三版本
或者第6章节的数据定义(DataDefinitions)ction(在适当的处理器
补充到SystemVABI)。
在下⾯表中的⼊⼝有
下划线的synonyms假设满⾜ANSIC标准。
+Figure3-3:libcContents,GlobalExternalDataSymbols
getdate_erroptarg
_getdate_erropterr
__ioboptind
optopt
本⽂来⾃CSDN博客,转载请标明出处:
本文发布于:2023-03-18 06:24:03,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/fanwen/zuowen/4a04409c5b7f78d054f9f4512bbb0eb9.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文word下载地址:如何改变文件格式.doc
本文 PDF 下载地址:如何改变文件格式.pdf
留言与评论(共有 0 条评论) |