linux下动态链接库的加载及解析过程

http://hi.baidu.com/hust_chen/blog/item/54a8c516231d0c0ec93d6d3e.html

 

linux下动态链接库的加载及解析过程(ZZ)
2008-12-18 15:19

表面上看,动态链接库(dll)的加载及解析是一个十分繁复的过程,其中牵涉到的数据结构及其之间的关系也让人望而生畏。Whatever,学习这 事情,说到底是没有捷径可走的,除了死啃仅有的一些资料,如ELF format、Loaders and Linkers,借用objdump及gdb来跟踪程序的执行流程,对于理解过程也是颇有帮助的,anyway,不嫌烦就好了。
在不同的体系下,dll的加载及解析过程是有差别的,这是因为不同体系下生成的ELF文件不尽相同,譬如说,在IA32下,会生成.plt这个dynamic section,但到了MIPS下,便成了.MIPS.stubs。
尽管如此,排除表面上的差异,其本质却是一致的。先说说ELF文件的format,ELF实质是可执行文件的一种格式,对于机器而言,所有的可执行文件不 过是二进制代码的流水罢了,但为了方便管理这些二进制代码,编译器便将这些机器码分为几个段,并统计出相关的信息,通俗地说,也就是对机器码包装包装而 已。Technically speaking, 每个ELF文件都有一个ELF header,保存的是program header与section header的偏移量。关于program header与section header,以后再说呗。

如果某个文件是动态编译的,那么其对应的ELF文件中肯定会有一个.dynamic section,这个section包含了dll的加载与解析所需要的信息。.dynamic中的entry因类型不同而被赋予不同的含义,最为常用的是下面几种类型的entry:

·DT_NEEDED:d_tag=1,d_val中的值为needed library的name在string table中偏移量。如果所需要动态库的数量为n个,则相应类型为DT_NEEDED的entry也应该有n条;

·DT_PLTGOT:d_tag=3,d_ptr中的值为.plt(procedure linkage table)或.got(global offset table)的起始地址,在MIPS体系下,这个值是指向.got的起始地址;

·DT_HASH:d_tab=4,d_ptr中的值为hash table的起始地址;

·DT_STRTAB:d_tab=5,d_ptr中的值为string table的起始地址;

·DT_SYMTAB:d_tab=6,d_ptr中的值为symbol table的起始地址;

·DT_RPATH:d_tab=15,d_val中的值为needed library所在的路径名称(注:needed library不一定就会在这个路径下,detail参见dll的加载过程)在string table中的起始地址。

这里面牵涉到了一些重要的数据结构:

(1) hash table:其作用是根据symbol name找到其在symbol table中的位置。简单地说,是以symbol name作为查找的key,返回指向symbol table的index(表示第index个symbol entry)。当然,实际的做法没有这般简单,因为要考虑到hash table的地址冲突问题。hash table包括两部分:bucket与chain。假设x代表symbol name的值,y=bucket[x%nbucket],如果y所指向的symbol entry不是所求的entry,那么就得利用chain[y]来查找下一个index,直到成功为止。

这里还牵涉到一个问题:如何得知y所指向的symbol entry是否所求的entry?一个显然的答案是用已知的symbol name与该symbol entry所对应的name作匹配,但因为symbol table中的entry并没有包含symbol name这个字段,所以必须要借助string table来得出其所对应的symbol name(参见下面)。

(2) symbol table:其保存的是符号的相关信息。其中,比较重要的是st_name与st_value。st_name是该entry所对应的name在string table中的偏移量;st_value是该symbol在代码段中的起始地址。

(3) string table:纯粹意义上的由一组字符串组成的表格。每个字符串以'/0'标记结束,特别注意的是,string table第一个元素的值为'/0'。

那为什么不直接把symbol name放到symbol table中,而间接用string table保存呢?因为symbol name的长度是不确定的,为了保证symbol table的每个entry的大小一致,便把symbol name移到string table中。

 

dll的加载过程

根据DT_NEEDED可以得到所需dll的名称在string table中的偏移量,通过访问string table便可以得到dll的名称了。下一步便是找这些dll所在的路径,以便将其加载到内存中。1)搜索DT_RPATH所指向的路径(也是通过 string table来得到路径的名称);2)搜索环境变量中的路径;3)搜索/usr/lib及/lib。如果在这几个地方都找不到所需的dll,那么加载便失败 了。

 

dll函数的解析过程

所谓的lazy mode,也就是当需要dll中的某个函数时方将其起始地址计算出来,而不是事先便把dll的所有函数的起始地址都计算好,这是因为并非dll中的每个函数都有机会被调用。

由于文件中可能多次调用同一外部函数,这便要考虑如何使得每个外部函数只被解析一次,而不是每调用一次便解析一次。实现的方法是利用.got 与.plt(或者是MIPS体系下的.MIPS.stubs)。.got是全局偏移量表,存放的是全局符号(自然包括所调用的外部函数的符号)所对应的二 进制代码在ELF文件中的起始地址。.plt是一段一段的小代码,每段小代码对应一个动态库中的函数符号,细节可以查看Loaders and Linkers。 大致的过程如下(以MIPS为例):调用外部函数的二进制代码中肯定有一句无条件转移语句,该语句表明程序会跳到got[n]所指向的地址中,在该函数符 号没有被解析前,这个地址是指向stub中相应的某段小代码(这是由编译器事先绑定好的),这段小代码中有个跳转语句,是转移到got[0]所指向的地 址,这个地址其实便是符号解析函数所在的起始地址,当完成符号解析这个工作后,便会把got[n]中的内容改为该函数符号的起始地址,这样,当第二次调用 该函数时,便会无须再进行第二次解析。相比之下,IA32的函数解析过程较为复杂,不过在很多地方都很找到相关资料。

至于如何计算函数的起始地址,well,通过hash table、symbol table、string table便可以了,不过前提是要搞清解析哪个函数符号,一般来说,这可以通过分析ELF文件来找出传递的参数,从而获取想要的信息。

当然,想偷懒也是可以的,C标准库提供了函数dlopen()、dlsym(),主要是用于解析函数符号的。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux中,动态链接库的初始化通常是通过动态链接器(ld.so)来完成的。 当一个程序使用到了某个动态链接库时,操作系统会使用动态链接器来加载和初始化该库。动态链接器会检查程序中对该库的引用,并根据指定的搜索路径来查找库文件。一旦找到库文件,动态链接器会将该库加载到进程的虚拟地址空间中。 动态链接器在加载和初始化动态链接库时,会执行以下几个步骤: 1. 打开库文件:动态链接器会使用系统调用(如open)打开库文件,以便读取和加载库中的代码和数据。 2. 解析符号:动态链接器会解析库中的符号。这包括解析库中所有的函数和全局变量的地址,并将其与程序中对应的符号进行匹配。 3. 分配空间:动态链接器会为库代码和全局变量分配内存空间,并将其加载到进程的虚拟地址空间中。 4. 修复引用:动态链接器会修改程序中对库的符号的引用,使其指向在内存中的正确地址。 5. 执行初始化:动态链接器会执行库的初始化函数,进行一些初始化操作,例如初始化全局变量、注册回调等。 6. 设置共享:动态链接器会将库设置为共享状态,这样其他进程也可以使用该库。 通过以上步骤,动态链接器能够成功地加载和初始化动态链接库,使得程序能够正确地使用库中提供的功能和资源。动态链接库的使用不仅可以减小程序的体积,还可以提供代码的复用和维护的便利性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值