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

5

我有一个依赖于libB.so的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上更改运行时路径:

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的方法?

3个回答

10

总之,您的最终可执行文件必须知道您的库所在位置。您可以通过两种方式实现此目的:(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


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

ld:在共享库(递归)中使用-rpath,$ ORIGIN

例如:

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上采用类似的方法,可以使用chrpath命令在.so文件编译后更改rpath-参见:
“我可以更改已编译二进制文件中的'rpath'吗?”(链接1)
【编辑添加】
好玩!在阅读-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 总是相对于 libA.so 的位置在 ../libB/。

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

2

使用-rpath链接器选项。

-rpath dir

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


谢谢你的帮助。当我使用man cc命令时,我没有看到任何关于rpath的选项。不幸的是,我必须使用cc。有什么想法吗? - Dan
1
当你说“cc”时,需要更具体一些。 “cc”是系统相关的。在Linux上,它通常只是指向“gcc”的链接。无论如何,“rpath”是一个链接器选项而不是编译器选项。因此,请阅读:man ld。 - kaylum
谢谢,我需要尽可能详细的帮助和解释 :) 对我来说,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。它编译通过了,但是当我尝试在另一个程序中加载库时,我得到了与上面相同的错误。 - Dan
1
在编译时使用 rpathgcc -Wall -Wextra -L/path/to/your/lib -Wl,-rpath=/path/to/your/lib -o progname somefile.c-Wl,... 选项表示将以下选项传递给链接器(直到遇到下一个空格为止--因此库选项字符串中没有空格)。 - David C. Rankin
我认为你的情况并不比你最初陈述的更复杂。rpath 同样适用于共享库和可执行文件。所以你运行的是 Python(加载器并不关心它是 Python 还是其他语言)。你是否尝试过 David 的建议?也就是你发布的带有 -Wl 的 gcc 命令行是不正确的。"-rpath" 后面不能有空格。你修正了这个问题并重新尝试了吗?此外,我不确定相对路径是如何处理的。你可以先尝试使用绝对路径。如果可以正常工作,再尝试使用相对路径进行实验。 - kaylum
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接