分享

(zz)关于静态库,共享库,动态链接库

 myxueye 2012-05-15
关于静态库,共享库,动态链接库~~~
2011-02-23 11:39

动态链接库de创建
 
 一个“程序函数库”简单说就是一个文件包含一些编译好的代码和数据,这些编译好的代码和数据可以在事后供其它的程序
使用。程序函数库可以使整个程序更加模块化,更容易重新编译,而且更方便升级。

 程序函数库可分为三种类型:静态函数库--static libraries,共享函数库--shared libraries,和动态函数库--dynamic loaded libraries。静态函数库是在程序执行前就加入到目标程序中去了;而共享函数库则是在程序启动的时候加载到程序中,它可以被不同的程序共享;动态加载函数库则可以在程序运行的任何时候动态加载。实际上,动态函数库并非另外一种库函数
模式,区别是动态函数库是如何被程序元使用的。
 
静态函数库:

 :静态函数库实际上就是简单的一个普通的目标文件集合,一般来说习惯使用".a"作为文件的后缀。
 :可以用ar这个程序产生静态函数库文件。ar是archiver的缩写。静态函数库现在已经不再像以前用得那么多了,主要是共享函数库与之比较有很多的优势的原因。
 :静态库函数允许程序员把程序link起来而不用重新编译代码,节省了重新编译代码的时间。
 :静态函数库对开发这还是很有用的,例如你想把自己提供的函数给别人使用,但是又想对函数的源代码进行保密,你就可以给别人提供一个静态函数库文件。理论上说,使用elf格式的静态库函数生成的代码可以比使用共享函数库(或者动态函数库)的程序运行速度上快一些,大概1-5%。
 :创建一个静态函数库文件,或者往一个已经存在的静态函数库文件添加新的目标代码,可以用下面命令:
 
   ar rcs my_library.a file1.o file2.o  //more info, please man ar
   
 :一旦你创建了一个静态函数库,你就可以使用它了。你可以把它作为你编译和链接过程中的一部分用来生成你的可执行代码。如果你用gcc来编译产生可执行代码的话,你可以用"-l"参数来指定这个库函数。你也可以用ld来做,它用它的"-l"和"-L"参数选项。具体做法可参考info:gcc。
 
