精华内容
下载资源
问答
  • Linux动态链接库 so文件的创建与使用

    千次阅读 2019-02-17 13:45:02
    Linux动态链接库 so文件的创建与使用
                    1. 介绍

            使用GNU的工具我们如何在Linux下创建自己的程序函数库?一个“程序函数库”简单的说就是一个文件包含了一些编译好的代码和数据,这些编译好的代码和数据可以在事后供其他的程序使用。程序函数库可以使整个程序更加模块化,更容易重新编译,而且更方便升级。  

    程序函数库可分为3种类型:静态函数库(static libraries)、共享函数库(shared libraries)、动态加载函数库(dynamically loaded libraries): 

    1、静态函数库,是在程序执行前就加入到目标程序中去了;

    2、共享函数库,则是在程序启动的时候加载到程序中,它可以被不同的程序共享;动态加载函数库则可以在程序运行的任何时候动态的加载。

    3、动态函数库,并非另外一种库函数格式,区别是动态加载函数库是如何被程序员使用的。

     

    2. 静态函数库

            静态函数库实际上就是简单的一个普通的目标文件的集合,一般来说习惯用“.a”作为文件的后缀。可以用ar这个程序来产生静态函数库文件。Ar是archiver的缩写。静态函数库现在已经不在像以前用得那么多了,主要是共享函数库与之相比较有很多的优势的原因。慢慢地,大家都喜欢使用共享函数库了。不过,在一些场所静态函数库仍然在使用,一来是保持一些与以前某些程序的兼容,二来它描述起来也比较简单。 

            静态库函数允许程序员把程序link起来而不用重新编译代码,节省了重新编译代码的时间。不过,在今天这么快速的计算机面前,一般的程序的重新编译也花费不了多少时间,所以这个优势已经不是像它以前那么明显了。静态函数库对开发者来说还是很有用的,例如你想把自己提供的函数给别人使用,但是又想对函数的源代码进行保密,你就可以给别人提供一个静态函数库文件。理论上说,使用ELF格式的静态库函数生成的代码可以比使用共享函数库(或者动态函数库)的程序运行速度上快一些,大概1-5%。 

    创建一个静态函数库文件,或者往一个已经存在地静态函数库文件添加新的目标代码,可以用下面的命令: 

            ar rcs my_library.a file1.o file2.o 

    这个例子中是把目标代码file1.o和file2.o加入到my_library.a这个函数库文件中,如果my_library.a不存在则创建一个新的文件。在用ar命令创建静态库函数的时候,还有其他一些可以选择的参数,可以参加ar的使用帮助。这里不再赘述。

    一旦你创建了一个静态函数库,你可以使用它了。你可以把它作为你编译和连接过程中的一部分用来生成你的可执行代码。如果你用gcc来编译产生可执行代码的话,你可以用“-l”参数来指定这个库函数。你也可以用ld来做,使用它的“-l”和“-L”参数选项。具体用法可以参考info:gcc。 

     

    3. 共享函数库

    共享函数库中的函数是在当一个可执行程序在启动的时候被加载。如果一个共享函数库正常安装,所有的程序在重新运行的时候都可以自动加载最新的函数库中的函数。对于Linux系统还有更多可以实现的功能: 
            1、升级了函数库但是仍然允许程序使用老版本的函数库。
            2、当执行某个特定程序的时候可以覆盖某个特定的库或者库中指定的函数。
            3、可以在库函数被使用的过程中修改这些函数库。

    3.1. 一些约定
    如果你要编写的共享函数库支持所有有用的特性,你在编写的过程中必须遵循一系列约定。你必须理解库的不同的名字间的区别,例如它的“soname”和“real name”之间的区别和它们是如何相互作用的。你同样还要知道你应该把这些库函数放在你文件系统的什么位置等等。下面我们具体看看这些问题。 

    3.1.1. 共享库的命名

    每个共享函数库都有个特殊的名字,称作“soname”。soname名字命名必须以“lib”作为前缀,然后是函数库的名字,然后是“.so”,最后是版本号信息。不过有个特例,就是非常底层的C库函数都不是以lib开头这样命名的。
        每个共享函数库都有一个真正的名字(“real name”),它是包含真正库函数代码的文件。真名有一个主版本号,和一个发行版本号。最后一个发行版本号是可选的,可以没有。主版本号和发行版本号使你可以知道你到底是安装了什么版本的库函数。另外,还有一个名字是编译器编译的时候需要的函数库的名字,这个名字就是简单的soname名字,而不包含任何版本号信息。

    管理共享函数库的关键是区分好这些名字。当可执行程序需要在自己的程序中列出这些他们需要的共享库函数的时候,它只要用soname就可以了;反过来,当你要创建一个新的共享函数库的时候,你要指定一个特定的文件名,其中包含很细节的版本信息。当你安装一个新版本的函数库的时候,你只要先将这些函数库文件拷贝到一些特定的目录中,运行ldconfig这个实用就可以。ldconfig检查已经存在的库文件,然后创建soname的符号链接到真正的函数库,同时设置/etc/ld.so.cache这个缓冲文件。这个我们稍后再讨论。

    ldconfig并不设置链接的名字,通常的做法是在安装过程中完成这个链接名字的建立,一般来说这个符号链接就简单的指向最新的soname或者最新版本的函数库文件。最好把这个符号链接指向soname,因为通常当你升级你的库函数后,你就可以自动使用新版本的函数库类。

    我们来举例看看:/usr/lib/libreadline.so.3 是一个完全的完整的soname,ldconfig可以设置一个符号链接到其他某个真正的函数库文件,例如是/usr/lib/libreadline.so.3.0。同时还必须有一个链接名字,例如 /usr/lib/libreadline.so就是一个符号链接指向/usr/lib/libreadline.so.3。

    3.1.2. 文件系统中函数库文件的位置

    共享函数库文件必须放在一些特定的目录里,这样通过系统的环境变量设置,应用程序才能正确的使用这些函数库。大部分的源码开发的程序都遵循GNU的一些标准,我们可以看info帮助文件获得相信的说明,info信息的位置是:info:standards#Directory_Variables。GNU标准建议所有的函数库文件都放在/usr/local/lib目录下,而且建议命令可执行程序都放在/usr/local/bin目录下。这都是一些习惯问题,可以改变的。 

    文件系统层次化标准FHS(Filesystem Hierarchy Standard)(http://www.pathname.com/fhs)规定了在一个发行包中大部分的函数库文件应该安装到/usr/lib目录下,但是如果某些库是在系统启动的时候要加载的,则放到/lib目录下,而那些不是系统本身一部分的库则放到/usr/local/lib下面。 

    上面两个路径的不同并没有本质的冲突。GNU提出的标准主要对于开发者开发源码的,而FHS的建议则是针对发行版本的路径的。具体的位置信息可以看/etc/ld.so.conf里面的配置信息。

    3.2. 这些函数库如何使用

    在基于GNU glibc的系统里,包括所有的linux系统,启动一个ELF格式的二进制可执行文件会自动启动和运行一个program loader。对于Linux系统,这个loader的名字是/lib/ld-linux.so.X(X是版本号)。这个loader启动后,反过来就会load所有的其他本程序要使用的共享函数库。

    到底在哪些目录里查找共享函数库呢?这些定义缺省的是放在/etc/ld.so.conf文件里面,我们可以修改这个文件,加入我们自己的一些特殊的路径要求。大多数RedHat系列的发行包的/etc/ld.so.conf文件里面不包括/usr/local/lib这个目录,如果没有这个目录的话,我们可以修改/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。 

    另外,FreeBSD使用cache的文件不一样。FreeBSD的ELF cache是/var/run/ld-elf.so.hints,而a.out的cache则是/var/run/ld.so.hints。它们同样是通过ldconfig来更新。

    3.3. 环境变量

    各种各样的环境变量控制着一些关键的过程。例如你可以临时为你特定的程序的一次执行指定一个不同的函数库。Linux系统中,通常变量LD_LIBRARY_PATH就是可以用来指定函数库查找路径的,而且这个路径通常是在查找标准的路径之前查找。这个是很有用的,特别是在调试一个新的函数库的时候,或者在特殊的场合使用一个非标准的函数库的时候。环境变量LD_PRELOAD列出了所有共享函数库中需要优先加载的库文件,功能和/etc/ld.so.preload类似。这些都是有/lib/ld-linux.so这个loader来实现的。值得一提的是,LD_LIBRARY_PATH可以在大部分的UNIX-linke系统下正常起作用,但是并非所有的系统下都可以使用,例如HP-UX系统下,就是用SHLIB_PATH这个变量,而在AIX下则使用LIBPATH这个变量。

    LD_LIBRARY_PATH在开发和调试过程中经常大量使用,但是不应该被一个普通用户在安装过程中被安装程序修改,大家可以去参考http://www.visi.com/~barr/ldpath.html,这里有一个文档专门介绍为什么不使用LD_LIBRARY_PATH这个变量。

    事实上还有更多的环境变量影响着程序的调入过程,它们的名字通常就是以LD_或者RTLD_打头。大部分这些环境变量的使用的文档都是不全,通常搞得人头昏眼花的,如果要真正弄清楚它们的用法,最好去读loader的源码(也就是gcc的一部分)。

    允许用户控制动态链接函数库将涉及到setuid/setgid这个函数,如果特殊的功能需要的话。因此,GNU loader通常限制或者忽略用户对这些变量使用setuid和setgid。如果loader通过判断程序的相关环境变量判断程序的是否使用了setuid或者setgid,如果uid和euid不同,或者gid和egid部一样,那么loader就假定程序已经使用了setuid或者setgid,然后就大大的限制器控制这个老链接的权限。如果阅读GNU glibc的库函数源码,就可以清楚地看到这一点。特别的我们可以看elf/rtld.c和sysdeps/generic/dl-sysdep.c这两个文件。这就意味着如果你使得uid和gid与euid和egid分别相等,然后调用一个程序,那么这些变量就可以完全起效。

    3.4. 创建一个共享函数库

    现在我们开始学习如何创建一个共享函数库。其实创建一个共享函数库非常容易。首先创建object文件,这个文件将加入通过gcc –fPIC参数命令加入到共享函数库里面。PIC的意思是“位置无关代码”(Position Independent Code)。下面是一个标准的格式:

            gcc -shared -Wl,-soname,your_soname -o library_name file_list library_list

    下面再给一个例子,它创建两个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

    下面是一些需要注意的地方:

    不用使用-fomit-frame-pointer这个编译参数除非你不得不这样。虽然使用了这个参数获得的函数库仍然可以使用,但是这使得调试程序几乎没有用,无法跟踪调试。

    使用-fPIC来产生代码,而不是-fpic。

    某些情况下,使用gcc 来生成object文件,需要使用“-Wl,-export-dynamic”这个选项参数。 

    通常,动态函数库的符号表里面包含了这些动态的对象的符号。这个选项在创建ELF格式的文件时候,会将所有的符号加入到动态符号表中。可以参考ld的帮助获得更详细的说明。

    3.5. 安装和使用共享函数库

    一旦你定义了一个共享函数库,你还需要安装它。其实简单的方法就是拷贝你的库文件到指定的标准的目录(例如/usr/lib),然后运行ldconfig。

    如果你没有权限去做这件事情,例如你不能修改/usr/lib目录,那么你就只好通过修改你的环境变量来实现这些函数库的使用了。首先,你需要创建这些共享函数库;然后,设置一些必须得符号链接,特别是从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”脚本来设置这个库函数和相关的东西。例如下面的例子:

            #!/bin/sh export LD_LIBRARY_PATH=/usr/local/my_lib,$LD_LIBRARY_PATH

            exec /usr/bin/my_program.orig $*

    我们可以通过运行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),然后运行这个程序。这样就有可能使得某地程序可能使得ldd来执行某些意想不到的代码,而产生不安全的隐患。

    3.6. 不兼容的函数库

    如果一个新版的函数库要和老版本的二进制的库不兼容,则soname需要改变。对于C语言,一共有4个基本的理由使得它们在二进制代码上很难兼容:

    一个函数的行文改变了,这样它就可能与最开始的定义不相符合。

    ·          输出的数据项改变了。

    ·          某些输出的函数删除了。

    ·          某些输出函数的接口改变了。
        如果你能避免这些地方,你就可以保持你的函数库在二进制代码上的兼容,或者说,你可以使得你的程序的应用二进制接口(ABI:Application Binary Interface)上兼容。

     

    4. 动态加载的函数库Dynamically Loaded (DL) Libraries

     

    动态加载的函数库Dynamically loaded (DL) libraries是一类函数库,它可以在程序运行过程中的任何时间加载。它们特别适合在函数中加载一些模块和plugin扩展模块的场合,因为它可以在当程序需要某个plugin模块时才动态的加载。例如,Pluggable Authentication Modules(PAM)系统就是用动态加载函数库来使得管理员可以配置和重新配置身份验证信息。

    Linux系统下,DL函数库与其他函数库在格式上没有特殊的区别,我们前面提到过,它们创建的时候是标准的object格式。主要的区别就是这些函数库不是在程序链接的时候或者启动的时候加载,而是通过一个API来打开一个函数库,寻找符号表,处理错误和关闭函数库。通常C语言环境下,需要包含这个头文件。 
            Linux中使用的函数和Solaris中一样,都是dlpoen() API。当然不是所有的平台都使用同样的接口,例如HP-UX使用shl_load()机制,而Windows平台用另外的其他的调用接口。如果你的目的是使得你的代码有很强的移植性,你应该使用一些wrapping函数库,这样的wrapping函数库隐藏不同的平台的接口区别。一种方法是使用glibc函数库中的对动态加载模块的支持,它使用一些潜在的动态加载函数库界面使得它们可以夸平台使用。具体可以参考http://developer.gnome.org/doc/API/glib/glib-dynamic-loading-of-modules.html. 另外一个方法是使用libltdl,是GNU libtool的一部分,可以进一步参考CORBA相关资料。  

    4.1. dlopen()
    dlopen函数打开一个函数库然后为后面的使用做准备。C语言原形是:

            void * dlopen(const char *filename, int flag);

    如果文件名filename是以“/”开头,也就是使用绝对路径,那么dlopne就直接使用它,而不去查找某些环境变量或者系统设置的函数库所在的目录了。否则dlopen()就会按照下面的次序查找函数库文件:
           1. 环境变量LD_LIBRARY指明的路径。

    2. /etc/ld.so.cache中的函数库列表。

    3. /lib目录,然后/usr/lib。不过一些很老的a.out的loader则是采用相反的次序,也就是先查 /usr/lib,然后是/lib。
        dlopen()函数中,参数flag的值必须是RTLD_LAZY或者RTLD_NOW,RTLD_LAZY的意思是resolve undefined symbols as code from the dynamic library is executed,而RTLD_NOW的含义是resolve all undefined symbols before dlopen() returns and fail if this cannot be done'。
        如果有好几个函数库,它们之间有一些依赖关系的话,例如X依赖Y,那么你就要先加载那些被依赖的函数。例如先加载Y,然后加载X。

        dlopen()函数的返回值是一个句柄,然后后面的函数就通过使用这个句柄来做进一步的操作。如果打开失败dlopen()就返回一个NULL。如果一个函数库被多次打开,它会返回同样的句柄。 
        如果一个函数库里面有一个输出的函数名字为_init,那么_init就会在dlopen()这个函数返回前被执行。我们可以利用这个函数在我的函数库里面做一些初始化的工作。我们后面会继续讨论这个问题的。  
    4.2. dlerror()

    通过调用dlerror()函数,我们可以获得最后一次调用dlopen(),dlsym(),或者dlclose()的错误信息。 
    4.3. dlsym()

    如果你加载了一个DL函数库而不去使用当然是不可能的了,使用一个DL函数库的最主要的一个函数就是dlsym(),这个函数在一个已经打开的函数库里面查找给定的符号。这个函数如下定义:

            void * dlsym(void *handle, char *symbol);

    函数中的参数handle就是由dlopen打开后返回的句柄,symbol是一个以NIL结尾的字符串。如果dlsym()函数没有找到需要查找的symbol,则返回NULL。如果你知道某个symbol的值不可能是NULL或者0,那么就很好,你就可以根据这个返回结果判断查找的symbol是否存在了;不过,如果某个symbol的值就是NULL,那么这个判断就有问题了。标准的判断方法是先调用dlerror(),清除以前可能存在的错误,然后调用dlsym()来访问一个symbol,然后再调用dlerror()来判断是否出现了错误。一个典型的过程如下:

     dlerror();  /*clear error code */ s = (actual_type)dlsym(handle, symbol_being_searched_for); if((error = dlerror()) != NULL){  /* handle error, the symbol wasn't found */ } else {  /* symbol found, its value is in s */ }

    4.4. dlclose()

    dlopen()函数的反过程就是dlclose()函数,dlclose()函数用力关闭一个DL函数库。Dl函数库维持一个资源利用的计数器,当调用dlclose的时候,就把这个计数器的计数减一,如果计数器为0,则真正的释放掉。真正释放的时候,如果函数库里面有_fini()这个函数,则自动调用_fini()这个函数,做一些必要的处理。Dlclose()返回0表示成功,其他非0值表示错误。

    4.5. DL Library Example

    下面是一个例子。例子中调入math函数库,然后打印2.0的余弦函数值。例子中每次都检查是否出错。应该是个不错的范例:

    int main(int argc, char *argv){  void *handle;  char *error;    double (*cosine )(double);  handle = dlopen("/lib/libm.so.6", RTLD_LAZY);  if(!handle){   fputs(dlerror(), stderr);    exit(1);  }    cosine = dlsym(handle, "cos");  if((error = dlerror()) != NULL){   fputs(error, stderr);   exit(1);  }    printf("%f", (*cosine)(2, 0));    dlclose(handle);    return 0;}

    如果这个程序名字叫foo.c,那么用下面的命令来编译:

            gcc -o foo foo.c –ldl

     

     

    参考推荐:

    Linux动态链接库.so文件的创建与使用

    Linux动态库(.so)搜索路径

    Linux 动态库与静态库制作及使用详解

     

               

    再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

    展开全文
  • 在/etc/ld.so.conf.d/下创建xxx.conf,在文本中加入.so所在路径,如:/usr/xxx等等,然后使用相应ldconfig命令使之生效。 将.so所在路径添加为PATH环境变量。 在编译命令中使用-Wl,-rpath=./参数,并将相应.so拷贝...
    1. /etc/ld.so.conf.d/下创建xxx.conf,在文本中加入.so所在路径,如:/usr/xxx等等,然后使用相应ldconfig命令使之生效。
    2. 将.so所在路径添加为LD_LIBRARY_PATH环境变量。
    3. 在编译命令中使用-Wl,-rpath=./参数,并将相应.so拷贝到执行目录;当然也可以将‘./’指定为其他目录。

    个人喜欢第一种,比较灵活,容易管理。

    注意:-L参数添加的lib搜索目录只用于编译时,运行时需使用上述方法之一,否则会发生运行时调用.so错误。

    展开全文
  • linux创建和使用动态链接库.so文件
  • 我们在linux下开发项目,有时会对外提供动态库,像***.so.1.0.0这样子的文件,另外提供相应的头文件。用户拿到动态库和头文件说明,就可以使用动态库里的function。 那随之而来的一个问题是,动态库的升级问题,我们...

    转载:
    https://blog.csdn.net/zhanglianpin/article/details/50491958

    我们在linux下开发项目,有时会对外提供动态库,像***.so.1.0.0这样子的文件,另外提供相应的头文件。用户拿到动态库和头文件说明,就可以使用动态库里的function。

    那随之而来的一个问题是,动态库的升级问题,我们的动态库更改了一个bug,升级了一个版本,那使用我们动态库的应用程序需要重新编译吗?运行时会产生异常吗?linux下是怎么规范这些内容的呐?

    大家一定听说过windows下的dll hell

    Linux中的.so文件 是动态链接的产物
    共享库理解为提供各种功能函数的集合,对外提供标准的接口
    Linux中命名系统中共享库的规则

    在这里插入图片描述

    主版本号:不同的版本号之间不兼容
    次版本号:增量升级 向后兼容
    发行版本号:对应次版本的错误修正和性能提升,不影响兼容性

    下面说说linux下动态库的命名规范。

    为方便管理依赖关系,创建或部署共享库时,必须遵循统一约定的规则才行,其中包括动态库的命名规则及其部署方式。

    共享库命名约定

    1. 每个动态库有一个包含了真正的库代码的文件名,通常被称为库的 realname ,命名格式通常为
      libxxx.so.x.y.z,其中so后缀中的x为主版本号,y为副版本号,z为发行版本号。例如,我的linux系统机器上zlib共享库的realname为 libz.so.1.2.8,这个文件是含有可执行的二进制代码的。

    2. 每个动态库都有一个以"lib"为前缀且以".so.x"为结尾的被称为 soname
      的特定名称,其中x为主版本号,soname命名格式通常为libxxx.so.x。例如,我的linux系统机器上zlib共享库的soname为libz.so.1。这个soname包含了动态库的主版本号,这个doname一般会包含在库代码的头文件中,这个可以使用 readelf -d 读取出来,使用这个动态库的程序的二进制ELF的头文件中包含有这个动态库的soname。程序运行时会按照这个名称去找真正的库文件。

    3. 此外,编译链依赖了共享库的应用模块时,链接器只认不带任何版本号的共享库名, 可以将库名称作" linker name。
      例如,我的linux系统机器上zlib共享库的linkername为libz.so。也即,链接使用了动态库的程序时查找的动态库名称。例如:gcc -o test test.o -lz , 链接时就会找libz.so 。若没有这个文件,链接器就报错。

    下面的内容转自:http://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html

    以实际例子的形式,详细地阐述了realname soname linkername 之间的关系。

    1. Shared Libraries
      Shared libraries are libraries that are loaded by programs when they start. When a shared library is installed properly, all programs that start afterwards automatically use the new shared library. It’s actually much more flexible and sophisticated than this, because the approach used by Linux permits you to:

    update libraries and still support programs that want to use older, non-backward-compatible versions of those libraries;

    override specific libraries or even specific functions in a library when executing a particular program.

    do all this while programs are running using existing libraries.

    3.1. Conventions
    For shared libraries to support all of these desired properties, a number of conventions and guidelines must be followed. You need to understand the difference between a library’s names, in particular its soname'' andreal name’’ (and how they interact). You also need to understand where they should be placed in the filesystem.

    3.1.1. Shared Library Names
    Every shared library has a special name called the soname''. The soname has the prefixlib’’, the name of the library, the phrase .so'', followed by a period and a version number that is incremented whenever the interface changes (as a special exception, the lowest-level C libraries don't start withlib’’). A fully-qualified soname includes as a prefix the directory it’s in; on a working system a fully-qualified soname is simply a symbolic link to the shared library’s ``real name’’.

    Every shared library also has a ``real name’’, which is the filename containing the actual library code. The real name adds to the soname a period, a minor number, another period, and the release number. The last period and release number are optional. The minor number and release number support configuration control by letting you know exactly what version(s) of the library are installed. Note that these numbers might not be the same as the numbers used to describe the library in documentation, although that does make things easier.

    In addition, there’s the name that the compiler uses when requesting a library, (I’ll call it the ``linker name’’), which is simply the soname without any version number.

    The key to managing shared libraries is the separation of these names. Programs, when they internally list the shared libraries they need, should only list the soname they need. Conversely, when you create a shared library, you only create the library with a specific filename (with more detailed version information). When you install a new version of a library, you install it in one of a few special directories and then run the program ldconfig(8). ldconfig examines the existing files and creates the sonames as symbolic links to the real names, as well as setting up the cache file /etc/ld.so.cache (described in a moment).

    ldconfig doesn’t set up the linker names; typically this is done during library installation, and the linker name is simply created as a symbolic link to the ``latest’’ soname or the latest real name. I would recommend having the linker name be a symbolic link to the soname, since in most cases if you update the library you’d like to automatically use it when linking. I asked H. J. Lu why ldconfig doesn’t automatically set up the linker names. His explanation was basically that you might want to run code using the latest version of a library, but might instead want development to link against an old (possibly incompatible) library. Therefore, ldconfig makes no assumptions about what you want programs to link to, so installers must specifically modify symbolic links to update what the linker will use for a library.

    Thus, /usr/lib/libreadline.so.3 is a fully-qualified soname, which ldconfig would set to be a symbolic link to some realname like /usr/lib/libreadline.so.3.0. There should also be a linker name, /usr/lib/libreadline.so which could be a symbolic link referring to /usr/lib/libreadline.so.3.

    3.1.2. Filesystem Placement
    Shared libraries must be placed somewhere in the filesystem. Most open source software tends to follow the GNU standards; for more information see the info file documentation at info:standards#Directory_Variables. The GNU standards recommend installing by default all libraries in /usr/local/lib when distributing source code (and all commands should go into /usr/local/bin). They also define the convention for overriding these defaults and for invoking the installation routines.

    The Filesystem Hierarchy Standard (FHS) discusses what should go where in a distribution (see http://www.pathname.com/fhs). According to the FHS, most libraries should be installed in /usr/lib, but libraries required for startup should be in /lib and libraries that are not part of the system should be in /usr/local/lib.

    There isn’t really a conflict between these two documents; the GNU standards recommend the default for developers of source code, while the FHS recommends the default for distributors (who selectively override the source code defaults, usually via the system’s package management system). In practice this works nicely: the latest'' (possibly buggy!) source code that you download automatically installs itself in thelocal’’ directory (/usr/local), and once that code has matured the package managers can trivially override the default to place the code in the standard place for distributions. Note that if your library calls programs that can only be called via libraries, you should place those programs in /usr/local/libexec (which becomes /usr/libexec in a distribution). One complication is that Red Hat-derived systems don’t include /usr/local/lib by default in their search for libraries; see the discussion below about /etc/ld.so.conf. Other standard library locations include /usr/X11R6/lib for X-windows. Note that /lib/security is used for PAM modules, but those are usually loaded as DL libraries (also discussed below).

    3.2. How Libraries are Used
    On GNU glibc-based systems, including all Linux systems, starting up an ELF binary executable automatically causes the program loader to be loaded and run. On Linux systems, this loader is named /lib/ld-linux.so.X (where X is a version number). This loader, in turn, finds and loads all other shared libraries used by the program.

    The list of directories to be searched is stored in the file /etc/ld.so.conf. Many Red Hat-derived distributions don’t normally include /usr/local/lib in the file /etc/ld.so.conf. I consider this a bug, and adding /usr/local/lib to /etc/ld.so.conf is a common ``fix’’ required to run many programs on Red Hat-derived systems.

    If you want to just override a few functions in a library, but keep the rest of the library, you can enter the names of overriding libraries (.o files) in /etc/ld.so.preload; these ``preloading’’ libraries will take precedence over the standard set. This preloading file is typically used for emergency patches; a distribution usually won’t include such a file when delivered.

    Searching all of these directories at program start-up would be grossly inefficient, so a caching arrangement is actually used. The program ldconfig(8) by default reads in the file /etc/ld.so.conf, sets up the appropriate symbolic links in the dynamic link directories (so they’ll follow the standard conventions), and then writes a cache to /etc/ld.so.cache that’s then used by other programs. This greatly speeds up access to libraries. The implication is that ldconfig must be run whenever a DLL is added, when a DLL is removed, or when the set of DLL directories changes; running ldconfig is often one of the steps performed by package managers when installing a library. On start-up, then, the dynamic loader actually uses the file /etc/ld.so.cache and then loads the libraries it needs.

    By the way, FreeBSD uses slightly different filenames for this cache. In FreeBSD, the ELF cache is /var/run/ld-elf.so.hints and the a.out cache is /var/run/ld.so.hints. These are still updated by ldconfig(8), so this difference in location should only matter in a few exotic situations.

    3.3. Environment Variables
    Various environment variables can control this process, and there are environment variables that permit you to override this process.

    3.3.1. LD_LIBRARY_PATH
    You can temporarily substitute a different library for this particular execution. In Linux, the environment variable LD_LIBRARY_PATH is a colon-separated set of directories where libraries should be searched for first, before the standard set of directories; this is useful when debugging a new library or using a nonstandard library for special purposes. The environment variable LD_PRELOAD lists shared libraries with functions that override the standard set, just as /etc/ld.so.preload does. These are implemented by the loader /lib/ld-linux.so. I should note that, while LD_LIBRARY_PATH works on many Unix-like systems, it doesn’t work on all; for example, this functionality is available on HP-UX but as the environment variable SHLIB_PATH, and on AIX this functionality is through the variable LIBPATH (with the same syntax, a colon-separated list).

    LD_LIBRARY_PATH is handy for development and testing, but shouldn’t be modified by an installation process for normal use by normal users; see ``Why LD_LIBRARY_PATH is Bad’’ at http://www.visi.com/~barr/ldpath.html for an explanation of why. But it’s still useful for development or testing, and for working around problems that can’t be worked around otherwise. If you don’t want to set the LD_LIBRARY_PATH environment variable, on Linux you can even invoke the program loader directly and pass it arguments. For example, the following will use the given PATH instead of the content of the environment variable LD_LIBRARY_PATH, and run the given executable:

    /lib/ld-linux.so.2 --library-path PATH EXECUTABLE
    Just executing ld-linux.so without arguments will give you more help on using this, but again, don’t use this for normal use - these are all intended for debugging.

    3.3.2. LD_DEBUG
    Another useful environment variable in the GNU C loader is LD_DEBUG. This triggers the dl* functions so that they give quite verbose information on what they are doing. For example:

    export LD_DEBUG=files
    command_to_run
    displays the processing of files and libraries when handling libraries, telling you what dependencies are detected and which SOs are loaded in what order. Setting LD_DEBUG to bindings'' displays information about symbol binding, setting it tolibs’’ displays the library search paths, and setting ti to ``versions’’ displays the version depdendencies.

    Setting LD_DEBUG to ``help’’ and then trying to run a program will list the possible options. Again, LD_DEBUG isn’t intended for normal use, but it can be handy when debugging and testing.

    3.3.3. Other Environment Variables
    There are actually a number of other environment variables that control the loading process; their names begin with LD_ or RTLD_. Most of the others are for low-level debugging of the loader process or for implementing specialized capabilities. Most of them aren’t well-documented; if you need to know about them, the best way to learn about them is to read the source code of the loader (part of gcc).

    Permitting user control over dynamically linked libraries would be disastrous for setuid/setgid programs if special measures weren’t taken. Therefore, in the GNU loader (which loads the rest of the program on program start-up), if the program is setuid or setgid these variables (and other similar variables) are ignored or greatly limited in what they can do. The loader determines if a program is setuid or setgid by checking the program’s credentials; if the uid and euid differ, or the gid and the egid differ, the loader presumes the program is setuid/setgid (or descended from one) and therefore greatly limits its abilities to control linking. If you read the GNU glibc library source code, you can see this; see especially the files elf/rtld.c and sysdeps/generic/dl-sysdep.c. This means that if you cause the uid and gid to equal the euid and egid, and then call a program, these variables will have full effect. Other Unix-like systems handle the situation differently but for the same reason: a setuid/setgid program should not be unduly affected by the environment variables set.

    3.4. Creating a Shared Library
    Creating a shared library is easy. First, create the object files that will go into the shared library using the gcc -fPIC or -fpic flag. The -fPIC and -fpic options enable ``position independent code’’ generation, a requirement for shared libraries; see below for the differences. You pass the soname using the -Wl gcc option. The -Wl option passes options along to the linker (in this case the -soname linker option) - the commas after -Wl are not a typo, and you must not include unescaped whitespace in the option. Then create the shared library using this format:

    gcc -shared -Wl,-soname,your_soname
    -o library_name file_list library_list
    Here’s an example, which creates two object files (a.o and b.o) and then creates a shared library that contains both of them. Note that this compilation includes debugging information (-g) and will generate warnings (-Wall), which aren’t required for shared libraries but are recommended. The compilation generates object files (using -c), and includes the required -fPIC option:

    gcc -fPIC -g -c -Wall a.c
    gcc -fPIC -g -c -Wall b.c
    gcc -shared -Wl,-soname,libmystuff.so.1
    -o libmystuff.so.1.0.1 a.o b.o -lc
    Here are a few points worth noting:

    Don’t strip the resulting library, and don’t use the compiler option -fomit-frame-pointer unless you really have to. The resulting library will work, but these actions make debuggers mostly useless.

    Use -fPIC or -fpic to generate code. Whether to use -fPIC or -fpic to generate code is target-dependent. The -fPIC choice always works, but may produce larger code than -fpic (mnenomic to remember this is that PIC is in a larger case, so it may produce larger amounts of code). Using -fpic option usually generates smaller and faster code, but will have platform-dependent limitations, such as the number of globally visible symbols or the size of the code. The linker will tell you whether it fits when you create the shared library. When in doubt, I choose -fPIC, because it always works.

    In some cases, the call to gcc to create the object file will also need to include the option -Wl,-export-dynamic''. Normally, the dynamic symbol table contains only symbols which are used by a dynamic object. This option (when creating an ELF file) adds all symbols to the dynamic symbol table (see ld(1) for more information). You need to use this option when there are 'reverse dependencies', i.e., a DL library has unresolved symbols that by convention must be defined in the programs that intend to load these libraries. Forreverse dependencies’’ to work, the master program must make its symbols dynamically available. Note that you could say -rdynamic'' instead of-Wl,export-dynamic’’ if you only work with Linux systems, but according to the ELF documentation the ``-rdynamic’’ flag doesn’t always work for gcc on non-Linux systems.

    During development, there’s the potential problem of modifying a library that’s also used by many other programs – and you don’t want the other programs to use the developmental''library, only a particular application that you're testing against it. One link option you might use is ld'srpath’’ option, which specifies the runtime library search path of that particular program being compiled. From gcc, you can invoke the rpath option by specifying it this way:

    -Wl,-rpath,$(DEFAULT_LIB_INSTALL_PATH)
    If you use this option when building the library client program, you don’t need to bother with LD_LIBRARY_PATH (described next) other than to ensure it’s not conflicting, or using other techniques to hide the library.

    3.5. Installing and Using a Shared Library
    Once you’ve created a shared library, you’ll want to install it. The simple approach is simply to copy the library into one of the standard directories (e.g., /usr/lib) and run ldconfig(8).

    First, you’ll need to create the shared libraries somewhere. Then, you’ll need to set up the necessary symbolic links, in particular a link from a soname to the real name (as well as from a versionless soname, that is, a soname that ends in ``.so’’ for users who don’t specify a version at all). The simplest approach is to run:

    ldconfig -n directory_with_shared_libraries
    Finally, when you compile your programs, you’ll need to tell the linker about any static and shared libraries that you’re using. Use the -l and -L options for this.

    If you can’t or don’t want to install a library in a standard place (e.g., you don’t have the right to modify /usr/lib), then you’ll need to change your approach. In that case, you’ll need to install it somewhere, and then give your program enough information so the program can find the library… and there are several ways to do that. You can use gcc’s -L flag in simple cases. You can use the rpath'' approach (described above), particularly if you only have a specific program to use the library being placed in anon-standard’’ place. You can also use environment variables to control things. In particular, you can set LD_LIBRARY_PATH, which is a colon-separated list of directories in which to search for shared libraries before the usual places. If you’re using bash, you could invoke my_program this way using:

    LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH my_program
    If you want to override just a few selected functions, you can do this by creating an overriding object file and setting LD_PRELOAD; the functions in this object file will override just those functions (leaving others as they were).

    Usually you can update libraries without concern; if there was an API change, the library creator is supposed to change the soname. That way, multiple libraries can be on a single system, and the right one is selected for each program. However, if a program breaks on an update to a library that kept the same soname, you can force it to use the older library version by copying the old library back somewhere, renaming the program (say to the old name plus .orig''), and then create a smallwrapper’’ script that resets the library to use and calls the real (renamed) program. You could place the old library in its own special area, if you like, though the numbering conventions do permit multiple versions to live in the same directory. The wrapper script could look something like this:

    #!/bin/sh
    export LD_LIBRARY_PATH=/usr/local/my_lib:$LD_LIBRARY_PATH
    exec /usr/bin/my_program.orig $*
    Please don’t depend on this when you write your own programs; try to make sure that your libraries are either backwards-compatible or that you’ve incremented the version number in the soname every time you make an incompatible change. This is just an ``emergency’’ approach to deal with worst-case problems.

    You can see the list of the shared libraries used by a program using ldd(1). So, for example, you can see the shared libraries used by ls by typing:

    ldd /bin/ls
    Generally you’ll see a list of the sonames being depended on, along with the directory that those names resolve to. In practically all cases you’ll have at least two dependencies:

    /lib/ld-linux.so.N (where N is 1 or more, usually at least 2). This is the library that loads all other libraries.

    libc.so.N (where N is 6 or more). This is the C library. Even other languages tend to use the C library (at least to implement their own libraries), so most programs at least include this one.

    Beware: do not run ldd on a program you don’t trust. As is clearly stated in the ldd(1) manual, ldd works by (in certain cases) by setting a special environment variable (for ELF objects, LD_TRACE_LOADED_OBJECTS) and then executing the program. It may be possible for an untrusted program to force the ldd user to run arbitrary code (instead of simply showing the ldd information). So, for safety’s sake, don’t use ldd on programs you don’t trust to execute.
    3.6. Incompatible Libraries
    When a new version of a library is binary-incompatible with the old one the soname needs to change. In C, there are four basic reasons that a library would cease to be binary compatible:

    The behavior of a function changes so that it no longer meets its original specification,

    Exported data items change (exception: adding optional items to the ends of structures is okay, as long as those structures are only allocated within the library).

    An exported function is removed.

    The interface of an exported function changes.

    If you can avoid these reasons, you can keep your libraries binary-compatible. Said another way, you can keep your Application Binary Interface (ABI) compatible if you avoid such changes. For example, you might want to add new functions but not delete the old ones. You can add items to structures but only if you can make sure that old programs won’t be sensitive to such changes by adding items only to the end of the structure, only allowing the library (and not the application) to allocate the structure, making the extra items optional (or having the library fill them in), and so on. Watch out - you probably can’t expand structures if users are using them in arrays.

    For C++ (and other languages supporting compiled-in templates and/or compiled dispatched methods), the situation is trickier. All of the above issues apply, plus many more issues. The reason is that some information is implemented under the covers'' in the compiled code, resulting in dependencies that may not be obvious if you don't know how C++ is typically implemented. Strictly speaking, they aren'tnew’’ issues, it’s just that compiled C++ code invokes them in ways that may be surprising to you. The following is a (probably incomplete) list of things that you cannot do in C++ and retain binary compatibility, as reported by Troll Tech’s Technical FAQ:

    add reimplementations of virtual functions (unless it it safe for older binaries to call the original implementation), because the compiler evaluates SuperClass::virtualFunction() calls at compile-time (not link-time).

    add or remove virtual member functions, because this would change the size and layout of the vtbl of every subclass.

    change the type of any data members or move any data members that can be accessed via inline member functions.

    change the class hierarchy, except to add new leaves.

    add or remove private data members, because this would change the size and layout of every subclass.

    remove public or protected member functions unless they are inline.

    make a public or protected member function inline.

    change what an inline function does, unless the old version continues working.

    change the access rights (i.e. public, protected or private) of a member function in a portable program, because some compilers mangle the access rights into the function name.

    Given this lengthy list, developers of C++ libraries in particular must plan for more than occasional updates that break binary compatibility. Fortunately, on Unix-like systems (including Linux) you can have multiple versions of a library loaded at the same time, so while there is some disk space loss, users can still run ``old’’ programs needing old libraries.

    展开全文
  • 最近项目用到了opencv,在测试环境编译后生成了so文件,在测试环境运行正常后准备在预发环境进行上线前的测试 但是System.loadLibrary(Core.NATIVE_LIBRARY_NAME)一直加载不成功,也没有报错 更改捕获Exception为...

    最近项目用到了opencv,在测试环境编译后生成了so文件,在测试环境运行正常后准备在预发环境进行上线前的测试

    但是System.loadLibrary(Core.NATIVE_LIBRARY_NAME)一直加载不成功,也没有报错

    更改捕获Exception为Throwable,发现libopencv_java330.so的某个依赖文件没找到或者不存在

    安装对应的依赖so文件后再次部署,运行后又报了另一个so文件不存在,这总不能部署一次报一次错安装对应的so文件吧

    这个时候就需要查看该so文件依赖哪些so文件,并且哪些是没有的,从而一次安装

    此时可通过如下命令

    ldd libopencv_java330.so

    发下有的依赖是not found,这就需要去安装全后才可以正常使用


    以下是依赖都存在的结果


    展开全文
  • 我的工作最近需要用到linux下构建多目录下Makefile产生so动态库样例的知识,我将最新的学习心得,做一个记录分享,以便以后需要使用时可以做参考。
  • Linux文件的类型是不依赖于其后缀名的,但一般来讲:  .o,是目标文件,相当于windows中... .so 为共享,是shared object,用于动态连接的,和dll差不多  .a为静态,是好多个.o合在一起,用于静态连接  .la为libtool
  • Linux下gcc编译生成动态链接库*.so文件并调用它 gcc -c test.c将生成test.o的目标文件 gcc -o app test.c将生成可执行程序app 动态库*.so在linux下用c和c++编程时经常会碰到,最近在网站找了几篇文章介绍动态库的...
  • Linux系统下调用动态库(.so) 1、linuxany.c代码如下: #include "stdio.h" void display(char* msg){ printf("%s\n",msg); } int add(int a,int b){ return a+b; } 2、编译c代码,最后生成Python可执行的....
  • .so文件:是表示的动态库 首先 1. g++ -c -fPIC test.cpp #将cpp源文件编译为.o文件 ar x libGuideUSBCamera.a #将.a库解包,将里面的.o文件全部解出来 ar crU libGuideUSBCamera.a *.o #将页面...
  • 动态链接库及静态链接库( 动态链接库及静态链接库(Windows 下的.dll .Iib 和IinuX下的.so .a ) 动态与静态两种动态通常用.so为后缀静态用.a为后缀例如libhello.so libhello.a 为了在冋一系统中使用不冋版本的...
  • //生成动态库文件libcount1.so (一)此时动态库文件只在当前目录下 ① gcc main.c -o main -L./ -lcount1 //编译通过:-L指定动态库文件所在路径,l指定库文件名 ./main //运行报错:error while ...
  • Linux动态链接库.so文件的创建与使用
  • Linux动态库文件的扩展名为\".so\"(Shared Object)。按照约定,所有动态库文件名的形式是libname.so(可能在名字中加入版本号)。这样,线程函数库被称作libthread.so。静态库的文件名形式是libname.a。共享...
  • linux系统下,当系统中存在多个相同命名的动态库(.so)时,ldconfig只为执行程序链接最后找到的库,而之后找到的库虽然通过“ifconfig -p”可以看到库路径信息,但不会被链接到执行程序。   在实际编程中,...
  • Linux动态链接库so文件搜索目录更新

    千次阅读 2017-03-20 11:19:24
    1. 用ln将需要的so文件链接到/usr/lib或者/lib这两个默认的目录下边 ln -s /where/you/install/lib/*.so /usr/lib sudo ldconfig 2.修改LD_LIBRARY_PATH export LD_LIBRARY_PATH=/where/...
  • linux 动态链接库so的封装及调用

    千次阅读 2018-08-16 16:00:03
    首先定义hello.c文件 #include <stdio.h> void hello(const char * name) { printf("Hello , %s!\n", name); }  定义hello.h头文件 #ifndef HELLO_H #define HELLO_H int g_count...
  • Linux链接动态库的方式

    千次阅读 2018-04-21 17:09:41
    Linux下应用程序链接动态库有以下三种方式:改变LD_LIBRARY_PATHexport LD_LIBRARY_PATH=/home/bow/all/program/test/lib_version_test:$LD_LIBRARY_PATH这里/home/bow/all/program/test/lib_version_test是共享库的...
  • Linux程序运行找不到动态库.so文件的三种解决办法 方法一:添加环境变量 方法1. 添加当前用户当前终端的环境变量-临时 export LD_LIBRARY_PATH=/home/czd/... #.so file path 方法2. 添加当前用户的环境变量 修改~/...
  • vs2015跨平台linux开发so库例子,http://www.cnblogs.com/jiftle/p/8401887.html
  • Linux下的动态链接库.so文件的使用

    千次阅读 2011-12-30 11:12:59
    大家都知道,在WINDOWS系统中有很多的动态链接库(以.DLL为后缀的文件,DLL即Dynamic Link Library)。这种动态链接库,和静态函数不同,它里面的函数并不是执行程序本身的一部分,而是根据执行程序需要按需装入,...
  • 在我的上一篇博客中,我介绍了如何在linux下编译c++程序,下面将进行扩展,用到动态链接库,最后也能实现编译。 方法:用命令:gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so //将所有的头文件的cpp统...
  • 如果使用自己手动生成的动态链接库.so文件,但是这个.so文件,没有加入文件搜索路劲中,程序运行时可能会出现找不到动态链接库的情形。 可以通过ldd命名来查看可执行文件依赖的动态链接库,如下(其中D为可执行...
  • Linux下的.so是基于Linux下的动态链接,其功能和作用类似与windows下.dll文件 一、引言 通常情况下,对函数链接是放在编译时期(compile)完成的。所有相关的对象文件(object file)与牵涉到的函数(library)...
  • Linux 调用动态库(.SO文件)总结

    万次阅读 2014-07-16 10:37:38
    像window调用库文件一样,在linux下,也有相应的API因为加载库文件而存在。它们主要是以下几个函数: 函数名 功能描述 dlopen 打开对象文件,使其可被程序访问 dlsym 获取执行了 ...
  • linux动态链接库

    2018-08-22 18:01:46
    动态链接库与普通的程序相比而言,没有main函数,是一系列函数的实现。通过shared和fPIC编译...例如下面实现一个简单的整数四则运输的动态链接库,定义的caculate.h和caculate.c两个文件,生产libcac.so动态链接库
  • ld.so 动态共享库搜索顺序 ...gcc加入链接参数“-Wl,-rpath”指定动态库搜索路径; 2、环境变量LD_LIBRARY_PATH指定路径; 3、/etc/ld.so.cache中缓存的动态库路径。可以通过修改配置文件/etc/ld.so.conf...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 56,486
精华内容 22,594
关键字:

linux链接动态库so文件

linux 订阅