首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

剖析共享程序库(2)

剖析共享程序库(2)

为了进行调试,首先必须知道如何编译为了调试使用共享程序库的问题,对它们如何编译有更多一些了解会对您有所帮助。
在传统的静态程序库中,生成的代码通常封装在一个程序库文件中(其名称以         .a 结尾),然后传递给链接器。在动态程序库中,程序库文件的名称通常以         .so 结尾。文件结构稍有不同。      
常规的静态程序库的格式是         ar 工具(一个非常简单的存档程序,类似于         tar,但是更简单)所创建的那种格式。相反,共享程序库通常以更复杂的文件格式存储。      
在现代 Linux 系统中,这一格式通常是 ELF 二进制格式(可执行与可链接格式(Executable and Linkable Format))。在 ELF 中,每个文件的组成包括:一个 ELF 头,随后是零或者一些段(segments),以及零或者一些区段(sections)。         中包含文件的运行时执行所需要的信息,而         区段 中包含用于链接和重定位的重要数据。整个文件中的每个字节每次只能由一个区段使用,不过可以存在不被任何区段所包含的孤立字节。通常,在 UNIX 可执行文件中,一个或多个区段会封装在一个段内。      
ELF 格式中包含用于应用程序和程序库的规范。但程序库格式要复杂得多,不仅仅是对象模块的简单存档。
链接器将所有对符号的引用进行分类,标识出它们是在哪个程序库中找到的。将静态程序库的符号添加到最终的可执行文件中;然后将共享程序库的符号放入 PLT 中,最后创建对 FLT 的引用。在完成这些任务之后,生成的可执行文件会拥有一个列表,该列表列出了计划从运行期将加载的程序库中找出的那些符号。
在运行期间,应用程序将加载动态链接器。实际上,动态链接器本身使用与共享程序库相同种类的版本号。例如,在 SUSE Linux 9.1 中,        /lib/ld-linux.so.2 文件是一个指向         /lib/ld-linux.so.2.3.3 的符号链接。另一方面,寻找         /lib/ld-linux.so.1 的程序不会尝试使用新的版本。      
然后动态链接器开始进行所有有趣的工作。它会查明某个程序先前链接到了哪些程序库(以及哪个版本),然后加载它们。加载程序库的步骤包括:
  • 找到程序库(它可能在系统中若干个目录中的任意一个目录中)。
  • 将程序库映射到程序的地址空间。
  • 分配程序库可能需要的由零填充的内存块。
  • 添加程序库的符号表。
调试这一过程可能会比较困难。您可能会遇到多种问题。例如,如果动态链接器不能找到某个给定的程序库,那么它将停止加载程序。如果它找到了所有需要的程序库,但却无法找到某个符号,那么它也可能会因此而停止加载操作(但是可能直到真正尝试去引用那个符号时才会发生这种情形) —— 这是一种很少见的情况,因为通常如果不存在某个符号,那么在初始化链接的时候就会被警告。
修改动态链接器的搜索路径当链接某个程序时,在运行期您可以指定另外的搜索路径。在         gcc 中,其语法是         -Wl,-R/path。如果程序已经被链接,那么您也可以设置环境变量         LD_LIBRARY_PATH 来改变这一行为。通常只是在应用程序需要搜索的路径不是系统级默认路径的一部分时才需要这样做,对大部分 Linux 系统来说,这种情况很少见。理论上,Mozilla 用户可以发布某个使用这个路径设置所编译的二进制程序,但是他们更倾向于发布包装器(wrapper)脚本,在启动可执行程序之前正确地设置程序库路径。      
设置程序库路径可以为两个应用程序需要同一程序库的不兼容版本的这种罕见情况提供一个迂回解决方案。可以使用包装器脚本使某一应用程序在使用特殊版本程序库的目录中进行搜索。这称不上是一个完美的解决方案,但是在某些情况下,这是您能采用的最佳方法。
如果出于不得已的原因需要为很多程序添加某个路径,那么也可以修改系统的默认搜索路径。通过         /etc/ld.so.conf 控制动态链接器,该文件包含默认搜索路径的列表。对         LD_LIBRARY_PATH 中指定的任何路径的搜索都要先于         ld.so.conf 中列出的路径,所以用户可以覆盖这些设置。      
大部分用户没有理由修改系统默认程序库搜索路径;通常环境变量更适用于修改搜索路径,比如连接某个工具包中的程序库,或者使用某个程序库的较新版本的测试程序。
使用         lddldd 是调试共享程序库问题的一个实用工具。其名称来自         list dynamic dependencies。这个程序会查看某个给定的可执行程序或者共享程序库,并指出它需要加载哪些共享程序库以及要使用哪些版本。输出类似如下:      
清单 1. /bin/sh 的依赖
1
2
3
4
5
6
7
8
$ ldd /bin/sh
        linux-gate.so.1 =>  (0xffffe000)
        libreadline.so.4 => /lib/libreadline.so.4 (0x40036000)
        libhistory.so.4 => /lib/libhistory.so.4 (0x40062000)
        libncurses.so.5 => /lib/libncurses.so.5 (0x40069000)
        libdl.so.2 => /lib/libdl.so.2 (0x400af000)
        libc.so.6 => /lib/tls/libc.so.6 (0x400b2000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)




看到一个“简单的”的程序使用了这么多个程序库,可能会有些令人惊讶。或许是         libhistory 需要         libncurses。为了查明真相,我们只需要运行另一个         ldd 命令:      
清单 2. libhistory 的依赖
1
2
3
4
5
$ ldd /lib/libhistory.so.4
        linux-gate.so.1 =>  (0xffffe000)
        libncurses.so.5 => /lib/libncurses.so.5 (0x40026000)
        libc.so.6 => /lib/tls/libc.so.6 (0x4006b000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x80000000)




