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过程连接表重定位
位置无关的函数调用到绝对的地址。从一个可执行或者共享的object文件到另外的,
连接编辑器不解析执行的传输(例如函数的调用)。因此,连接编辑器安排程序
的传递控制到PLT中的入口。在SYSTEM V体系下,PLT存在共享文本中,但是它们
使用的地址是在私有的GOT中。符号连接器决定了目标的绝对地址并且修改GOT的
内存映象。因此,在没有危及到位置无关、程序文本的共享能力的情况下。动态
连接器能重定位人口。
+ Figure 2-12: Absolute Procedure Linkage Table {*}
绝对的过程连接表
.PLT0:pushl got_plus_4
jmp *got_plus_8
nop; nop
nop; nop
.PLT1:jmp *name1_in_GOT
pushl $offset
jmp .PLT0@PC
.PLT2:jmp *name2_in_GOT
pushl $offset
jmp .PLT0@PC
...
+ Figure 2-13: Position-Independent Procedure Linkage Table
位置无关(或者说位置独立)的过程连接表
.PLT0:pushl 4(%ebx)
jmp *8(%ebx)
nop; nop
nop; nop
.PLT1:jmp *name1@GOT(%ebx)
pushl $offset
jmp .PLT0@PC
.PLT2:jmp *name2@GOT(%ebx)
pushl $offset
jmp .PLT0@PC
...
注意:如图所示,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_4 or 4(%ebx))的值,
因此,给动态连接器一个word的鉴别信息。然后程序跳到第三个GOT入口
(got_plus_8 or 8(%ebx)),它传输控制到动态连接器。
7. 当动态连接器接到控制权,它展开堆栈,查看指派的重定位入口,寻找符号的
值,在GOT入口中存储真实的name1地址,然后传输控制想要目的地。
8. PLT入口的并发执行将直接传输控制到name1,而不用第二次调用动态连接器
了。所以,在.PLT1中的jmp指令将转到name1,代替“falling through”
转到pushl指令。
LD_BIND_NOW环境变量能改变动态连接器的行为。假如这个变量为非空,动态
连接器在传输控制到程序前计算PLT入口。换句话说,动态连接器处理重定位
类型为R_386_JMP_SLOT的入口在进程初始化时。否则,动态连接器计算PLT入口
懒惰的,推迟到符号解析和重定位直到一个表入口的第一次执行。
注意:一般来说,以懒惰(Lazy)方式绑定是对全应用程序执行的改进。
因为不使用的符号就不会招致动态连接器做无用功。然而,对一些应用程序,
两种情况使用懒惰(Lazy)方式是不受欢迎的。
第一 初始的引用一个共享object函数比后来的调用要花的时间长,因为动
态连接器截取调用来解析符号。一些应用程序是不能容忍这样的。
第二 假如这个错误发生并且动态连接器不能解析该符号,动态连接器将终止
程序。在懒惰(Lazy)方式下,这可能发生在任意的时候。一再的,一
些应用程序是不能容忍这样的。通过关掉懒惰(Lazy)方式,在应用程
序接到控制前,当在处理初始话时发生错误,动态连接器强迫程序,使
之失败。
Hash Table(哈希表)
Elf32_Word object的哈希表支持符号表的访问。
标号出现在下面帮助解释哈希表的组织,但是它们不是规范的一部分。
+ Figure 2-14: Symbol Hash Table
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的入口。
+ Figure 2-15: Hashing Function
unsigned long
elf_hash(const unsigned char *name)
{
unsigned long h = 0, g;
while (*name) {
h = (h << 4) + *name++;
if (g = h & 0xf0000000)
h ^= g >> 24;
h &= ~g;
}
return h;
}
Initialization and Termination Functions
初始化和终止函数
在动态连接妻建立进程映象和执行重定位以后,每一个共享object得到适当
的机会来执行一些初始话代码。初始化函数不按特别的顺序被调用,但是
所有的共享object初始化发生在执行程序获得控制之前。
类似地,共享的object可能包含终止函数,它们在进程本身开始它的终止之后
被执行(以atexit(BA_OS)的机制)。
共享object通过设置在动态结构中的DT_INIT和DT_FINI入口来指派它们的初始化
和终止函数,如上动态section(Dynamic Section)部分描述。典型的,那些函数
代码存在.init和.fini section中,第一部分的“section”已经提到过。
注意:尽管atexit(BA_OS)的终止处理一般可可正常完成,但是不保证在死进程上
被执行。特别的,假如_exit被调用(看exit(BA_OS))或者假如进程死掉,那么
进程是不执行终止处理的。因为它收到一个信号,该信号可捕获或忽略。
阅读(8955) | 评论(0) | 转发(0) |