Linux 间接引用 .so 的路径问题

Posted ymwh@foxmail.com

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux 间接引用 .so 的路径问题相关的知识,希望对你有一定的参考价值。

问题简述

描述一下问题的大概状况

            程序 P---->直接调用 libA.so
                                +----> 调用 libB.so
     也就是程序 P 间接调用了  libB.so

之前记录过这个问题(链接选项-rpath的一个问题记录),并没有详细去找寻原因。这里再次记录一下。

在编译 libA.so 的时候,没有使用链接选项 -Wl,-rpath,在编译 P 的时候,只链接了 libA.so,没有链接 libB.so(这个在之前的文章也提到了)。

实际示例

1、运行提示信息

编写的程序 autoover 使用到了第三方库 libgdal.so ,而这个库间接使用到了 libproj.so。编译 autoover 的时候指定了 -Wl,-rpath=./lib 链接选项。
直接运行,报错,无法找到 libproj.so.20 文件,实际这个文件也是放在 ./lib 目录下的。

bin/autoover
bin/autoover: error while loading shared libraries: libproj.so.20: cannot open shared object file: No such file or directory

2、依赖库状况

先通过 ldd 命令查看下 autoover 所依赖到的库,可以看到它找不到 libproj.so.20 这个动态库。

 ldd bin/autoover
        linux-vdso.so.1 (0x00007ffc707f9000)
        libgdal.so.27 => ./lib/libgdal.so.27 (0x00007f030c7b6000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f030c784000)
        libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f030c596000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f030c447000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f030c42d000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f030c23c000)
        libproj.so.20 => not found
        librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f030c22f000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f030c229000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f030e92a000)

先通过 readelf -d 查看下程序 都引用了哪些so,可以看到程序autoover引用了libgdal.so.27Library runpath./lib。它并没有直接引用 libproj.so.20

 readelf -d bin/autoover

Dynamic section at offset 0x2a8 contains 30 entries:
  标记        类型                         名称/值
 0x000000000000001d (RUNPATH)            Library runpath: [./lib]
 0x0000000000000001 (NEEDED)             共享库:[libgdal.so.27]
 0x0000000000000001 (NEEDED)             共享库:[libpthread.so.0]
 0x0000000000000001 (NEEDED)             共享库:[libstdc++.so.6]
 0x0000000000000001 (NEEDED)             共享库:[libm.so.6]
 0x0000000000000001 (NEEDED)             共享库:[libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             共享库:[libc.so.6]
#  .....  省略很多行不重要的信息

readelf -d lib/libgdal.so.27.0.0

Dynamic section at offset 0x1f1e128 contains 33 entries:
  标记        类型                         名称/值
 0x0000000000000001 (NEEDED)             共享库:[libproj.so.20]
 0x0000000000000001 (NEEDED)             共享库:[librt.so.1]
 0x0000000000000001 (NEEDED)             共享库:[libpthread.so.0]
 0x0000000000000001 (NEEDED)             共享库:[libdl.so.2]
 0x0000000000000001 (NEEDED)             共享库:[libstdc++.so.6]
 0x0000000000000001 (NEEDED)             共享库:[libm.so.6]
 0x0000000000000001 (NEEDED)             共享库:[libc.so.6]
 0x0000000000000001 (NEEDED)             共享库:[ld-linux-x86-64.so.2]
 0x0000000000000001 (NEEDED)             共享库:[libgcc_s.so.1]
 0x000000000000000e (SONAME)             Library soname: [libgdal.so.27]

3、运行时加载依赖库状况

使用 strace 跟踪执行下(也可以使用ltrace跟踪) ,查看查找 libproj.so.20 的过程,可以知道在加载间接引用的库的时候,不会去当前程序的 Library runpath 查找。

strace -e trace=file bin/autoover
execve("bin/autoover", ["bin/autoover"], 0x7ffe318e2060 /* 28 vars */) = 0
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/tls/haswell/x86_64/libgdal.so.27", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/tls/haswell/libgdal.so.27", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/tls/x86_64/libgdal.so.27", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/tls/libgdal.so.27", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/haswell/x86_64/libgdal.so.27", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/haswell/libgdal.so.27", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/x86_64/libgdal.so.27", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/libgdal.so.27", O_RDONLY|O_CLOEXEC) = 3


# ...... 上面在 ./lib 目录下找到了 libgdal.so.27   下面省略很多行 ..........
# .......  程序 自身直接引用的加载完成,之后再去加载间接引用的库(如果已经加载了则不再加载)..........
# ........ 可以看到下面的加载路径,不会去 ./lib 下加载,只去了系统相关路径(实际上会去 libgdal.so.27 的 Library runpath 加载)....