共享函数库:

 :共享函数库中的函数是在当一个可执行程序在启动的时候被加载。如果一个共享函数库正常安装,所有的程序在重新运行的时候都可以自动加载最新的函数库中的函数。对于Linux系统还有更多的可以实现的功能:(1)升级了函数库但是仍然允许程序使用老版本的函数库;(2)当执行某个特定程序的时候可以覆盖某个特定的库或者库中指定的函数; (3)可以在库函数被使用的过程中修改这些函数库。
 一些约定:如果你要编写的共享函数库支持所有有用的特性,你在编写的过程中必须遵循一系列约定。你必须理解库的不同名字间的区别,例如它的"soname"和"real name"之间的区别和它们是如何相互作用的。你同样还要知道你应该把这些库函数放在你文件系统的什么位置下等等。
 :共享库的命名--每个共享函数库都有个特殊的名字,称作soname。soname名字命名必须以"lib"作为前缀,然后是函数库的名字,然后是".so",最后是版本号信息。不过有个特例,就是非常底层的C库函数都不是以lib开头这样命名的。
   每个共享函数库都有一个真正的名字--real name,它是包含真正库函数代码的文件。真名有一个主版本号,和一个发行版本号。最后一个发行版本号是可选的,可以没有。主版本号和发行版本号使你可以知道你到底是安装了什么版本的库函数。
   另外,还有一个名字是编译器编译的时候需要的函数库的名字,这个名字就是简单的soname名字,而不包含任何版本号信息。
   管理共享函数库的关键是区分好这些名字。当可执行程序需要在自己的程序中列出这些它们需要的共享库函数的时候,它只要用soname就可以了;反过来,当你要创建一个新的共享函数库的时候,你要指定一个特定的文件名,其中包含很多很细节的版本信息。当你要安装一个新版本的函数库的时候,你只要先将这些函数库文件拷贝到一些特定的目录中,运行ldconfig就可以。ldconfig检查已经存在的库函数,然后创建soname的符号连接到真正的函数库,同时设置/etc/ld.so.cache这个缓冲文件。
   ldconfig并不设置连接的名字,通常的做法是在安装过程当中完成这个连接名字的建立,一般来说这个符号连接就简单的指向最新的soname或者最新版本的函数文件。最好把这个符号连接指向soname,因为通常当你升级你的库函数的时候,你就可以自动使用新版本的函数库了。
   Example:
   /usr/lib/libreadline.so.3是一个完全的完整的soname,ldconfig可以设置一个符号链接到其它某个真正的函数库文件,
   例如是/usr/lib/libreadline.so.3.0。同时还必须有一个连接名字,例如/usr/lib/libreadline.so就是一个符号链接
   指向/usr/lib/libreadline.so.3。//???????
 :文件系统中函数库文件的位置--共享函数库文件必须放在一些特定的目录里,这样通过系统的环境变量设置,应用程序才能正确的使用这些函数库。大部分的源码开发的程序都遵循GNU的一些标准。GNU标准建议所有的函数库文件都放在/usr/local/lib目录下,而且建议命令可执行程序都放在/usr/local/bin目录下。这都是一些习惯问题,可以改变。而文件系统层次化标准FHS(Filesystem Hierarchy Standard)规定:在一个发行包中大部分的函数库文件应该安装到/usr/lib目录下,但是如果某些库是在系统启动的时候要加载的,则放到/lib目录下,而那些不适系统本身一部分的库则放到/usr/local/lib下。
   GNU提出的标准主要对于开发者开放源码的,而FHS的建议则是针对发行版本的路径的。具体信息可以看/etc/ld.so.conf里面的配置信息。

 :如何使用?
   在基于GNU glibc的系统里,包括所有的linux系统,启动一个elf格式的二进制可执行文件会自动启动和运行一个program loader。对于linux系统,这个loader的名字是/lib/ld-linux.so.X(version)。这个loader启动后,反过来就会load所有的本程序要使用的共享函数库。
   到底在哪些目录里查找共享函数呢?这些定义缺省的是放在/etc/ld.so.conf文件里面,我们可以修改这个文件,加入我们自己的一些特殊的路径要求。
   如果想覆盖某个库中的一些函数,用自己的函数替换它们,同时保留该库中其它的函数的话,你可以在/etc/ld.so.preload中加入你想要替换的库(.o结尾的文件),这些preloading的库函数将有优先加载的权利。 
   当程序启动的时候搜索所有的目录显然会效率很低,于是linux系统实际上用的是一个高速缓冲的做法。ldconfig缺省情况下读出/etc/ld.so.conf相关信息,然后设置适当当地符号链接,然后写cache到/etc/ld.so.cache这个文件中,而这个/etc/ld.so.cache则可以被其它程序有效使用。这样的做法可以大大提高访问函数库的速度。这就要求每次新增加一个动态加载的函数库的时候,就要运行ldconfig来更新这个cache,如果要删除某个函数库,或者某个函数库的路径修改了,都要重新运行ldconfig来更新这个cache。通常的一些包管理器在安装一个新的函数库的时候都要运行ldconfig。
   
 :环境变量
   各种各样的环境变量控制着一些关键的过程。例如你可以临时为特定的程序的一次执行指定一个不同的函数库。Linux系统中,通常变量LD_LIBRARY_PATH就是可以用来会自定函数库查找路径的,而且这个路径通常是在查找标准路径之前查找。这个是很有用的,特别是在调试一个新的函数库的时候,或者在特殊的场合使用一个非标准的函数库的时候。环境变量LD_PRELOAD列出了所有共享函数库中需要优先加载的库文件,功能和/etc/ld.so.preload类似。这些都是有/lib/ld-linux.so这个loader来实现的。值得一提的是LD_LIBRARY_PATH可以在大部分UNIX-LIKE系统下正常工作,但是并非所有的系统下都可以使用,例如HP-UX系统下,就是用SHLIB_PATH这个变量,而在AIX下则使用LIBPATH这个变量。LD_LIBRARY_PATH在开发和调试过程中经常大量使用,但是不应该被一个普通用户在安装过程中被安装程序修改...
   事实上还有更多的环境变量影响着程序的调入过程,它们的名字通常就是以LD_或者RTLD_打头。大部分这些环境变量的使用的文档都不完全。如果要真正弄清楚它们的用法,最好去读loader的源码(gcc的一部分)。
   允许用户控制动态链接函数库将涉及到setuid/setgid这个函数如果特殊的功能需要的话。因此,GNU loader通常限制或者忽略
   用户对这些变量使用的setuid和setgid,如果loader通过判断程序的相关环境变量判断程序是否使用了setuid或者setgid,然后就大大的限制控制这个老链接的权限。如果阅读GNU glibc的库函数源码,就可以清查的看到这一点,特别的我们可以看elf/rtld.c和/sysdeps/generic/dl-sysdep.c这两个文件。这就意味着如果你使用uid和gid与euid和egid分别相等,然后调用一个程序,那么这些变量就可以完全起效。
   
   创建一个共享函数库:
   现在我们开始学习创建一个共享函数库。其实创建一个共享函数库非常容易,首先创建object文件,这个文件将加入通过gcc -fPIC参数命令加入到共享函数库里面。PIC的意思是位置无关代码--Position Independent Code。下面是一个标准的格式:
   gcc -shared -Wl, -soname,your_soname -o library_name file_list library_list
   
   Example:
   创建两个object文件a.o和b.o,然后创建一个包含a.o和b.o的共享函数库。例子中-g和-Wall参数不是必须的。
   gcc -fPIC -g -c -Wall a.c
   gcc -fPIC -g -c -Wall b.c
   gcc -shared -Wl, -soname, liblusterstuff.so.1. -o liblusterstuff.so.1.0.1 a.o b.o -lc
   
   一些注意的地方:
     不用-formit-frame-pointer这个编译参数,除非你不得不这样。虽然使用了这个参数后获得的函数库仍然可用,但是这使得调试程序几乎没有用,无法跟踪调试。
  使用-fPIC来乘胜代码,而不是-fpic。
  某些情况下,使用gcc来生成object文件,需要使用"-Wl, -export-dynamic"这个选项参数。通常动态函数库的符号表里面包含了这些动态的对象的符号。这个选项在创建elf格式的文件的时候,会将所有的符号加入到动态符号表中。可以参考ld的帮助获得更多信息。
   
 :安装和使用共享函数库
   一旦你创建并使用一个共享函数库,你还需要安装它。其实简单得方法就是拷贝你的库文件到指定的标准的目录,然后运行ldconfig。
   如果你没有权限去做这些事情,那么你只好通过修改你的环境变量来实现这些函数库的使用了。首先,你需要创建这些共享函数库;然后,设置一些必须的符号链接,特别是从soname到真正的函数库文件的符号链接,简单的方法就是运行ldconfig:
   
   ldconfig -n directory_with_shared_libraries
   
   然后你就可以设置你的LD_LIBRARY_PATH这个环境变量,它是一个以逗号分割的路径的集合,这个可以用来指明共享函数库的搜索路径。例如,使用bash,就可以这样来启动一个程序my_program:
   
   LD_LIBRARY_PATH=. :$LD_LIBRARY_PATH my_program
   
   如果你需要的是重载部分函数,则你就需要创建一个包含需要重载的函数的object文件,然后设置LD_PRELOAD环境变量。通常你可以很方便升级你的函数库,如果某个API改变了,创建库的程序会改变soname。然而,如果一个函数升级了某个函数库而保持了原来的soname,你可以强行将老版本的函数库拷贝到某个位置,然后重新命名这个文件(例如使用原来的名字,然后后面加.orig后缀),然后创建一个小的"wrapper"脚本来设置这个库函数和相关的东西。  
   Example:
   #!/bin/sh export
   LD_LIBRARY_PATH=/usr/local/my_lib:$LD_LIBRARY_PATH exec /usr/bin/my_program.orig $*
   
   我们可以通过运行ldd来查看某个程序使用的共享函数库。
   Example:利用ldd来看ls这个使用工具使用的函数库:
   
   ldd /bin/ls
   libtermcap.so.2 => /lib/libtermcap.so.2 (0x4001c000)
   libc.so.6 => /lib/libc.so.6 (0x40020000)
   /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
   
   通常我们可以看到一个soname的列表,包括路径。在所有的情况下,你都至少可以看到两个库:
   
   /lib/ld-linux.so.N (N是1或者更大,一般至少2) //这个是用来加载其它所有的共享库的库

   /libc.so.N (N应该大于或者等于6)。这是C语言库函数。
   
   值得一提的是,不要再对你不信任的程序运行ldd命令。在ldd的manual里面写的清楚,ldd是通过设置某些特殊的环境变量(例如,对于elf对象,设置LD_TRACE_LOADED_OBJECTS),然后运行这个程序。这样就有可能执行某些意想不到的代码,而产生不安全的隐患。
   
 :不兼容的函数库
   如果一个新版本的函数库要和老版本的二进制的库不兼容,则soname需要改变。对于C语言,一共有四个基本理由使得它们在
   二进制代码上很难兼容:(1)一个函数的行文改变了,这样它就可能与最开始的定义不相符合;(2)输出的数据项改变了;(3)某些输出的函数删除了;(4)某些输出函数的接口改变了。如果你能避免这些地方,你就可以保持你的函数库在二进制代码上的兼容,或者说,你可以使得你的程序应用二进制接口(ABI:Application Binary Interface)上兼容。


动态加载的函数库:参见第一部分。

 

nm命令:

nm命令可以列出一个函数库文件中的符号表。它对于静态的函数库和共享的函数库都起作用。对于一个给定的函数库,nm命令可以列出函数库中定义的所有符号,包括每个符号的值和类型。还可以给出在原程序中这个函数(符号)是在多少行定义的,不过这必须要求编译该函数库的时候加“-l”选项。

符号的类型是以一个字母的形式显示的,小写字母表示这个符号是本地(local)的,而大写字母则表示这个符号是全局的(global,externel)。一般来说,类型有一下几种:T、D、B、U、W。各自的含义如下:T表示在代码段中定义的一般变量符号;D表示时初始化过的数据段;B表示初始化的数据段;U表示没有定义的,在这个库里面使用了,但是在其他库中定义的符号;W,weak的缩写,表示如果其他函数库中也有对这个符号的定义,则其他符号的定义可以覆盖这个定义。

如果你知道一个函数的名字,但是你不知道这个函数在什么苦衷定义的,那么可以用nm的"-o"选项和grep命令来查找库的名字。
Example:

 nm -o /lib/* /usr/lib/* /usr/lib/*/* /usr/local/lib/* 2 > /dev/null | grep 'cos$'

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多