在某些情况下,可能需要为应用程序指定另外的程序库路径。例如,对 Mozilla 二进制程序尝试运行         ldd 所得到输出的前几行如下所示:      
清单 3. 运行 dll 查找不在搜索路径中的 程序库的结果
1
2
3
4
5
6
7
$ ldd /opt/mozilla/lib/mozilla-bin
        linux-gate.so.1 =>  (0xffffe000)
        libmozjs.so => not found
        libplds4.so => not found
        libplc4.so => not found
        libnspr4.so => not found
        libpthread.so.0 => /lib/tls/libpthread.so.0 (0x40037000)




为什么找不到这些程序库?因为它们不在常见的程序库搜索路径中。实际上,它们在         /opt/mozilla/lib 中,所以,解决方案之一是将这个目录添加到         LD_LIBRARY_PATH 中。      
另一个选项是将路径设置为         .,并在这个目录下运行         ldd,尽管这样做更危险 —— 将当前目录添加到程序库路径中与将它添加到可执行程序路径中一样有着潜在的危险。      
在这种情况下,将这些程序库所在的目录添加到系统级搜索路径中显然不是一个好办法。只有 Mozilla 需要这些程序库。
链接 Mozilla说起 Mozilla,如果您觉得自己从未见过超过几行的程序库,那么在某种程度上,Mozilla 是一个更为典型的大型应用程序。现在您可以明白为什么 Mozilla 的启动需要那么长时间了吧!
清单 4. mozilla-bin 的依赖性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
linux-gate.so.1 =>  (0xffffe000)
libmozjs.so => ./libmozjs.so (0x40018000)
libplds4.so => ./libplds4.so (0x40099000)
libplc4.so => ./libplc4.so (0x4009d000)
libnspr4.so => ./libnspr4.so (0x400a2000)
libpthread.so.0 => /lib/tls/libpthread.so.0 (0x400f5000)
libdl.so.2 => /lib/libdl.so.2 (0x40105000)
libgtk-x11-2.0.so.0 => /opt/gnome/lib/libgtk-x11-2.0.so.0 (0x40108000)
libgdk-x11-2.0.so.0 => /opt/gnome/lib/libgdk-x11-2.0.so.0 (0x40358000)
libatk-1.0.so.0 => /opt/gnome/lib/libatk-1.0.so.0 (0x403c5000)
libgdk_pixbuf-2.0.so.0 => /opt/gnome/lib/libgdk_pixbuf-2.0.so.0 (0x403df000)
libpangoxft-1.0.so.0 => /opt/gnome/lib/libpangoxft-1.0.so.0 (0x403f1000)
libpangox-1.0.so.0 => /opt/gnome/lib/libpangox-1.0.so.0 (0x40412000)
libpango-1.0.so.0 => /opt/gnome/lib/libpango-1.0.so.0 (0x4041f000)
libgobject-2.0.so.0 => /opt/gnome/lib/libgobject-2.0.so.0 (0x40451000)
libgmodule-2.0.so.0 => /opt/gnome/lib/libgmodule-2.0.so.0 (0x40487000)
libglib-2.0.so.0 => /opt/gnome/lib/libglib-2.0.so.0 (0x4048b000)
libm.so.6 => /lib/tls/libm.so.6 (0x404f7000)
libstdc++.so.5 => /usr/lib/libstdc++.so.5 (0x40519000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x405d5000)
libc.so.6 => /lib/tls/libc.so.6 (0x405dd000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
libX11.so.6 => /usr/X11R6/lib/libX11.so.6 (0x406f3000)
libXrandr.so.2 => /usr/X11R6/lib/libXrandr.so.2 (0x407ef000)
libXi.so.6 => /usr/X11R6/lib/libXi.so.6 (0x407f3000)
libXext.so.6 => /usr/X11R6/lib/libXext.so.6 (0x407fb000)
libXft.so.2 => /usr/X11R6/lib/libXft.so.2 (0x4080a000)
libXrender.so.1 => /usr/X11R6/lib/libXrender.so.1 (0x4081e000)
libfontconfig.so.1 => /usr/lib/libfontconfig.so.1 (0x40826000)
libfreetype.so.6 => /usr/lib/libfreetype.so.6 (0x40850000)
libexpat.so.0 => /usr/lib/libexpat.so.0 (0x408b9000)




深入了解共享程序库有兴趣深入了解 Linux 中的动态链接的用户有很多选择。GNU 编译器和链接器工具链(linker tool chain)文档都非常好,虽然其内容是以         info 格式存储的,而且也没有在标准手册页中提及。      
ld.so 的手册页包含有一个非常详尽的列表,列出了改变动态链接器行为的变量,以及对过去曾经使用的不同版本的动态链接器的说明。      
大部分 Linux 文档都假定所有共享程序库都是动态链接的,因为在 Linux 系统上,它们通常是这样的。实现静态链接的共享程序库需要做的工作非常多,而且大部分用户不会因此获得任何好处,尽管支持这个特性的系统的性能会有显著改变。
如果您正在使用现成的预先包装好的系统,那么您可能不会遇到太多的共享程序库版本 —— 系统可能只附带它要链接的那些共享程序库版本。另一方面,如果您做过很多次更新和源代码构建,那么您可能最终得到多个版本的共享程序库,因为老版本依然会被保留,“以防万一”。
像平时一样,如果想了解更多,那么就去亲自实践吧。记住,在某个系统上,几乎所有程序都会引用一些相同的共享程序库,所以,如果破坏了系统的某个核心共享程序库,那么您就得去求助系统恢复工具了。
返回列表