转载自:IBM developerWorks 中国网站
王瑞川 (jeppeterone@163.com)
2003 年 10 月
动态链接,一个经常被人提起的话题。但在这方面很少有文章来阐明这个重要的软件运行机制,只有一些关于动态链接库编程的文章。本系列文章就是要从动态链接库源代码的层次来探讨这个问题。
当然从文章的题目就能够看出,intel平台下的linux ELF文档的动态链接。一则是因为这一方面的资料查找比较方便,二则也是这个讨论的意思比其他的动态链接要更为重要(毕竟现在是intel的天下)。当然,有了这么一个例子,其他的平台下的ELF文档的动态链接也就大同小异。您能够在阅读完了本文之后"举一隅,而反三隅"了。
由于这是个系列的文章,我计划分三部分来写,第一部分主要分析加载,涉及dl_open这个函数的内容,但由于这个函数所包含的内容实在太多。这里主要是他的_dl_map_object和_dl_init这两个部分,因为这里是把动态链接文档通过在ELF文档中的得到信息映射到内存空间中,而_dl_init中是个特别的初始化。这是对面向对象的函数实现的。
第二部分我将分析函数解析和卸载,这里要讲的内容会比较多,但每一个内容都不会多。首先是在前一篇中没有说完的dl_open中的涉及的_dl_map_object_deps和_dl_relocate_object两个函数内容,因为这些都和函数解析的内容直接相关,所以安排在这里。而下面的函数解析过程_dl_runtime_resolve是在程式运行中的动态解析过程。这里从本质上来讲没有太多的代码,但他的精巧程度却是最多的(正是我这三篇文章的核心之处)。最后是个dl_close的实现。这里是个结尾的工作,顺带一下是_dl_signal_cerror,和 _dl_catch_error的错误例外处理。
第三部将给出injectso实例分析和应用,会介绍一个应用了动态链接的实例,并能够在日后的程式调试过程中使用的injectso 实例,他不但能够让我们对前面所说的动态链接原理有一个更感性的认识,而且就这个实例而言,还能够在以后的代码研发过程中来作为一种动态打补丁的工具,甚至有可能,我会在以后的文章中会用这个工具来介绍新的技术。
一、历史问题
关于动态链接,能够说由来已久。假如追溯,最早的思想就在五十年代就有了,那时就想把一些公用的代码放在内存中的一个地方上,在别的地址用call便是了。到后来又发展到了 loading overlays(就是把在程式运行生命期不同的代码在不同的时间段被加入内存),这是在六十年代的事。但这只能算是"滥觞"时期。接近于我们现在所说的动态链接是在unix操作系统之后,因为从unix的设计结构而言,本身就是分成模块来实现一个复杂的功能的操作系统。但这些还不是现代意义上的动态链接,原因是现代意义上的动态链接要符合两个特点:
1、动态的加载,就是当这个运行的模块在需要的时候才被映射入运行模块的虚拟内存空间中,如一个模块在运行中要用到mylib.so中的myget函数,而在没有调用mylib.so这个模块中的其他函数之前,是不会把这个模块加载到您的程式中(也就是内存映射),这些内容在内核中实现,用的是页面异常机制(我可能在另一篇文章中提到这个问题)。
2、动态的解析,就是当要调用的函数被调用的时候,才会去把这个函数在虚拟内存空间的起始地址解析出来,再写到专门在调用模块中的储存地址内,如前面所说的您已调用了myget,所以mylib.so模块肯定已被映射到了程式虚拟内存之中,而假如您再调用mylib.so中的myput函数,那他的函数地址就在调用的时候才会被解析出来。
(注:这里用的程式就是一般所说的进程process,而模块既可能是您的程式的二进制代码,也可能是被您的程式所依赖的别的共享链接文档-------同样ELF格式。)
在这两点中很有点像现在的操作系统中对内存的操作,也就是只有当要用到一个内存空间中的时候才会进行虚拟空间映射,而不是过早的把任何的空间映射好,而只有当要从这个内存空间读的时候才分配物理空间。这有点像第一条。而只有当对这个内存空间进行写的时候产生一个COW(copy on write)。这就有点像第二条。
这样的好处就是充分避免不必要的开销。因为任何一个程式在运行的时候,大部分情况下,不可能用到任何的调用函数。
这样的思想方法提出和实现都是在八十年代的sun公司的SunOS的系统上。
关于这一段历史,请您参见资料[1]。
ELF二进制格式文档和现代的动态链接思想大致是在同一时段形成的,他的来源是AT&T公司的最早的unix中的a.out二进行文档格式。Bell labs的工作人员为了使这种在unix的早期主要的文档格式适应当时新的软件和操作系统的需要(如aix,SunOS,HP-UX这样的unix变种,对更广泛的应用程式的扩展需要,对面向对象的支持等等),就发明了ELF文档格式。
我在这里并不周详讨论ELF文档的具体细节,这本来就能够写一篇很长的文章,您能够参看资料[2]来得到关于他的ABI (application binary interface的规范)。但在ELF文档所采用的那种分层的管理方式却不但在动态链接中起着重要的作用,而且这一思想能够说是我们电脑中的最古老,也是最经典的思想。
对每个ELF文档,都有一个ELF header,在这里的每个header有两个数据成员,就是
Elf32_Off e_phoff;
Elf32_Off e_shoff;
他们分别代表了program header 和section header 在ELF文档中的偏移量。Program header 是总纲,而section header 则是第一个小目。
Elf32_Addr sh_addr;
Elf32_Off sh_offset;
Sh_addr这个section 在内存中的映射地址(对动态链接库而言,这是个相对量,他和整个ELF文档被加载的l_addr形成绝对地址)。Sh_offset是这个 section header在文档中的偏移量。
用一图来表示就是这样的,他就是用elf header 来管理了整个ELF文档:
文章整理:西部数码--专业提供域名注册、虚拟主机服务
http://www.west263.com
以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!




