zoukankan      html  css  js  c++  java
  • 静态函数库,共享函数库以及动态链接库

    动态链接库的使用及注意事项

         windows中动态链接库以.dll后缀为标记。动态链接库和净态链接库不同,它里面的函数并不是执行程序的一部分,而是根据执行程序需要按需装入,同时其执行代码可在多个执行程序间共享,节省了空间,提高了效率,具备很高的灵活性,得到越来越多的青睐。

         在Linux中同样有动态链接库,它们以.so为后缀,即shared object,共享对象。在Linux下创建动态链接库是件再简单不过的事情,只要在变异函数库员程序时加上-shared选项即可,这样所生成的执行程序即为动态链接库。从某种意义上说,动态链接库也是一种执行程序。按一般规则,程序名应带.so后缀。

                                             gcc -shared -o dest src
     
    Linux下动态链接库的使用:

         Linux下使用动态链接库,原程序需要包含dlfcn.h头文件,此文件定义了调用动态链接库的函数的原型。
     
    #include <dlfcn.h>
    #include <link.h>
      
    //Four functions dlopen(), dlsym(),dlclose(),dlerror() implementing the interface to the dynamic linking loader
     
     const char *dlerror(void);
     :当动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时表示操作函数执行成功。
     :The funtion returns a human readable string describing the most recent error that occured from dlopen(),dlsym() or dlclose() since the last call to dlerror(). It returns NULL if no errors have occurred since initialization or since it was last called.


     void *dlopen(const char *filename, int flag);
     :dlopen用于打开指定名字(filename)的动态链接库,并返回操作句柄。
     :The function loads the dynamic library file named by the null-terminated string filename and returns an opaque "handle" for the dynamic library. If filename is NULL, then the returned handle is for the main() program. If filename contains a slash ("/"), then it is interpreted as a (relative or absolute)
      pathname. Otherwise, the dynamic linker searches for the library as follows:(1)(elf only)If the executable file for the calling program contains a DT_RPATH tag, and does not contain a DT_RUNPATH tag, then the directories listed in the DT_RPATH tag are searched.(2)If the environment variable LD_LIBRARY_PATH is defined to contain a conon-separated list of directories, then these are searched. (As a security measure this variable is ignored for set-user-ID and set-group-ID programs). (3)(elf only)If the executable file for the calling program contains a DT_RUNPATH tag, then the directories listed in that tag are searched.(4)The cache file /etc/ld.so.cache (maintained by ldconfig) is checked to see whether it contains an entry fro filename.(5)The directories /lib and /usr/lib are searched(in that order). If the library has dependencies on other shared libraries, then these are also automatically loaded by the dynamic linker using the same rules. (This process may occur recursively, if those libraries in turn have dependencies, ans so on.)
     :filename如果名字不以/开头,则非绝对路径名,将按下列顺序查找该文件。(1)用户环境变量中的LD_LIBRARY值;(2)动态链接缓冲文件/etc/ld.so.cache;(3)目录/lib,/usr/lib;
     :flag表示在什么时候解决未定义的符号(调用)。取值有两个:(1)RTLD_LAZY,表明在动态链接库的函数代码  执行时解决,即不会立即解析为定义的符号名,直到共享库中代码第一次试图引用未定义的符号名时才解析它。如果解析成功,程序继续执行,反之显示错误信息并终止程序(2)RTLD_NOW,表明在dlopen返回前就解决所有未定义的符号,一旦未解决,dlopen将返回错误。
     :RTLD_LAZY: Only resovle symbols as the code that references them is executed. If the symbols is never referenced, then it is never resolved. (Lazy binding is only performed for function references; references to variables are always immediately bound when the library is loaded.)
     :RTLD_NOW: If this value is specified, or the environment variable LD_BIND_NOW is set to a non-empty string, all undefined symbols in the library are resolved before dlopen() returns. If this cannot be done, an error is returned.
     :Zero of more of the following values may also be ORed in flag:
      RTLD_GLOBAL --- The symbols defined by this library will be made available for symbol resolution of subsequently loaded libraries.
      RTLD_LOCAL --- This is the converse of RTLD_GLOBAL, and the default if neither flag is specified. 
      Symbols defined in this library are not made available to resolve references in subsequently loaded libraries.
      RTLD_NODELETE ---  Do not unload the library during dlclose(). Consequently, the library's static variables are not reinitialized if the library is reloaded with dlopen() at a later time.This flag is not specified in POSIX.1-2001. since glibc 2.2
      RTLD_NOLOAD --- Don't load the library. This can be used to test if the library is already resident. (dlopen() returns NULL if it is not, or the library's handle if it is resident). This flag can be used to promote the flags on a library that is already loaded. For example, a library that was previously
      loaded with RTLD_LOCAL can be re-opened with RTLD_NOLOAD | RTLD_GLOBAL. This flag is not specified in POSIX.1-2001.
     :If filename is a NULL pointer, then the returned handle is for the main program. When given to dlsym(), this handle causes a search for a symbol in the main program, followed by all shared libraries loaded at program startup, and then all shared libraries loaded by dlopen() with the flag RTLD_GLOBAL.
     :External references in the library are resolved using the libraries in that library's dependency list and any other libraries previously opend with the RTLD_GLOBAL flag. If the executable was linked with the flag "-rdynamic"(or, synonymously, "--export-dynamic"), then the global symbols in the executable
      will also be used to resolve references in a dynamically loaded library.
     :dlopen调用失败时,将返回NULL值,否则返回的是操作句柄。
     :If the same library is loaded again with dlopen(), the same file handle is returned. The dl library maintains reference counts for library handles, so a dynamic library is not deallocated until dlclose() has been called on it as many times as dlopen() has succeeded on it. The _init routine, if present, is only called once. But a subsequent call with RTLD_NOW may force symbol resolution for a library earlier loaded with RTLD_LAZY.
     
     
      void *dlsym(void *handle, char *symbol);
     :dlsym根据动态链接库操作句柄(handle)与符号(symbol),返回符号对应的函数的执行代码地址。由此地址,可以带参数执行相应的函数。
     :The funtion dlsym() takes a "handle" of a dynamic library returned by dlopen() and the null-terminated symbol name, returning the address where that symbol is loaded in to memory. If the symbol is not found, in the specified library or any of the libraries that were automatically loaded by dlopen() when that library was loaded, dlsym() returns NULL.(The search performed by dlsym() is breadth first through the dependency tree of these libraries.) Since the value of the symbol could actually be NULL (so that a NULL return from dlsym() need not indicate an error), the correct way to test for an error is to call dlerror() to clear any old error conditions, then call dlsym(), and then call dlerror() again, saving its return value into a variable, and check whether this saved value is not NULL.
     :There are two special pseudo-handles, RTLD_DEFAULT and RTLD_NEXT. The former will find the first occurrence of the desired symbol using the default library search order. The latter will find the next occurrence of a function in the search order after the current library. This allows one to provide a wrapper around a function in another shared library.


     :example --- void (*add)(int x, int y);
         add = dlsym("xxx.so", "add");//第一参数是否有点问题?
         add(89,43);


     int dlclose(void *handle);
     :dlclose用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。
     :The function dlclose() decrements the reference count on the dynamic library handle parameter handle. If the reference count drops to zero and no other loaded libraries use symbols in it, then the dynamic library is unloaded.
     :The function dlclose() returns 0 on success, and non-zero on error.


     The obsolete symbols _init and _fini
     :函数原型 void _init(void);
                       void _fini(void);
     :The linker recoginizes special symbols _init and _fini. If a dynamic library exports a routine named _init, then that code is executed after the loading, before dlopen() returns. If the dynamic library exports a routine named _fini, then that routine is called just before the library is unloader. In case you need to avoid linking against the system startup files, this can be done by giving gcc the "-nostartfiles" parameter on the command line.
      当用gcc编译源程序为".o"文件的时候,需要加一个"-nostartfiles"选项。这个选项使得C编译器不链接系统的启动函数库里面的启动函数。否则,就会有一个"multiple-definiton"的错误。
     :Using these routines, or the gcc -nostartfiles of -nostdlib options, is not recommended. Their use may result in undesired behavior, since the constructor/destructor routines will not be executed (unless special measures are taken).
     :Instead, libraries should export routines using the __attribute__((constructor)) and __attribute__((destructor)) funtion attributes. Constructor routines are executed before dlopen() returns, and destructor routines
      are executed before dlclose() returns.
     

     "如果主程序使用dlopen打开共享库A和B,缺省情况下A与B彼此不能引用对方定义的动态符号。但是可以在dlopen第二个参数上逻辑或一个RTLD_GLOBAL,使得相应共享库中动态符号全局可见"

     
    Example:

     #include <stdio.h>
     #include <stdlib.h>
     #include <dlfcn.h>
     
     int main(int argc, char *argv[])
     {
      void *handle;
      double (*cosine)(double); //define a funtion pointer
      char *error;
      handle = dlopen("libm.so", RTLD_LAZY);
      if(!handle)
      {
       fprintf(stderr, "%s\n", dlerror()); //return error reason and clear existing error
       exit(1);
      }
      dlerror(); //clear any existing error
      cosine = dlsym(handle, "cos");
      if((error = dlerror()) != NULL)
      {
       fprintf(stderr, "%s\n", error);
       exit(1);
      }
      printf("%f\n", (*cosine)(2.0));
      dlclose(handle);
      
      return 0;
     }
     
     
     :If this program were in a file named "foo.c", you would build the program with the following command:
     
      gcc -rdynamic -o foo foo.c -ldl
      
     :Libraries exporting _init() and _fini() will want to be compiled as follows, using bar.c as the example
      name:
      
      gcc -shared -nostartfiles -o bar bar.c
     

     
    //****************************************************************************************************************

    动态链接库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$'

  • 相关阅读:
    团队-爬取豆瓣电影TOP250-成员简介及分工
    结对-贪吃蛇游戏-需求分析
    个人编程作业
    团队项目编程作业
    课后作业-阅读任务-阅读提问-1
    团队-爬虫豆瓣电影TOP250-成员简介及分工
    结对-英文词频检测程序-需求分析
    对软件工程课程的期望
    自我介绍
    作业0
  • 原文地址:https://www.cnblogs.com/avril/p/1736524.html
Copyright © 2011-2022 走看看