openat(AT_FDCWD, "/lib/x86_64-linux-gnu/tls/haswell/x86_64/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/tls/haswell/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/tls/x86_64/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/tls/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/haswell/x86_64/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/haswell/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/x86_64/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
# .... 这里也省略很多行(删除了很多不重要的输出) ...........
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/x86_64/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "/lib/tls/x86_64/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "/lib/tls/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "/lib/x86_64/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "/lib/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "/usr/lib/tls/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "/usr/lib/haswell/x86_64/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "/usr/lib/haswell/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "/usr/lib/x86_64/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "/usr/lib/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
stat("/usr/lib", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
bin/autoover: error while loading shared libraries: libproj.so.20: cannot open shared object file: No such file or directory
+++ exited with 127 +++

libgdal.so.27 设置一个 rpath 然后再跟踪一次(之前libgdal.so.27是没有设置rpath的,这里就展示了)

# 设置一下 rpath
patchelf --set-rpath ./lib lib/libgdal.so.27
# 查看下设置是否生效
readelf -d lib/libgdal.so.27.0.0

Dynamic section at offset 0x1f5c000 contains 34 entries:
  标记        类型                         名称/值
 0x000000000000001d (RUNPATH)            Library runpath: [./lib]
 0x0000000000000001 (NEEDED)             共享库:[libproj.so.20]
 0x0000000000000001 (NEEDED)             共享库:[librt.so.1]
 0x0000000000000001 (NEEDED)             共享库:[libpthread.so.0]
 0x0000000000000001 (NEEDED)             共享库:[libdl.so.2]
 0x0000000000000001 (NEEDED)             共享库:[libstdc++.so.6]
 0x0000000000000001 (NEEDED)             共享库:[libm.so.6]
 0x0000000000000001 (NEEDED)             共享库:[libc.so.6]
 0x0000000000000001 (NEEDED)             共享库:[ld-linux-x86-64.so.2]
 0x0000000000000001 (NEEDED)             共享库:[libgcc_s.so.1]
 0x000000000000000e (SONAME)             Library soname: [libgdal.so.27]

跟踪下库加载过程

strace -e trace=file bin/autoover
execve("bin/autoover", ["bin/autoover"], 0x7ffcda7c1eb0 /* 28 vars */) = 0
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/tls/haswell/x86_64/libgdal.so.27", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/tls/haswell/libgdal.so.27", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/tls/x86_64/libgdal.so.27", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/tls/libgdal.so.27", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/haswell/x86_64/libgdal.so.27", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/haswell/libgdal.so.27", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/x86_64/libgdal.so.27", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/libgdal.so.27", O_RDONLY|O_CLOEXEC) = 3
# .... 上面已经加载成功 libgdal.so.27 下面加载 autoover 直接引用的其它库的省略 ....
# ..... 加载 libproj.so.20 先从 libgdal.so.27 的 rpath 进行加载(这里可以给libgdal.so.27设置rpath为绝对路径可以看出来)
# ............  这里加载的时候 rpath 如果是相对路径,都是相对于当前工作目录的,不是文件路径的 ....
openat(AT_FDCWD, "./lib/tls/haswell/x86_64/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/tls/haswell/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/tls/x86_64/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/tls/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/haswell/x86_64/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/haswell/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/x86_64/libproj.so.20", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/libproj.so.20", O_RDONLY|O_CLOEXEC) = 3
# 加载完成之后会继续去加载 间接引用库 的引用(如果已经加载则不再加载),这里 librt 是 libgdal.so 引用的,libdl 是 libproj.so 引用的
openat(AT_FDCWD, "./lib/tls/haswell/x86_64/librt.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/tls/haswell/librt.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/tls/x86_64/librt.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/tls/librt.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/haswell/x86_64/librt.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/haswell/librt.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/x86_64/librt.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/librt.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/librt.so.1", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "./lib/tls/haswell/x86_64/libdl.so.2", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/tls/haswell/libdl.so.2", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/tls/x86_64/libdl.so.2", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/tls/libdl.so.2", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/haswell/x86_64/libdl.so.2", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/haswell/libdl.so.2", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/x86_64/libdl.so.2", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "./lib/libdl.so.2", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3

再看一下 autoover 的依赖库情况

ldd bin/autoover
        linux-vdso.so.1 (0x00007ffd619d8000)
        libgdal.so.27 => ./lib/libgdal.so.27 (0x00007f3b7846b000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f3b78439000)
        libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f3b7824b000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f3b780fc000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f3b780e2000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3b77ef1000)
        libproj.so.20 => ./lib/libproj.so.20 (0x00007f3b77883000)
        librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f3b77878000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f3b77872000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f3b7a7c5000)

解决办法汇总

  1. 运行程序前使用环境变量 LD_LIBRARY_PATH=xxxx 指定库加载路径(间接引用的库在里面)
    LD_LIBRARY_PATH=./lib bin/autoover
    
  2. 对直接引用的库,设置 rpath 用于查找间接引用的库(可能有多级间接)
  3. 将间接引用的库所在的目录路径添加到 /etc/ld.so.conf 中,并使用 ldconfig 更新下 /etc/ld.so.cache
  4. 将间接引用的库,添加到程序的依赖列表里面去
    patchelf --add-needed libproj.so.20 bin/autoover
    

以上是关于Linux 间接引用 .so 的路径问题的主要内容,如果未能解决你的问题,请参考以下文章

对共享库函数的未定义引用

编译链接实战(10)linux动态库so查找路径全面盘点

关于linux下连接动态库问题

在 Arch Linux 上构建微软 CNTK 时出错:libCntk.Eval-2.0.so 抱怨未定义对 TensorView 的引用

无法解析类型 javax.servlet.http.HttpServlet。从必需的 .class 文件间接引用了它

如何使用间接引用遍历数组?