【问题标题】:How to set the path that a .so library will search for other .so libraries?如何设置 .so 库搜索其他 .so 库的路径?
【发布时间】:2015-06-07 23:28:59
【问题描述】:

我有一个依赖于 libB.so 的 libA.so,它位于 ../libB/(来自 libA.c)。我试图以不需要设置任何环境变量的方式编译东西。我有:

    cc -std=c99 -c -fPIC -I../libB/ -Wall libA.c
    cc -std=c99 -shared libA.o -L../libB -lB -o libA.so

这编译得很好。当我运行一个使用 dlopen 加载 libA 的程序时,我得到:

dyld: Library not loaded: libB.so
  Referenced from: libA/libA.so
  Reason: image not found
Trace/BPT trap: 5

所以 libA 在运行时找不到 libB。我找到了这个解决方案来更改 Mac OS X 上的运行时路径:

install_name_tool -change libB.so @loader_path/../libB.so libA.so

但我想找到一种适用于 OS X 和 Linux 的解决方案。同样,我试图让最终用户尽可能少做,所以我不希望他们必须设置环境变量,我必须使用 cc(对我来说是 Apple LLVM 4.2 版(clang-425.0 .27)(基于 LLVM 3.2svn),我希望它也能在 Linux 上工作,所以大概是 cc=gcc 那里)。

编辑我的问题可能比我意识到的要复杂。我正在用 C 语言制作这个动态库,但试图在 python 中使用它。我可以在python中使用libB.so(没有依赖关系)没问题,当我从python中加载libA.so时它会找到它(见上面的错误),只是那时libA.so意识到它没有'不知道在哪里可以找到 libB.so。如果我在下面正确理解了您的答案,则解决方案取决于在编译可执行文件时设置链接器路径,在我的情况下是在 python 中。

编译时有没有办法告诉 libA.so 在哪里寻找 libB.so?之后我可以在 OSX 上使用 install_name_tool 来做到这一点,但是编译器没有办法在 OSX 和 linux 上都可以工作吗?

