• Linux下so动态库查看与运行时搜索路径的设置掌握这个就够了


    ldd命令

    linux 下可以使用ldd查看可执行文件所需要的动态链接库(*.so)。
    注:下文用so代替动态链接库文件。

    // 举个例子
    ~$ ldd /bin/ls
        linux-vdso.so.1 =>  (0x00007ffe06386000)
        libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007fd686b54000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd68678b000)
        libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007fd68651a000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fd686316000)
        /lib64/ld-linux-x86-64.so.2 (0x0000563411315000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fd6860f9000)

    问题

    如果ldd命令没有找到对应的共享库文件和其具体位置?

    可能是两种情况引起的:

    1)共享库没有安装在该系统中;

    2)共享库保存在/etc/ld.so.conf文件列出的搜索路径之外的位置。

    通常情况下,许多开源代码的程序或函数库都会默认将在即安装到/usr/local目录下的相应位置(如:/usr/local/bin 或 /usr/local/lib)以便于系统自身的程序或函数库相区别。而许多linux系统的/ect/ld.so.conf 文件中默认又不包含 /usr/local/lib 。因此出现安装了共享库,但是却无法找到共享库的情况。

    解决办法:
    检查/etc/ld.so.conf文件,如果其中缺少/usr/local/lib 目录,就添加进去;
    注意:在修改了/etc/ld.so.conf 文件或者在系统中安装了新的函数库之后,需要运行命令 ldconfig ,该命令用来刷新系统的共享库缓存,即 /etc/ld.so.cache 文件。为了减少共享库系统的库搜索时间,共享库系统维护了一个共享库so名称的缓存文件 /etc/ld.so.cache 。 因此,在安装新的共享库之后,一定要运行 ldconfig刷新该缓存。


    strace命令

    strace命令可以跟踪进程执行过程

    #include <iostream>
    using namespace std;
    int main() 
    {
        cout << "test" << endl;
    }
    $ g++ -o main main.cpp
    $ strace main
    strace: Can't stat 'main': No such file or directory
    $ strace ./main
    execve("./main", ["./main"], [/* 30 vars */]) = 0
    brk(NULL)                               = 0x20c6000
    access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f195056b000
    access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
    open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
    fstat(3, {st_mode=S_IFREG|0644, st_size=85573, ...}) = 0
    mmap(NULL, 85573, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f1950556000
    close(3)                                = 0
    access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    open("/usr/lib/x86_64-linux-gnu/libstdc++.so.6", O_RDONLY|O_CLOEXEC) = 3
    read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0 \235\10\0\0\0\0\0"..., 832) = 832
    fstat(3, {st_mode=S_IFREG|0644, st_size=1566440, ...}) = 0
    mmap(NULL, 3675136, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f194ffc6000
    mprotect(0x7f1950138000, 2097152, PROT_NONE) = 0
    mmap(0x7f1950338000, 49152, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x172000) = 0x7f1950338000
    mmap(0x7f1950344000, 13312, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f1950344000
    close(3)                                = 0
    access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
    read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\t\2\0\0\0\0\0"..., 832) = 832
    fstat(3, {st_mode=S_IFREG|0755, st_size=1864888, ...}) = 0
    mmap(NULL, 3967488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f194fbfd000
    mprotect(0x7f194fdbd000, 2093056, PROT_NONE) = 0
    mmap(0x7f194ffbc000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bf000) = 0x7f194ffbc000
    mmap(0x7f194ffc2000, 14848, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f194ffc2000
    close(3)                                = 0
    access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    open("/lib/x86_64-linux-gnu/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
    read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0V\0\0\0\0\0\0"..., 832) = 832
    fstat(3, {st_mode=S_IFREG|0644, st_size=1088952, ...}) = 0
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1950555000
    mmap(NULL, 3178744, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f194f8f4000
    mprotect(0x7f194f9fc000, 2093056, PROT_NONE) = 0
    mmap(0x7f194fbfb000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x107000) = 0x7f194fbfb000
    close(3)                                = 0
    access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    open("/lib/x86_64-linux-gnu/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3
    read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p*\0\0\0\0\0\0"..., 832) = 832
    fstat(3, {st_mode=S_IFREG|0644, st_size=89696, ...}) = 0
    mmap(NULL, 2185488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f194f6de000
    mprotect(0x7f194f6f4000, 2093056, PROT_NONE) = 0
    mmap(0x7f194f8f3000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x15000) = 0x7f194f8f3000
    close(3)                                = 0
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1950554000
    mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1950552000
    arch_prctl(ARCH_SET_FS, 0x7f1950552740) = 0
    mprotect(0x7f194ffbc000, 16384, PROT_READ) = 0
    mprotect(0x7f194fbfb000, 4096, PROT_READ) = 0
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1950551000
    mprotect(0x7f1950338000, 40960, PROT_READ) = 0
    mprotect(0x600000, 4096, PROT_READ)     = 0
    mprotect(0x7f195056d000, 4096, PROT_READ) = 0
    munmap(0x7f1950556000, 85573)           = 0
    brk(NULL)                               = 0x20c6000
    brk(0x20f8000)                          = 0x20f8000
    fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 4), ...}) = 0
    write(1, "test\n", 5test
    )                   = 5
    exit_group(0)                           = ?
    +++ exited with 0 +++
    

    因此,可以明显的观察到Linux操作系统把程序(program)变成进程(process),要经过三个步骤:

    1. fork进程,在内核创建进程相关内核想,加载进程可执行文件

    2. 查找依赖的so,并把它映射到虚拟地址空间

    3. 初始化程序变量


    动态库链接时搜索路径和运行时搜索路径

    举个例子:
    编写一个简单的动态链接库文件

    // add.h
    #ifndef __ADD_H__
    #define __ADD_H__
    int add(int a, int b);
    #endif
    // add.cpp
    int add(int a, int b)
    {
        return a+b;
    }
    // main.cpp
    #include <iostream>
    using namespace std;
    int main() 
    {
        int sum = add(3, 2);
        cout << sum << endl;
    }
    $ g++ -fPIC -shared -o libadd.so add.cpp
    
    // 注:这里-ladd不能在main.cpp之前,否则连接器在链接main.o时找不到libadd.so
    $ g++ -o main -I. -L.  main.cpp -ladd
    $ ./main
    ./main: error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory

    运行时报错,找不到依赖的so文件

    $ ldd main
        linux-vdso.so.1 =>  (0x00007fffaef9b000)
        libadd.so => not found
        libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007feb12d01000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007feb12937000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007feb1262e000)
        /lib64/ld-linux-x86-64.so.2 (0x000055794a8c2000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007feb12418000)

    一般使用设置LD_LIBRARY_PATH这个环境变量来解决

    $ LD_LIBRARY_PATH=.
    $ export LD_LIBRARY_PATH
    $ ./main
    5

    使用export只对当前shell有效,开启的其他shell必须重新设置。可以把export LD_LIBRARY_PATH=xxx写到~/.bashrc对当前用户生效或写入/etc/profile对所有用户生效。可参考:linux设置环境变量

    使用-L. -ladd 是一种设置相对路径方法,也可以使用绝对路径链接方法

    // libadd.so 在main.cpp 后,否则报链接错误
    $ g++ -o main main.cpp /home/jack/workpace/libadd.so
    $ main
    $ ldd main
    $ ldd main
        linux-vdso.so.1 =>  (0x00007fff3ebb7000)
        /home/jack/workspace/libadd.so (0x00007fde18144000)
        libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fde17dad000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fde179e3000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fde176da000)
        /lib64/ld-linux-x86-64.so.2 (0x0000562f1c354000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fde174c4000)

    搜索路径分两种:

    • 链接时搜索路径
      使用-L. 就属于链接时搜索路径,给ld程序链接时使用的搜索动态库路径;

    • 运行时搜索路径
      而使用LD_LIBRARY_PATH属于运行时搜索路径。可以参考5.2 动态连接时执行时的搜索路径


    介绍另一种运行时搜索路径的方式,使用-rpath或-R连接选项来指定运行时搜索路径,其优先级高于LD_LIBRARY_PATH。可参考5.2 动态连接时执行时的搜索路径

    $ g++ -o main -L. -I. main.cpp -Wl,-rpath .
    // 或
    $ g++ -o main -L. -I. main.cpp -Wl,-rpath=.
    // 或
    $ g++ -o main -L. -I. main.cpp -Wl,-R.
    $ ldd main
        linux-vdso.so.1 =>  (0x00007fff4a7f0000)
        libadd.so => ./libadd.so (0x00007fa770bbc000)
        libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fa770825000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa77045b000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fa770152000)
        /lib64/ld-linux-x86-64.so.2 (0x0000557d67d4b000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fa76ff3c000)

    由上面可知: libadd.so => ./libadd.so (0x00007fa770bbc000)

    $ readelf -d main
    
    Dynamic section at offset 0xdf8 contains 27 entries:
      Tag        Type                         Name/Value
     0x0000000000000001 (NEEDED)             Shared library: [libadd.so]
     0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
     0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
     0x000000000000000f (RPATH)              Library rpath: [.]
     0x000000000000000c (INIT)               0x400778
     0x000000000000000d (FINI)               0x400a44
     0x0000000000000019 (INIT_ARRAY)         0x600dd8
     0x000000000000001b (INIT_ARRAYSZ)       16 (bytes)
     0x000000000000001a (FINI_ARRAY)         0x600de8
     0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
     0x000000006ffffef5 (GNU_HASH)           0x400298
     0x0000000000000005 (STRTAB)             0x4004a8
     0x0000000000000006 (SYMTAB)             0x4002e0
     0x000000000000000a (STRSZ)              371 (bytes)
     0x000000000000000b (SYMENT)             24 (bytes)
     0x0000000000000015 (DEBUG)              0x0
     0x0000000000000003 (PLTGOT)             0x601000
     0x0000000000000002 (PLTRELSZ)           192 (bytes)
     0x0000000000000014 (PLTREL)             RELA
     0x0000000000000017 (JMPREL)             0x4006b8
     0x0000000000000007 (RELA)               0x400688
     0x0000000000000008 (RELASZ)             48 (bytes)
     0x0000000000000009 (RELAENT)            24 (bytes)
     0x000000006ffffffe (VERNEED)            0x400648
     0x000000006fffffff (VERNEEDNUM)         2
     0x000000006ffffff0 (VERSYM)             0x40061c
     0x0000000000000000 (NULL)               0x0

    由上面可知:Library rpath: [.] 表示运行时库的搜索路径。

    因此,libadd.so的路径已经被写入了可执行文件中。注意:这里写入的是libadd.so的相对路径,当然也可以写入绝对路径。

    $ g++ -o main -L. -I. main.cpp -Wl,-rpath=/home/jack/workspace -ladd
    $ ldd main
        linux-vdso.so.1 =>  (0x00007ffeed3b3000)
        libadd.so => /home/jack/workspace/libadd.so (0x00007ff2bc9e6000)
        libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007ff2bc64f000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff2bc285000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007ff2bbf7c000)
        /lib64/ld-linux-x86-64.so.2 (0x0000555d79140000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007ff2bbd66000)
    
    $ readelf -d main
    
    Dynamic section at offset 0xdf8 contains 27 entries:
      Tag        Type                         Name/Value
     0x0000000000000001 (NEEDED)             Shared library: [libadd.so]
     0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
     0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
     0x000000000000000f (RPATH)              Library rpath: [/home/jack/workspace]
     0x000000000000000c (INIT)               0x400788
     0x000000000000000d (FINI)               0x400a54
     0x0000000000000019 (INIT_ARRAY)         0x600dd8
     0x000000000000001b (INIT_ARRAYSZ)       16 (bytes)
     0x000000000000001a (FINI_ARRAY)         0x600de8
     0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
     0x000000006ffffef5 (GNU_HASH)           0x400298
     0x0000000000000005 (STRTAB)             0x4004a8
     0x0000000000000006 (SYMTAB)             0x4002e0
     0x000000000000000a (STRSZ)              392 (bytes)
     0x000000000000000b (SYMENT)             24 (bytes)
     0x0000000000000015 (DEBUG)              0x0
     0x0000000000000003 (PLTGOT)             0x601000
     0x0000000000000002 (PLTRELSZ)           192 (bytes)
     0x0000000000000014 (PLTREL)             RELA
     0x0000000000000017 (JMPREL)             0x4006c8
     0x0000000000000007 (RELA)               0x400698
     0x0000000000000008 (RELASZ)             48 (bytes)
     0x0000000000000009 (RELAENT)            24 (bytes)
     0x000000006ffffffe (VERNEED)            0x400658
     0x000000006fffffff (VERNEEDNUM)         2
     0x000000006ffffff0 (VERSYM)             0x400630
     0x0000000000000000 (NULL)               0x0
    

    除了上面介绍的动态库运行时搜索路径设置方法之外,还可以通过设置LD_RUN_PATH环境变量,它也是把路径编译到可执行文件中,它只设置运行时搜索路径。

    $ export LD_RUN_PATH=/home/jack/workspace
    $ g++ -I. -L. -o main main.cpp -ladd
    ./main
    5
    $ ldd main
        linux-vdso.so.1 =>  (0x00007ffdd91e3000)
        libadd.so => /home/jack/workspace/libadd.so (0x00007f2748338000)
        libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f2747fa1000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2747bd7000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f27478ce000)
        /lib64/ld-linux-x86-64.so.2 (0x000055e88eb4a000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f27476b8000)
    
    $ readelf -d main
    
    Dynamic section at offset 0xdf8 contains 27 entries:
      Tag        Type                         Name/Value
     0x0000000000000001 (NEEDED)             Shared library: [libadd.so]
     0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
     0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
     0x000000000000000f (RPATH)              Library rpath: [/home/jack/workspace]
     0x000000000000000c (INIT)               0x400788
     0x000000000000000d (FINI)               0x400a54
     0x0000000000000019 (INIT_ARRAY)         0x600dd8
     0x000000000000001b (INIT_ARRAYSZ)       16 (bytes)
     0x000000000000001a (FINI_ARRAY)         0x600de8
     0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
     0x000000006ffffef5 (GNU_HASH)           0x400298
     0x0000000000000005 (STRTAB)             0x4004a8
     0x0000000000000006 (SYMTAB)             0x4002e0
     0x000000000000000a (STRSZ)              392 (bytes)
     0x000000000000000b (SYMENT)             24 (bytes)
     0x0000000000000015 (DEBUG)              0x0
     0x0000000000000003 (PLTGOT)             0x601000
     0x0000000000000002 (PLTRELSZ)           192 (bytes)
     0x0000000000000014 (PLTREL)             RELA
     0x0000000000000017 (JMPREL)             0x4006c8
     0x0000000000000007 (RELA)               0x400698
     0x0000000000000008 (RELASZ)             48 (bytes)
     0x0000000000000009 (RELAENT)            24 (bytes)
     0x000000006ffffffe (VERNEED)            0x400658
     0x000000006fffffff (VERNEEDNUM)         2
     0x000000006ffffff0 (VERSYM)             0x400630
     0x0000000000000000 (NULL)               0x0
    

    另外也可以通过配置/etc/ld.so.conf文件来设置动态库运行时搜索路径,使用root权限在其中加入/home/jack/workspace路径,然后使用ldconfig命令将/etc/ld.so.conf加载到ld.so.cache中即可。
    具体可参考ldd命令


    这么多方式设置动态库的运行时搜索路径,它们的优先级如下:

    1. LD_RUN_PATH设置的路径
    2. 链接器使用-rpath或-R选项设置的路径
    3. LD_LIBRARY_PATH设置的路径
    4. /etc/ld.so.conf配置的路径
    5. /usr/lib/和 /lib/

    其中LD_RUN_PATH和链接器使用-rpath或-R选项都是设置可执行文件中的Library rpath。


    参考

    linux下so动态库一些不为人知的秘密(上)
    linux下so动态库一些不为人知的秘密(中)
    linux下so动态库一些不为人知的秘密(中二)
    linux静态库和动态库编译及使用

     
  • 相关阅读:

    ATM三层架构思路
    一个项目的从无到有
    re模块
    logging模块
    物联网公共安全平台软件体系架构
    本科生怎样发表自己的论文
    Cloud Native 云化架构阅读笔记
    实验5 Spark SQL编程初级实践
    云计算环境下计算机软件系统架构分析
  • 原文地址:https://www.cnblogs.com/lidabo/p/16375625.html
Copyright © 2020-2023  润新知