如何设置一个.so库搜索其他.so库的路径?

How to set the path that a .so library will search for other .so libraries?

我有一个 libA.so 依赖于 libB.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:

上的 运行time 路径

install_name_tool -更改 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 上工作?

使用 -rpath 链接器选项。

-rpath dir

Add a directory to the runtime library search path. This is used when linking an ELF executable with shared objects. All -rpath arguments are concatenated and passed to the runtime linker, which uses them to locate shared objects at runtime. The -rpath option is also used when locating shared objects which are needed by shared objects explicitly included in the link; see the description of the -rpath-link option. If -rpath is not used when linking an ELF executable, the contents of the environment variable LD_RUN_PATH will be used if it is defined. The -rpath option may also be used on SunOS. By default, on SunOS, the linker will form a runtime search patch out of all the -L options it is given. If a -rpath option is used, the runtime search path will be formed exclusively using the -rpath options, ignoring the -L options. This can be useful when using gcc, which adds many -L options which may be on NFS mounted filesystems. For compatibility with other ELF linkers, if the -R option is followed by a directory name, rather than a file name, it is treated as the -rpath option.

底线是您的最终可执行文件必须知道您的库所在的位置。您可以通过 (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

然后编译一个使用这个库的可执行文件,你编译成对象,然后 link 对象 rpath 作为 linker 选项给出。在您的情况下,您将为 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.

虽然您不是自己构建可执行文件,但方法几乎相同,只是您将在 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?

[编辑添加]

嗯,这很有趣!在阅读 -rpath and install_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 所在的位置。