【问题讨论】:

    标签: linux macos shared-libraries dynamic-linking cc


    【解决方案1】:

    最重要的是,您的最终可执行文件必须知道您的库所在的位置。您可以通过两种方式 (1) 导出包含库路径的 LD_LIBRARY_PATH,或 (2) 使用 rpath,以便您的可执行文件知道在哪里找到你的图书馆。导出LD_LIBRARY_PATH 通常看起来像这样:

    LD_LIBRARY_PATH=/path/to/your/lib:${LD_LIBRARY_PATH}
    export LD_LIBRARY_PATH
    

    我更喜欢使用rpath。要使用rpath,像往常一样编译你的库(下面的例子是我的扩展测试函数库libetf.so

    gcc -fPIC -Wall -W -Werror -Wno-unused -c -o lib_etf.o lib_etf.c
    gcc -o libetf.so.1.0 lib_etf.o -shared -Wl,-soname,libetf.so.1
    

    然后编译一个使用这个库的可执行文件,你编译成对象,然后用链接器选项给出的rpath链接对象。在您的情况下,您将为 libA.solibB.so 提供路径。构建我的testso 可执行文件:

    gcc -O2 -Wall -W -Wno-unused -c -o testetf.o testetf.c
    gcc -o testso testetf.o -L/home/david/dev/src-c/lib/etf -Wl,-rpath=/home/david/dev/src-c/lib/etf -letf
    

    使用ldd 确认可执行文件已正确定位您的库:

    $ ldd testso
            linux-vdso.so.1 (0x00007fffd79fe000)
            libetf.so.1 => /home/david/dev/src-c/lib/etf/libetf.so.1 (0x00007f4d1ef23000)
            libc.so.6 => /lib64/libc.so.6 (0x00007f4d1eb75000)
            /lib64/ld-linux-x86-64.so.2 (0x00007f4d1f126000)
    

    注意:libetf.so.1 指向/home/david/dev/src-c/lib/etf/libetf.so.1

    【讨论】:

      【解决方案2】:

      虽然您自己没有构建可执行文件,但方法几乎相同,只是您将在 libA.so 中而不是在可执行二进制文件中设置 rpath。设置 rpath 时,使用特殊的 $ORIGIN 字符串,以便 libB.so 的位置始终相对于 libA.so。

      ld: Using -rpath,$ORIGIN inside a shared library (recursive)

      例如:

      cc -std=c99 -c -fPIC -I../libB/ -Wall libA.c
      cc -std=c99 -shared libA.o -L../libB -lB -o libA.so -Wl,-rpath,\$ORIGIN/../libB
      

      请注意,$ORIGIN 不是环境变量,它由运行时加载程序直接解释,因此在传递给链接器时会被转义,如上所示。

      顺便说一句,如果您更喜欢在 OS X 上采用类似的方法,您可以在 .so 文件中使用 chrpath 命令编译后更改 rpath - 请参阅:

      Can I change 'rpath' in an already compiled binary?

      [编辑添加]

      嗯,这很有趣!在阅读-rpathinstall_name 上的各种帖子和玩各种选项之间,我想我已经找到了有效的组合。主要技巧似乎是在 libB.so 上设置 install_name 以及在 libA 上设置 @loader_path:

      cc -shared -o libA.so libA.o -L../libB -lB -Wl,-rpath,@loader_path
      cc -shared -o libB.so libB.o -install_name @loader_path/../libB/libB.so
      

      现在 libB.so 始终位于 ../libB/ 相对于 libA.so 所在的位置。

      【讨论】:

      • 非常感谢您的帮助。我尝试了你的建议,但不幸的是它没有用——从第一个链接开始,似乎让它工作的方法是添加 -rpath-link 选项,但我在 OS X 上的编译器不支持它。第二个链接中的 chrpath 也很好,但是当我检查我的 linux 工作站(红帽)时,它没有安装。我希望找到一个解决方案,这样最终用户就不必安装任何依赖项。还有其他想法吗?
      • 看起来在 OS X 上不支持 $ORIGIN 特殊字符串...所以在 OS X 上构建时你应该坚持你的计划。在 Linux 上构建时使用上面概述的技巧,或者安装“chrpath”与:sudo yum install chrpath。基本上,如果您在每个平台上以相应的方式构建它,您的最终用户只需下载并运行即可。
      • 谢谢,您的上述解决方案确实适用于 OS X,但当然不适用于 Linux。我认为必须有一种简单的方法可以在两个操作系统上执行此操作,但也许答案是没有?
      • 不@DanT,我不相信在两个系统上都有相同的方法可以做到这一点。尽管在概念上几乎相同,但魔鬼在细节中。这将是一个很好的候选 Makefile 来总结这些差异,因此在任一构建平台上的最终结果都是一个简单的“make”命令。
      • 你的第二个链接正是我需要的。
      【解决方案3】:

      使用 -rpath 链接器选项。

      -rpath 目录

      将目录添加到运行时库搜索路径。这用于 将 ELF 可执行文件与共享对象链接。所有 -rpath 参数 被连接起来并传递给运行时链接器,运行时链接器使用它们来 在运行时定位共享对象。 -rpath 选项也用于 明确定位共享对象需要的共享对象 包含在链接中;请参阅 -rpath-link 选项的说明。 如果在链接 ELF 可执行文件时未使用 -rpath,则 如果定义了环境变量 LD_RUN_PATH ,它将被使用。 -rpath 选项也可以在 SunOS 上使用。默认情况下,在 SunOS 上, 链接器将从它的所有 -L 选项中形成一个运行时搜索补丁 给出。如果使用 -rpath 选项,则运行时搜索路径将为 仅使用 -rpath 选项形成,忽略 -L 选项。 这在使用 gcc 时很有用,它添加了许多 -L 选项 可能在 NFS 挂载的文件系统上。为了与其他 ELF 兼容 链接器,如果 -R 选项后跟一个目录名,而不是 一个文件名,它被视为 -rpath 选项。

      【讨论】:

      • 感谢您的帮助。当我 man cc 时,我看不到 rpath 的任何选项。不幸的是,我必须使用 cc。有什么想法吗?
      • 当您说“cc”时,您需要更加具体。 “cc”取决于系统。在 Linux 上,它通常只是指向“gcc”的链接。在任何情况下,“rpath”都是链接器选项而不是编译器选项。所以请阅读:man ld
      • 如果不清楚。使用 gcc(可能还有您的 cc)选项可以通过 -Wl 选项传递给链接器。
      • 谢谢,我需要我能得到的所有帮助和明确性 :) cc 对我来说是 Apple LLVM 版本 4.2 (clang-425.0.27)(基于 LLVM 3.2svn)。我试过 cc -shared libA.o -Wl,-rpath ../libB -L../libB -lB -o libA.so。它很好,但是当我尝试在另一个程序中加载库时,我得到与上面相同的错误。
      • 在编译期间使用rpathgcc -Wall -Wextra -L/path/to/your/lib -Wl,-rpath=/path/to/your/lib -o progname somefile.c -Wl,... 选项表示将以下选项传递给链接器(直到遇到下一个空格——因此库选项字符串中没有空格)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-09-16
      • 2015-02-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多