精华内容
下载资源
问答
  • gcc编译链接动态库详解

    千次阅读 2015-01-14 16:00:35
    比较详细的介绍了gcc编译链接动态库的原理。 转载: http://kmoving.blog.163.com/blog/static/205049197201267113438532/ 一、GNU gcc的编译工具用法 我们先来写一个简单的C程序:hello.c 1. #include ...

    比较详细的介绍了gcc编译链接动态库的原理。
    转载: http://kmoving.blog.163.com/blog/static/205049197201267113438532/


    一、GNU gcc的编译工具用法


    我们先来写一个简单的C程序:hello.c
    1. #include <stdio.h>
    2.
    3. void print_hello() {
    4. printf("Hello World/n");
    5. }
    6.
    7. int main(int argc, char argv[]) {
    8. print_hello();

    9. return 0;

    10. }


    定义了一个print_hello函数,调用main函数打印Hello World。
    如何编译它呢?
    1. gcc -o hello -O2 hello.c
    -o参数指定生成的可执行程序的文件名, -O2是优化级别。该命令会编译生成hello可执行程序,看看这个文件:ls -l hello
    C代码 复制代码
    1. -rwxr-xr-x 1 robbin users 11939 2008-11-02 13:48 hello
    有11KB大小。


    看看他链接了哪些系统动态链接库,用ldd命令:

    1. ldd hello

    输出信息为:

    1. libc.so.6 => /lib64/tls/libc.so.6 (0x0000002a9566d000)
    2. /lib64/ld-linux-x86-64.so.2 (0x0000002a95556000)


    libc是C语言标准函数库,ld是动态链接器。
    接着我们看看hello这个程序里面有哪些符号,用nm命令:

    1. nm hello

    输出:
    1. 00000000005008f8 A __bss_start
    2. 000000000040043c t call_gmon_start
    3. ......
    4. 00000000004004f0 T main
    5. 0000000000500658 d p.0
    6. 00000000004004e0 T print_hello
    7. U puts@@GLIBC_2.2.5
    8. 0000000000400410 T _start


    中间省略了一些,不过我们还是可以在符号表里面找到函数定义。
    hello有11KB,体积偏大,去处符号表可以给它瘦身,我们用strip命令:

    1. strip hello

    然后再ls -l hello,输出为:

    1. -rwxr-xr-x 1 webuser users 4464 2008-11-02 13:56 hello

    只有4.4KB了,瘦身效果明显! 不过这次符号表再也看不到了,nm hello,输出为:nm: hello: no symbols。


    最后如果我们想从可执行程序里面提取出来一点什么文本信息的话,还可以用strings命令:

    1. strings hello

    输出信息为:


    1. /lib64/ld-linux-x86-64.so.2
    2. SuSE
    3. libc.so.6
    4. puts
    5. __libc_start_main
    6. __gmon_start__
    7. GLIBC_2.2.5
    8. t fff
    9. Hello World


    友情提醒一下,如果你用Java写一个HelloWorld.java,编译以后你也可以用strings窥探一番。


    二、动态共享库怎么使用


    这次我们把hello.c拆开成为两个文件:hello.c和main.c。hello.c的代码是:


    1. #include <stdio.h>
    2.
    3. void print_hello() {
    4. printf("Hello World/n");
    5. }


    而main.c的代码是:


    1. int main(int argc, char argv[]) {
    2. print_hello();
    3. return 0;
    4. }


    hello.c是我们的动态共享库,在hello.c里面我们声明和实现了各种公用的函数,最后main.c可以去调用这些公用函数。首先我们要把hello.c编译成为动态共享库:

    C代码 复制代码


    1. gcc -o libhello.so -O2 -fPIC -shared hello.c

    -fPIC参数声明链接库的代码段是可以共享的,-shared参数声明编译为共享库。请注意这次我们编译的共享库的名字叫做libhello.so,这也是Linux共享库的一个命名的惯例了:后缀使用so,而名称使用libxxxx格式。
    然后编译main.c的时候,我们需要更多的参数让gcc知道如何寻找共享库:

    1. gcc -o main -O2 -L. -lhello main.c

    -L参数指定到哪个附加路径下面去寻找共享库,现在我们指定在当前目录下面寻找;
    -l (L的小写字母)参数指定链接到哪个共享库上面,我们传的参数hello,那么gcc就会自动链接到libhello.so这个共享库上面(注意我们上面说的libXXXX.so命名规则);


    -I ( i 的大写字母)参数指定到哪个附加路径下面去寻找h文件,这个我们没有使用。


    最后我们成功编译好了main,执行一下,报错:

    引用
    ./main: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory

    找不到libhello.so这个共享库,怎么回事?这是因为libhello.so并不在操作系统默认的共享库的路径下面,我们可以临时指定一下

    链接路径:

    1. export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH

    这样就成功了。我们用ldd main看一下:


    1. libhello.so => ./libhello.so (0x0000002a9566d000)
    2. libc.so.6 => /lib64/tls/libc.so.6 (0x0000002a9576e000)
    3. /lib64/ld-linux-x86-64.so.2 (0x0000002a95556000)


    这次main程序链接到了libhello.so这个共享库上面。


    三、关于Linux的动态共享库的设置

    可执行程序找不到要链接的动态共享库,这是Linux上面编译和运行程序很容易碰到的问题,通过上面的小例子,我们已经大致了解共享库的一点基本原理,接下来我们要探讨一下怎么设置程序寻找动态共享库的行为。


    Linux操作系统上面的动态共享库大致分为三类:


    1、操作系统级别的共享库和基础的系统工具库

    比方说libc.so, libz.so, libpthread.so等等,这些系统库会被放在/lib和/usr/lib目录下面,如果是64位操作系统,还会有/lib64和/usr /lib64目录。如果操作系统带有图形界面,那么还会有/usr/X11R6/lib目录,如果是64位操作系统,还有/usr/X11R6 /lib64目录。此外还可能有其他特定Linux版本的系统库目录。这些系统库文件的完整和版本的正确,确保了Linux上面各种程序能够正常的运行。


    2、应用程序级别的系统共享库
    并非操作系统自带,但是可能被很多应用程序所共享的库,一般会被放在/usr/local/lib和/usr/local/lib64这两个目录下面。很多你自行编译安装的程序都会在编译的时候自动把/usr/local/lib加入gcc的-L参数,而在运行的时候自动到/usr/local/lib下面去寻找共享库。


    以上两类的动态共享库,应用程序会自动寻找到他们,并不需要你额外的设置和担心。这是为什么呢?因为以上这些目录默认就被加入到动态链接程序的搜索路径里面了。Linux的系统共享库搜索路径定义在/etc/ld.so.conf这个配置文件里面。这个文件的内容格式


    大致如下:

    1. /usr/X11R6/lib64
    2. /usr/X11R6/lib
    3. /usr/local/lib
    4. /lib64
    5. /lib
    6. /usr/lib64
    7. /usr/lib
    8. /usr/local/lib64
    9. /usr/local/ImageMagick/lib


    假设我们自己编译安装的ImageMagick图形库在/usr/local/ImageMagick目录下面,并且希望其他应用程序都可以使用ImageMagick的动态共享库,那么我们只需要把/usr/local/ImageMagick/lib目录加入/etc /ld.so.conf文件里面,然后执行:ldconfig 命令即可。


    ldcofig将搜索以上所有的目录,为共享库建立一个缓存文件/etc/ld.so.cache。为了确认ldconfig已经搜索到ImageMagick的库,我们可以用上面介绍的strings命令从ld.so.cache里面抽取文本信息来检查一下:


    1. strings /etc/ld.so.cache | grep ImageMagick

    输出结果为:

    1. /usr/local/ImageMagick/lib/libWand.so.10
    2. /usr/local/ImageMagick/lib/libWand.so
    3. /usr/local/ImageMagick/lib/libMagick.so.10
    4. /usr/local/ImageMagick/lib/libMagick.so
    5. /usr/local/ImageMagick/lib/libMagick++.so.10
    6. /usr/local/ImageMagick/lib/libMagick++.so


    已经成功了!


    3、应用程序独享的动态共享库

    有很多共享库只被特定的应用程序使用,那么就没有必要加入系统库路径,以免应用程序的共享库之间发生版本冲突。因此Linux还可以通过设置环境变量 LD_LIBRARY_PATH来临时指定应用程序的共享库搜索路径,就像我们上面举的那个例子一样,我们可以在应用程序的启动脚本里面预先设置 LD_LIBRARY_PATH,指定本应用程序附加的共享库搜索路径,从而让应用程序找到它。


    具体如下:

    (1)新建自己的头文件路径/home/user/workspace/include和库文件路径/home/user/workspace/lib,这两个目录用来存放我自己编写的头文件和库文件;
    (2)接下来写个头文件和库文件作测试,在/home/user/workspace/include下新建文件test.h,在/home/user/workspace/lib下新建test.c,test.h中是一些函数原型,test.c是函数的实现;
    (3)通过命令gcc -fPIC -shared -o libtest.so test.c在/home/user/workspace/lib生成了一个动态链接库文件libtest.so;
    (4)现在把库文件路径添加进.bash_profile文件,添加内容如下:


    # my code

    C_INCLUDE_PATH=/home/cheney/workspace/include
    export C_INCLUDE_PATH
    LD_LIBRARY_PATH=/home/cheney/workspace/lib
    export LD_LIBRARY_PATH


    然后通过source .bash_profile把.bash_profile文件即时更新了。


    展开全文
  • http://hbprotoss.github.io/posts/linuxxia-bian-yi-lian-jie-dong-tai-ku.html 转载于:https://www.cnblogs.com/liwentao1091/p/4511265.html

    http://hbprotoss.github.io/posts/linuxxia-bian-yi-lian-jie-dong-tai-ku.html

    转载于:https://www.cnblogs.com/liwentao1091/p/4511265.html

    展开全文
  • Linux程序编译链接动态库版本的问题

    千次阅读 2017-03-04 13:37:26
    不同版本的动态库可能会不兼容,如果程序在编译时指定动态库是某个低版本,运行是用的一个高版本,可能会导致无法运行。Linux上对动态库的命名采用libxxx.so.a.b.c的格式,其中a代表大版本号,b代表小版本号,c代表...

    转载来源:http://blog.csdn.net/littlewhite1989/article/details/47726011

    不同版本的动态库可能会不兼容,如果程序在编译时指定动态库是某个低版本,运行是用的一个高版本,可能会导致无法运行。Linux上对动态库的命名采用libxxx.so.a.b.c的格式,其中a代表大版本号,b代表小版本号,c代表更小的版本号,我们以Linux自带的cp程序为例,通过ldd查看其依赖的动态库

     $ ldd /bin/cp                                                                                                                                                                                        
    linux-vdso.so.1 =>  (0x00007ffff59df000)
    libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fb3357e0000)
    librt.so.1 => /lib64/librt.so.1 (0x00007fb3355d7000)
    libacl.so.1 => /lib64/libacl.so.1 (0x00007fb3353cf000)
    libattr.so.1 => /lib64/libattr.so.1 (0x00007fb3351ca000)
    libc.so.6 => /lib64/libc.so.6 (0x00007fb334e35000)
    libdl.so.2 => /lib64/libdl.so.2 (0x00007fb334c31000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fb335a0d000)
    libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fb334a14000)
    

    左边是依赖的动态库名字,右边是链接指向的文件,再查看libacl.so相关的动态库

      $ ll /lib64/libacl.so*                                                                                                                                                                               
    lrwxrwxrwx. 1 root root    15 1月   7 2015 /lib64/libacl.so.1 -> libacl.so.1.1.0
    -rwxr-xr-x. 1 root root 31280 12月  8 2011 /lib64/libacl.so.1.1.0
    

    我们发现libacl.so.1实际上是一个软链接,它指向的文件是libacl.so.1.1.0,命名方式符合我们上面的描述。也有不按这种方式命名的,比如

    $ ll /lib64/libc.so*                                                                                                                                                                                  
    lrwxrwxrwx 1 root root 12 8月  12 14:18 /lib64/libc.so.6 -> libc-2.12.so
    

    不管怎样命名,只要按照规定的方式来生成和使用动态库,就不会有问题。而且我们往往是在机器A上编译程序,在机器B上运行程序,编译和运行的环境其实是有略微不同的。下面就说说动态库在生成和使用过程中的一些问题

    动态库的编译

    我们以一个简单的程序作为例子

    // filename:hello.c
    #include <stdio.h>
    
    void hello(const char* name)
    {
        printf("hello %s!\n", name);
    }
    
    // filename:hello.h
    void hello(const char* name);
    

    采用如下命令进行编译

    gcc hello.c -fPIC -shared -Wl,-soname,libhello.so.0 -o libhello.so.0.0.1
    

    需要注意的参数是-Wl,soname(中间没有空格),-Wl选项告诉编译器将后面的参数传递给链接器,
    -soname则指定了动态库的soname(简单共享名,Short for shared object name)

    现在我们生成了libhello.so.0.0.1,当我们运行ldconfig -n .命令时,当前目录会多一个软连接

     $ ll libhello.so.0                                                                                                                                                                                   
    lrwxrwxrwx 1 handy handy 17 8月  17 14:18 libhello.so.0 -> libhello.so.0.0.1
    

    这个软链接是如何生成的呢,并不是截取libhello.so.0.0.1名字的前面部分,而是根据libhello.so.0.0.1编译时指定的-soname生成的。也就是说我们在编译动态库时通过-soname指定的名字,已经记载到了动态库的二进制数据里面。不管程序是否按libxxx.so.a.b.c格式命名,但Linux上几乎所有动态库在编译时都指定了-soname,我们可以通过readelf工具查看soname,比如文章开头列举的两个动态库

     $ readelf -d /lib64/libacl.so.1.1.0                                                                                                                                                                   
    
    Dynamic section at offset 0x6de8 contains 24 entries:
    Tag        Type                         Name/Value
    0x0000000000000001 (NEEDED)             Shared library: [libattr.so.1]
    0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
    0x000000000000000e (SONAME)             Library soname: [libacl.so.1]
    

    这里省略了一部分,可以看到最后一行SONAME为libacl.so.1,所以/lib64才会有一个这样的软连接

    再看libc-2.12.so文件,该文件并没有采用我们说的命名方式

     $ readelf -d /lib64/libc-2.12.so                                                                                                                                                                      
    
    Dynamic section at offset 0x18db40 contains 27 entries:
    Tag        Type                         Name/Value
    0x0000000000000001 (NEEDED)             Shared library: [ld-linux-x86-64.so.2]
    0x000000000000000e (SONAME)             Library soname: [libc.so.6]
    

    同样可以看到最后一行SONAME为libc.so.6,即便该动态库没有按版本号的方式命名,但仍旧有一个软链指向该动态库,而该软链的名字就是soname指定的名字

    所以关键就是这个soname,它相当于一个中间者,当我们的动态库只是升级一个小版本时,我们可以让它的soname相同,而可执行程序只认soname指定的动态库,这样依赖这个动态库的可执行程序不需重新编译就能使用新版动态库的特性

    可执行程序的编译

    还是以hello动态库为例,我们写一个简单的程序

    // filename:main.c
    #include "hello.h"
    
    int main()
    {
        hello("handy");
        return 0;
    }
    

    现在目录下是如下结构

    ├── hello.c
    ├── hello.h
    ├── libhello.so.0 -> libhello.so.0.0.1
    ├── libhello.so.0.0.1
    └── main.c
    

    libhello.so.0.0.1是我们编译生成的动态库,libhello.so.0是通过ldconfig生成的链接,采用如下命令编译main.c

     $ gcc main.c -L. -lhello -o main                                                                                                                                                                            
    /usr/bin/ld: cannot find -lhello
    

    报错找不到hello动态库,在Linux下,编译时指定-lhello,链接器会去寻找libhello.so这样的文件,当前目录下没有这个文件,所以报错。建立这样一个软链,目录结构如下

    ├── hello.c
    ├── hello.h
    ├── libhello.so -> libhello.so.0.0.1
    ├── libhello.so.0 -> libhello.so.0.0.1
    ├── libhello.so.0.0.1
    └── main.c
    

    让libhello.so链接指向实际的动态库文件libhello.so.0.0.1,再编译main程序

    gcc main.c -L. -lhello -o main
    

    这样可执行文件就生成了。通过以上测试我们发现,在编译可执行程序时,链接器会去找它依赖的libxxx.so这样的文件,因此必须保证libxxx.so的存在

    用ldd查看其依赖的动态库

     $ ldd main                                                                                                                                                                                            
            linux-vdso.so.1 =>  (0x00007fffe23f2000)
            libhello.so.0 => not found
            libc.so.6 => /lib64/libc.so.6 (0x00007fb6cd084000)
            /lib64/ld-linux-x86-64.so.2 (0x00007fb6cd427000)
    

    我们发现main程序依赖的动态库名字是libhello.so.0,既不是libhello.so也不是libhello.so.0.0.1。其实在生成main程序的过程有如下几步

    • 链接器通过编译命令-L. -lhello在当前目录查找libhello.so文件
    • 读取libhello.so链接指向的实际文件,这里是libhello.so.0.0.1
    • 读取libhello.so.0.0.1中的SONAME,这里是libhello.so.0
    • 将libhello.so.0记录到main程序的二进制数据里

    也就是说libhello.so.0是已经存储到main程序的二进制数据里的,不管这个程序在哪里,通过ldd查看它依赖的动态库都是libhello.so.0

    而为什么这里ldd查看main显示libhello.so.0为not found呢,因为ldd是从环境变量$LD_LIBRARY_PATH指定的路径里来查找文件的,我们指定环境变量再运行如下

     $ export LD_LIBRARY_PATH=. && ldd main                                                                                                                                                                
        linux-vdso.so.1 =>  (0x00007fff7bb63000)
        libhello.so.0 => ./libhello.so.0 (0x00007f2a3fd39000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f2a3f997000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f2a3ff3b000)
    

    可执行程序的运行

    现在测试目录结果如下

    ├── hello.c
    ├── hello.h
    ├── libhello.so -> libhello.so.0.0.1
    ├── libhello.so.0 -> libhello.so.0.0.1
    ├── libhello.so.0.0.1
    ├── main
    └── main.c
    

    这里我们把编译环境和运行环境混在一起了,不过没关系,只要我们知道其中原理,就可以将其理清楚

    前面我们已经通过ldd查看了main程序依赖的动态库,并且指定了LD_LIBRARY_PATH变量,现在就可以直接运行了

     $ ./main                                                                                                                                                                                              
    hello Handy!
    

    看起来很顺利。那么如果我们要部署运行环境,该怎么部署呢。显然,源代码是不需要的,我们只需要动态库和可执行程序。这里新建一个运行目录,并拷贝相关文件,目录结构如下

    ├── libhello.so.0.0.1
    └── main
    

    这时运行会main会发现

     $ ./main                                                                                                                                                                                              
    ./main: error while loading shared libraries: libhello.so.0: cannot open shared object file: No such file or directory
    

    报错说libhello.so.0文件找不到,也就是说程序运行时需要寻找的动态库文件名其实是动态库编译时指定的SONAME,这也和我们用ldd查看的一致。通过ldconfig -n .建立链接,如下

    ├── libhello.so.0 -> libhello.so.0.0.1
    ├── libhello.so.0.0.1
    └── main
    

    再运行程序,结果就会符合预期了

    从上面的测试看出,程序在运行时并不需要知道libxxx.so,而是需要程序本身记载的该动态库的SONAME,所以main程序的运行环境只需要以上三个文件即可

    动态库版本更新

    假设动态库需要做一个小小的改动,如下

    // filename:hello.c
    #include <stdio.h>
    
    void hello(const char* name)
    {
        printf("hello %s, welcom to our world!\n", name);
    }
    

    由于改动较小,我们编译动态库时仍然指定相同的soname

    gcc hello.c -fPIC -shared -Wl,-soname,libhello.so.0 -o libhello.so.0.0.2
    

    将新的动态库拷贝到运行目录,此时运行目录结构如下

    ├── libhello.so.0 -> libhello.so.0.0.1
    ├── libhello.so.0.0.1
    ├── libhello.so.0.0.2
    └── main
    

    此时目录下有两个版本的动态库,但libhello.so.0指向的是老本版,运行ldconfig -n .后我们发现,链接指向了新版本,如下

    ├── libhello.so.0 -> libhello.so.0.0.2
    ├── libhello.so.0.0.1
    ├── libhello.so.0.0.2
    └── main
    

    再运行程序

     $ ./main                                                                                                                                                                                              
    hello Handy, welcom to our world!
    

    没有重新编译就使用上了新的动态库, wonderful!

    同样,假如我们的动态库有大的改动,编译动态库时指定了新的soname,如下

    gcc hello.c -fPIC -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0.0
    

    将动态库文件拷贝到运行目录,并执行ldconfig -n .,目录结构如下

    ├── libhello.so.0 -> libhello.so.0.0.2
    ├── libhello.so.0.0.1
    ├── libhello.so.0.0.2
    ├── libhello.so.1 -> libhello.so.1.0.0
    ├── libhello.so.1.0.0
    └── main
    

    这时候发现,生成了新的链接libhello.so.1,而main程序还是使用的libhello.so.0,所以无法使用新版动态库的功能,需要重新编译才行

    最后

    在实际生产环境中,程序的编译和运行往往是分开的,但只要搞清楚这一系列过程中的原理,就不怕被动态库的版本搞晕。简单来说,按如下方式来做

    • 编译动态库时指定-Wl,-soname,libxxx.so.a,设置soname为libxxx.so.a,生成实际的动态库文件libxxx.so.a.b.c,
    • 编译可执行程序时保证libxx.so存在,如果是软链,必须指向实际的动态库文件libxxx.so.a.b.c
    • 运行可执行文件时保证libxxx.so.a.b.c文件存在,通过ldconfig生成libxxx.so.a链接指向libxxx.so.a.b.c
    • 设置环境变量LD_LIBRARY_PATH,运行可执行程序

    EOF

    展开全文
  • 不同版本号的动态库可能会不兼容,假设程序在编译时指定动态库是某个低版本号。执行是用的一个高版本号,可能会导致无法执行。Linux上对动态库的命名採用libxxx.so.a.b.c的格式。当中a代表大版本号号。b代表小版本号...

    不同版本号的动态库可能会不兼容,假设程序在编译时指定动态库是某个低版本号。执行是用的一个高版本号,可能会导致无法执行。

    Linux上对动态库的命名採用libxxx.so.a.b.c的格式。当中a代表大版本号号。b代表小版本号号,c代表更小的版本号号。我们以Linux自带的cp程序为例,通过ldd查看其依赖的动态库

     $ ldd /bin/cp                                                                                                                                                                                        
    linux-vdso.so.1 =>  (0x00007ffff59df000)
    libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fb3357e0000)
    librt.so.1 => /lib64/librt.so.1 (0x00007fb3355d7000)
    libacl.so.1 => /lib64/libacl.so.1 (0x00007fb3353cf000)
    libattr.so.1 => /lib64/libattr.so.1 (0x00007fb3351ca000)
    libc.so.6 => /lib64/libc.so.6 (0x00007fb334e35000)
    libdl.so.2 => /lib64/libdl.so.2 (0x00007fb334c31000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fb335a0d000)
    libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fb334a14000)
    

    左边是依赖的动态库名字,右边是链接指向的文件,再查看libacl.so相关的动态库

      $ ll /lib64/libacl.so*                                                                                                                                                                               
    lrwxrwxrwx. 1 root root    15 1月   7 2015 /lib64/libacl.so.1 -> libacl.so.1.1.0
    -rwxr-xr-x. 1 root root 31280 12月  8 2011 /lib64/libacl.so.1.1.0
    

    我们发现libacl.so.1实际上是一个软链接,它指向的文件是libacl.so.1.1.0,命名方式符合我们上面的描写叙述。也有不按这种方式命名的,比方

    $ ll /lib64/libc.so*                                                                                                                                                                                  
    lrwxrwxrwx 1 root root 12 8月  12 14:18 /lib64/libc.so.6 -> libc-2.12.so
    

    无论如何命名,仅仅要依照规定的方式来生成和使用动态库。就不会有问题。

    并且我们往往是在机器A上编译程序。在机器B上执行程序,编译和执行的环境事实上是有稍微不同的。以下就说说动态库在生成和使用过程中的一些问题

    动态库的编译

    我们以一个简单的程序作为样例

    // filename:hello.c
    #include <stdio.h>
    
    void hello(const char* name)
    {
        printf("hello %s!\n", name);
    }
    
    // filename:hello.h
    void hello(const char* name);
    

    採用例如以下命令进行编译

    gcc hello.c -fPIC -shared -Wl,-soname,libhello.so.0 -o libhello.so.0.0.1
    

    须要注意的參数是-Wl,soname(中间没有空格),-Wl选项告诉编译器将后面的參数传递给链接器,
    -soname则指定了动态库的soname(简单共享名。Short for shared object name)

    如今我们生成了libhello.so.0.0.1,当我们执行ldconfig -n .命令时,当前文件夹会多一个软连接

     $ ll libhello.so.0                                                                                                                                                                                   
    lrwxrwxrwx 1 handy handy 17 8月  17 14:18 libhello.so.0 -> libhello.so.0.0.1
    

    这个软链接是如何生成的呢,并非截取libhello.so.0.0.1名字的前面部分,而是依据libhello.so.0.0.1编译时指定的-soname生成的。也就是说我们在编译动态库时通过-soname指定的名字,已经记载到了动态库的二进制数据里面。无论程序是否按libxxx.so.a.b.c格式命名,但Linux上差点儿全部动态库在编译时都指定了-soname,我们能够通过readelf工具查看soname,比方文章开头列举的两个动态库

     $ readelf -d /lib64/libacl.so.1.1.0                                                                                                                                                                   
    
    Dynamic section at offset 0x6de8 contains 24 entries:
    Tag        Type                         Name/Value
    0x0000000000000001 (NEEDED)             Shared library: [libattr.so.1]
    0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
    0x000000000000000e (SONAME)             Library soname: [libacl.so.1]
    

    这里省略了一部分,能够看到最后一行SONAME为libacl.so.1。所以/lib64才会有一个这种软连接

    再看libc-2.12.so文件,该文件并没有採用我们说的命名方式

     $ readelf -d /lib64/libc-2.12.so                                                                                                                                                                      
    
    Dynamic section at offset 0x18db40 contains 27 entries:
    Tag        Type                         Name/Value
    0x0000000000000001 (NEEDED)             Shared library: [ld-linux-x86-64.so.2]
    0x000000000000000e (SONAME)             Library soname: [libc.so.6]
    

    相同能够看到最后一行SONAME为libc.so.6。即便该动态库没有按版本号号的方式命名,但仍旧有一个软链指向该动态库,而该软链的名字就是soname指定的名字

    所以关键就是这个soname,它相当于一个中间者。当我们的动态库仅仅是升级一个小版本号时,我们能够让它的soname相同。而可执行程序仅仅认soname指定的动态库,这样依赖这个动态库的可执行程序不需又一次编译就能使用新版动态库的特性

    可执行程序的编译

    还是以hello动态库为例,我们写一个简单的程序

    // filename:main.c
    #include "hello.h"
    
    int main()
    {
        hello("handy");
        return 0;
    }
    

    如今文件夹下是例如以下结构

    ├── hello.c
    ├── hello.h
    ├── libhello.so.0 -> libhello.so.0.0.1
    ├── libhello.so.0.0.1
    └── main.c
    

    libhello.so.0.0.1是我们编译生成的动态库,libhello.so.0是通过ldconfig生成的链接。採用例如以下命令编译main.c

     $ gcc main.c -L. -lhello -o main                                                                                                                                                                            
    /usr/bin/ld: cannot find -lhello
    

    报错找不到hello动态库,在Linux下,编译时指定-lhello,链接器会去寻找libhello.so这种文件。当前文件夹下没有这个文件,所以报错。

    建立这样一个软链。文件夹结构例如以下

    ├── hello.c
    ├── hello.h
    ├── libhello.so -> libhello.so.0.0.1
    ├── libhello.so.0 -> libhello.so.0.0.1
    ├── libhello.so.0.0.1
    └── main.c
    

    让libhello.so链接指向实际的动态库文件libhello.so.0.0.1。再编译main程序

    gcc main.c -L. -lhello -o main
    

    这样可执行文件就生成了。

    通过以上測试我们发现,在编译可执行程序时。链接器会去找它依赖的libxxx.so这种文件。因此必须保证libxxx.so的存在

    用ldd查看其依赖的动态库

     $ ldd main                                                                                                                                                                                            
            linux-vdso.so.1 =>  (0x00007fffe23f2000)
            libhello.so.0 => not found
            libc.so.6 => /lib64/libc.so.6 (0x00007fb6cd084000)
            /lib64/ld-linux-x86-64.so.2 (0x00007fb6cd427000)
    

    我们发现main程序依赖的动态库名字是libhello.so.0。既不是libhello.so也不是libhello.so.0.0.1。事实上在生成main程序的过程有例如以下几步

    • 链接器通过编译命令-L. -lhello在当前文件夹查找libhello.so文件
    • 读取libhello.so链接指向的实际文件。这里是libhello.so.0.0.1
    • 读取libhello.so.0.0.1中的SONAME,这里是libhello.so.0
    • 将libhello.so.0记录到main程序的二进制数据里

    也就是说libhello.so.0是已经存储到main程序的二进制数据里的,无论这个程序在哪里,通过ldd查看它依赖的动态库都是libhello.so.0

    而为什么这里ldd查看main显示libhello.so.0为not found呢。由于ldd是从环境变量$LD_LIBRARY_PATH指定的路径里来查找文件的,我们指定环境变量再执行例如以下

     $ export LD_LIBRARY_PATH=. && ldd main                                                                                                                                                                
        linux-vdso.so.1 =>  (0x00007fff7bb63000)
        libhello.so.0 => ./libhello.so.0 (0x00007f2a3fd39000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f2a3f997000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f2a3ff3b000)
    

    可执行程序的执行

    如今測试文件夹结果例如以下

    ├── hello.c
    ├── hello.h
    ├── libhello.so -> libhello.so.0.0.1
    ├── libhello.so.0 -> libhello.so.0.0.1
    ├── libhello.so.0.0.1
    ├── main
    └── main.c
    

    这里我们把编译环境和执行环境混在一起了。只是没关系。仅仅要我们知道当中原理。就能够将其理清楚

    前面我们已经通过ldd查看了main程序依赖的动态库,并且指定了LD_LIBRARY_PATH变量,如今就能够直接执行了

     $ ./main                                                                                                                                                                                              
    hello Handy!
    

    看起来非常顺利。那么假设我们要部署执行环境,该怎么部署呢。

    显然,源码是不须要的,我们仅仅须要动态库和可执行程序。这里新建一个执行文件夹。并拷贝相关文件,文件夹结构例如以下

    ├── libhello.so.0.0.1
    └── main
    

    这时执行会main会发现

     $ ./main                                                                                                                                                                                              
    ./main: error while loading shared libraries: libhello.so.0: cannot open shared object file: No such file or directory
    

    报错说libhello.so.0文件找不到,也就是说程序执行时须要寻找的动态库文件名称事实上是动态库编译时指定的SONAME,这也和我们用ldd查看的一致。

    通过ldconfig -n .建立链接,例如以下

    ├── libhello.so.0 -> libhello.so.0.0.1
    ├── libhello.so.0.0.1
    └── main
    

    再执行程序,结果就会符合预期了

    从以上測试看出,程序在执行时并不须要知道libxxx.so,而是须要程序本身记载的该动态库的SONAME。所以main程序的执行环境仅仅须要以上三个文件就可以

    动态库版本号更新

    假设动态库须要做一个小小的修改,例如以下

    // filename:hello.c
    #include <stdio.h>
    
    void hello(const char* name)
    {
        printf("hello %s, welcom to our world!\n", name);
    }
    

    由于修改较小,我们编译动态库时仍然指定相同的soname

    gcc hello.c -fPIC -shared -Wl,-soname,libhello.so.0 -o libhello.so.0.0.2
    

    将新的动态库复制到执行文件夹。此时执行文件夹结构例如以下

    ├── libhello.so.0 -> libhello.so.0.0.1
    ├── libhello.so.0.0.1
    ├── libhello.so.0.0.2
    └── main
    

    此时文件夹下有两个版本号的动态库。但libhello.so.0指向的是老本版,执行ldconfig -n .后我们发现,链接指向了新版本号,例如以下

    ├── libhello.so.0 -> libhello.so.0.0.2
    ├── libhello.so.0.0.1
    ├── libhello.so.0.0.2
    └── main
    

    再执行程序

     $ ./main                                                                                                                                                                                              
    hello Handy, welcom to our world!
    

    没有又一次编译就使用上了新的动态库, wonderful。

    相同。假如我们的动态库有大的修改。编译动态库时指定了新的soname。例如以下

    gcc hello.c -fPIC -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0.0
    

    将动态库文件复制到执行文件夹。并执行ldconfig -n .,文件夹结构例如以下

    ├── libhello.so.0 -> libhello.so.0.0.2
    ├── libhello.so.0.0.1
    ├── libhello.so.0.0.2
    ├── libhello.so.1 -> libhello.so.1.0.0
    ├── libhello.so.1.0.0
    └── main
    

    这时候发现,生成了新的链接libhello.so.1,而main程序还是使用的libhello.so.0,所以无法使用新版动态库的功能,须要又一次编译才行

    最后

    在实际生产环境中,程序的编译和执行往往是分开的,但仅仅要搞清楚这一系列过程中的原理,就不怕被动态库的版本号搞晕。简单来说,按例如以下方式来做

    • 编译动态库时指定-Wl,-soname,libxxx.so.a。设置soname为libxxx.so.a,生成实际的动态库文件libxxx.so.a.b.c,
    • 编译可执行程序时保证libxx.so存在。假设是软链。必须指向实际的动态库文件libxxx.so.a.b.c
    • 执行可执行文件时保证libxxx.so.a.b.c文件存在,通过ldconfig生成libxxx.so.a链接指向libxxx.so.a.b.c
    • 环境变量设置LD_LIBRARY_PATH,执行可执行程序

    EOF

    展开全文
  • 有时候在同一个项目中,还需要把自己写的类编译动态库,下面记录下自己在ubuntu开发中遇到的坑。 假设我们这里定义了一个类 (1)这是类的头文件 mylib.hpp #ifndef MYLIB_H #define MYLIB_H #include <iostream&...
  • 静态编译动态编译、静态链接库动态链接库理解 1.静态编译:编译器在编译可执行文件时,把需要用到的对应动态链接库(.so或.ilb)中的部分提取出来,链接到可执行文件中去, 使可执行文件在运行时不需要依赖于动态...
  • 前言最近在使用Java开发过程中,需要使用JNI调用DLL动态链接库,但是动态库源码是使用C语言编写的,遂从网上查找资料,作已记录。编译步骤STEP - 1:新建项目文件–新建–项目–Visual C++模块–Win32–Win32项目–...
  • 1.静态编译: 编译器在编译可执行文件时,把需要用到的对应动态链接库(.so或.ilb)中的部分提取出来,...动态编译的可执行文件需要附带一个的动态链接库,在执行时,需要调用其对应动态链接库中的命令。所以其优点...
  • 记录一下,下次搞不清可以来看看...动态编译动态编译的可执行文件需要附带一个动态链接库,在执行时,需要调用其动态链接库中的命令。所以其优点是缩小了执行文件本身的体积,另一方面是加快了编译速度。缺点是哪怕是
  • g++ 编译动态链接库

    2019-08-26 14:41:33
    g++ 编译动态链接库 在linux中动态链接库的后缀为(.so),windows中的...-share指为共享的,-fPIC表示position independent code位置无关,这是动态库特性 -o 指定生成的动态链接库的文件名 -I ./compact_enc_de...
  • 本文的目的是测试各种类型编译后的使用效果,包括链接其他编译方法,使用方法,依赖性等。 太长不看版:请跳至文章最后的总结对比表。 一。内容包含: ①静态libbb.a依赖静态libaa.a的测试; ②...
  • 静态编译:将所有的dll和主程序都编译到一个主程序里面。 动态编译编译的结果为,主程序是主程序,各个模块是各个模块。...动态链接动态链接就是只把中函数的定位信息写入到可执行文件了,然后在运行
  • LIB包含了函数所在的DLL文件和文件中函数位置的信息(入口),代码由运行时加载在进程空间中的DLL提供,称为动态链接库dynamic link library。  静态链接库包括两个文件:  (1).h头文件,包含静态链接库中说明...
  • 最近做项目的时候,需要跟人工智能组对接应用接口,要把写好的C函数给Python代码调用,所以打算把写好的代码制作成动态库,给他们直接import,随手记录一点相关知识 1.概念 程序的函数库一般就是:静态函数库、...
  • 1、把头文件及C文件编译成*.o的文件 一般的命令:  gcc -c -fPIC x.c  ... -fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足
  • 我们调用动态链接库有两种方法:一种是编译的时候,指明所依赖的动态链接库,这样loader可以在程序启动的时候,来所有的动态链接映射到内存中;一种是在运行过程中,通过dlopen和dlfree的方式加载动态链接库动态将...
  • 指明需要链接动态库 libmytest.so 7 LIBPATH = -L./dynamiclib/ libs 指明 libmytest.so 的路径 8 #search paths for errorhandler.c 9 vpath %.c ./ comm 10 #下行是为依赖项 apue.h 准备的,比如 ...
  • GCC 编译使用动态链接库和静态链接库的方法 ...有别于静态库,动态库链接是在程序执行的时候被链接的。所以,即使程序编译完,库仍须保留在系统上,以供程序运行时调用。 2 静态库和动态库的比较 链接静态库
  • LIB包含了函数所在的DLL文件和文件中函数位置的信息(入口),代码由运行时加载在进程空间中的DLL提供,称为动态链接库dynamic link library。 静态链接库包括两个文件: (1).h头文件,包含静态链接库中说明输出...
  • g++ 编译动态链接库和静态链接库

    千次阅读 2014-12-24 09:59:56
    现在我有hello1.cpp和hello2.cpp两个文件,现在我要生成动态链接库libhello.so和静态链接库libhello.a。以下为步骤: 1.生成动态链接库: g++ -m32 hello1.cpp hello2.cpp -fPIC -shared -o ../lib/linux32/...
  • gcc 编译动态链接库

    2012-10-29 18:27:41
    动态库*.so在linux下用c和c++编程时经常会碰到,最近在网站找了几篇文章介绍动态库编译链接,总算搞懂了这个之前一直不太了解得东东,这里做个笔记,也为其它正为动态库链接库而苦恼的兄弟们提供一点帮助。...
  • 1 库的分类 根据链接时期的不同,库又有静态库和动态库之分。 静态库是在链接阶段被链接的(好像是废话,但事实就是这样),所以生成的可执行文件...(TODO:链接动态库时链接阶段到底做了什么) 2 静态库和动

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,437
精华内容 4,174
关键字:

编译链接动态库