====== Dynamic Linking (动态链接) ======
一个可执行文件可能有一个 PT_INTERP 程序头元素。在 exec(BA_OS) 的
过程中,系统从 PT_INTERP 段中取回一个路径名并由解释器文件的段创建初始的
进程映像。也就是说,系统为解释器“编写”了一个内存映像,而不是使用原始
的可执行文件的段映像。此时该解释器就负责接收系统来的控制并且为应用程序
提供一个环境变量。
解释器使用两种方法中的一种来接收系统来的控制。首先,它会接收一个文件描述符
来读取该可执行文件,定位于开头。它可以使用这个文件描述符来读取 并且(或者)
映射该可执行文件的段到内存中。其次,依赖于该可执行文件的格式,系统会载入
这个可执行文件到内存中而不是给该解释器一个文件描述符。伴随着可能的文件描述符
异常的情况,解释器的初始进程声明应匹配该可执行文件应当收到的内容。解释器本身
并不需要第二个解释器。一个解释器可能是一个共享对象也可能是一个可执行文件。
* 一个共享对象(通常的情况)在被载入的时候是位置无关的,各个进程可能不同;
系统在 mmap(KE_OS) 使用的动态段域为它创建段和相关的服务。因而,一个
共享对象的解释器将不会和原始的可执行文件的原始段地址相冲突。
* 一个可执行文件被载入到固定地址;系统使用程序头表中的虚拟地址为其创建段。
因而,一个可执行文件解释器的虚拟地址可能和第一个可执行文件相冲突;这种
冲突由解释器来解决。
Dynamic Linker(动态链接器)
当使用动态链接方式建立一个可执行文件时,链接器把一个 PT_INTERP 类型
的元素加到可执行文件中,告诉系统把动态链接器做为该程序的解释器。
注意:由系统提供的动态链接器是和特定处理器相关的。
Exec(BA_OS) 和动态链接器合作为程序创建进程,必须有如下的动作:
* 将可执行文件的内存段加入进程映像中;
* 将共享对象的内存段加入进程映像中;
* 为可执行文件和它的共享对象进行重定位;
* 如果有一个用于读取可执行文件的文件描述符传递给了动态链接器,那么关闭它。
* 向程序传递控制,就象该程序已经直接从 exec(BA_OS) 接收控制一样。
链接器同时也为动态链接器构建各种可执行文件和共享对象文件的相关数据。就象
在上面“程序头”中说的那样,这些数据驻留在可载入段中,使得它们在执行过程
中有效。(再一次的,要记住精确的段内容是处理器相关的。可以参阅相应处理器
的补充说明来获得详尽的信息。)
* 一个具有 SHT_DYNAMIC 类型的 .dynamic section 包含各种数据。驻留在
section 开头的结构包含了其他动态链接信息的地址。
* SHT_HASH 类型的 .hash section 包含了一个 symbol hash table.
* SHT_PROGBITS 类型的 .got 和 .plt section 包含了两个分离的 table:
全局偏移表和过程链接表。 下面的 section 演示了动态链接器使用和改变
这些表来为 object file 创建内存映像。
由于每一个遵循 ABI 的程序从一个共享对象库中输入基本的系统服务,因此动态
链接器分享于每一个遵循 ABI 的程序的执行过程中。
就象在处理器补充说明的“程序载入”所解释的那样,共享对象也许会占用与记录在
文件的程序头表中的地址不同的虚拟内存地址。动态链接器重定位内存映像,在应用程序
获得控制之前更新绝对地址。尽管在库被载入到由程序头表指定的地址的情况下绝对地址
应当是正确的,通常的情况却不是这样。
如果进程环境 [see exec(BA_OS)] 包含了一个非零的 LD_BIND_NOW 变量,
动态链接器将在控制传递到程序之前进行所有的重定位。举例而言,所有下面的
环境入口将指定这种行为。
* LD_BIND_NOW=1
* LD_BIND_NOW=on
* LD_BIND_NOW=off
其他情况下, LD_BIND_NOW 或者不在环境中或者为空值。动态链接器可以不急于
处理过程链接表入口,因而避免了对没有调用的函数的符号解析和重定位。参阅
"Procedure Linkage Table"获取更多的信息。
Dynamic Section(动态section)
假如一个object文件参与动态的连接,它的程序头表将有一个类型为PT_DYNAMIC
的元素。该“段”包含了.dynamic section。一个_DYNAMIC特别的符号,表明了
该section包含了以下结构的一个数组。
+ Figure 2-9: Dynamic Structure
typedef struct {
Elf32_Sword d_tag;
union {
Elf32_Sword d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;
extern Elf32_Dyn _DYNAMIC[];
对每一个有该类型的object,d_tag控制着d_un的解释。
* d_val
那些Elf32_Word object描绘了具有不同解释的整形变量。
* d_ptr
那些Elf32_Word object描绘了程序的虚拟地址。就象以前提到的,在执行时,
文件的虚拟地址可能和内存虚拟地址不匹配。当解释包含在动态结构中的地址
时是基于原始文件的值和内存的基地址。为了一致性,文件不包含在
重定位入口来纠正在动态结构中的地址。
以下的表格总结了对可执行和共享object文件需要的tag。假如tag被标为
mandatory,ABI-conforming文件的动态连接数组必须有一个那样的入口。
同样的,“optional”意味着一个可能出现tag的入口,但是不是必须的。
+ Figure 2-10: Dynamic Array Tags, d_tag
Name Value d_un Executable Shared Object
==== ===== ==== ========== =============
DT_NULL 0 ignored mandatory mandatory
DT_NEEDED 1 d_val optional optional
DT_PLTRELSZ 2 d_val optional optional
DT_PLTGOT 3 d_ptr optional optional
DT_HASH 4 d_ptr mandatory mandatory
DT_STRTAB 5 d_ptr mandatory mandatory
DT_SYMTAB 6 d_ptr mandatory mandatory
DT_RELA 7 d_ptr mandatory optional
DT_RELASZ 8 d_val mandatory optional
DT_RELAENT 9 d_val mandatory optional
DT_STRSZ 10 d_val mandatory mandatory
DT_SYMENT 11 d_val mandatory mandatory
DT_INIT 12 d_ptr optional optional
DT_FINI 13 d_ptr optional optional
DT_SONAME 14 d_val ignored optional
DT_RPATH 15 d_val optional ignored
DT_SYMBOLIC 16 ignored ignored optional
DT_REL 17 d_ptr mandatory optional
DT_RELSZ 18 d_val mandatory optional
DT_RELENT 19 d_val mandatory optional
DT_PLTREL 20 d_val optional optional
DT_DEBUG 21 d_ptr optional ignored
DT_TEXTREL 22 ignored optional optional
DT_JMPREL 23 d_ptr optional optional
DT_LOPROC 0x70000000 unspecified unspecified unspecified
DT_HIPROC 0x7fffffff unspecified unspecified unspecified
* DT_NULL
一个DT_NULL标记的入口表示了_DYNAMIC数组的结束。
* DT_NEEDED
这个元素保存着以NULL结尾的字符串表的偏移量,那些字符串是所需库的名字。
该偏移量是以DT_STRTAB 为入口的表的索引。看“Shared Object Dependencies”
关于那些名字的更多信息。动态数组可能包含了多个这个类型的入口。那些
入口的相关顺序是重要的,虽然它们跟其他入口的关系是不重要的。
* DT_PLTRELSZ
该元素保存着跟PLT关联的重定位入口的总共字节大小。假如一个入口类型
DT_JMPREL存在,那么DT_PLTRELSZ也必须存在。
* DT_PLTGOT
该元素保存着跟PLT关联的地址和(或者)是GOT。具体细节看处理器补充
(processor supplement)部分。
* DT_HASH
该元素保存着符号哈希表的地址,在“哈希表”有描述。该哈希表指向
被DT_SYMTAB元素引用的符号表。
* DT_STRTAB
该元素保存着字符串表地址,在第一部分有描述,包括了符号名,库名,
和一些其他的在该表中的字符串。
* DT_SYMTAB
该元素保存着符号表的地址,在第一部分有描述,对32-bit类型的文件来
说,关联着一个Elf32_Sym入口。
* DT_RELA
该元素保存着重定位表的地址,在第一部分有描述。在表中的入口有明确的
加数,就象32-bit类型文件的Elf32_Rela。一个object文件可能好多个重定位
section。当为一个可执行和共享文件建立重定位表的时候,连接编辑器连接
那些section到一个单一的表。尽管在object文件中那些section是保持独立的。
动态连接器只看成是一个简单的表。当动态连接器为一个可执行文件创建一个
进程映象或者是加一个共享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入口记录的表的索引。关于那些名字看
Shared Object Dependencies 部分获得更多的信息。
* DT_RPATH
该元素保存着以NULL结尾的搜索库的搜索目录字符串的字符串表偏移量。
在共享object依赖关系(Shared Object Dependencies)中有讨论
* 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_LOPROC through DT_HIPROC
在该范围内的变量为特殊的处理器语义保留。除了在数组末尾的DT_NULL元素,
和DT_NEEDED元素相关的次序,入口可能出现在任何次序中。在表中不出
现的Tag值是保留的。
Shared Object Dependencies(共享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,再是当前目录。
* 第二,在进程环境中(see exec(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.
注意:出于安全考虑,动态连接器忽略set-user和set-group的程序的
LD_LIBRARY_PATH所指定的搜索目录。但它会搜索DT_RPATH指明的目录和
/usr/lib。
Global Offset Table(GOT全局偏移量表)
一般情况下,位置无关的代码不包含绝对的虚拟地址。全局偏移量表在私有数据
中保存着绝对地址,所以应该使地址可用的,而不是和位置无关性和程序代码段
共享能力妥协。一个程序引用它的GOT(全局偏移量表)来使用位置无关的地址并且
提取绝对的变量,所以重定位位置无关的参考到绝对的位置。
初始时,GOT(全局偏移量表)保存着它重定位入口所需要的信息 [看第一部分的
“Relocation”]。在系统为一个可装载的object文件创建内存段以后,动态
连接器处理重定位入口,那些类型为R_386_GLOB_DAT的指明了GOT(全局偏移量表)。
动态连接器决定了相关的标号变量,计算他们的绝对地址,并且设置适当的内存
表入口到正确的变量。虽然当连接编辑器建造object文件的时候,绝对地址
是不知道,连接器知道所以内存段的地址并且能够因此计算出包含在那里的
标号地址。
假如程序需要直接访问符号的绝对地址,那么这个符号将有一个GOT(全局偏移量表)
入口。因为可执行文件和共享文件有独立的GOT(全局偏移量表),一个符号地址
可能出现在不同的几个表中。在交给进程映象的代码控制权以前,动态连接器处
理所有的重定位的GOT(全局偏移量表),所以在执行时,确认绝对地址是可用的。
该表的入口0是为保存动态结构地址保留的(参考_DYNAMIC标号)。这允许
象动态连接程序那样来找出他们自己的动态结构(还没有处理他们的重
定向入口)。这些对于动态连接器是重要的,因为它必要初始化自己而不
能依赖于其他程序来重定位他们的内存映象。在32位Interl系统结构中,在
GOT中的人口1和2也是保留的,具体看以下的过程连接表(Procedure Linkage
Table)。
系统可以为在不同的程序中相同的共享object选择不同的内存段;它甚至可以
为相同的程序不同的进程选择不同的库地址。虽然如此,一旦进程映象被建立
以后,内存段不改变地址。只要一个进程存在,它的内存段驻留在固定的虚拟
地址。
GOT表的格式和解释是处理器相关的。在32位Intel体系结构下,标号
_GLOBAL_OFFSET_TABLE_可能被用来访问该表。
+ Figure 2-11: Global Offset Table
extern Elf32_Addr _GLOBAL_OFFSET_TABLE_[];
标号_GLOBAL_OFFSET_TABLE_可能驻留在.got section的中间,允许负的和非负
的下标索引这个数组。
Procedure Linkage Table(PLT过程连接表)
就象GOT重定位把位置无关的地址计算成绝对地址一样,PLT过程连接