RPATH $ORIGIN LD_LIBRARY_PATH和可移植 linux 二进制文件的描述

23mar10

ldd并不总是给出真正的答案。

很奇怪吧?

链接以在 Linux 系统上创建可移植二进制文件

可移植可执行文件意味着生成的二进制文件将在不同的现代Linux发行版上运行。

链接到系统 libc 和系统 libm 对我来说是可以的。您只需要确保您链接到的glibc版本比您针对的所有发行版都要旧。因此,构建机器可以链接到 GLIBC 2.7,然后所有具有 GLIBC >=2.7 的发行版都可以运行输出二进制文件。出于许多神秘的原因,您不想静态链接到glibc。我不会静态地链接到glibc(libc)或libm,我也不包括这些lib。我让exe找到这些库的系统版本。我也懒得包含我自己的pthreads lib。这些是核心库。

我的目标是现代Linux发行版。问题的范围不能太大。

我构建了我想要在运行旧 debian 的虚拟框中可移植的可执行文件。最近我一直在使用 debian lenny 来编译 ffmpeg 和它的支持库。

考虑 RPATH 和LD_LIBRARY_PATH

我一直在使用嵌入在可执行文件中的rpath来设置高优先级的库搜索路径。而不是使用非常脆弱的LD_LIBRARY_PATH,搜索路径被直接放入可执行文件中,因此您可以执行一些事情,例如在搜索系统库之前,在exe所在的同一目录中搜索它所依赖的库。

RPATH 存储在 elf 可执行文件的动态部分中。它可以是相对路径。因此,您可以移动exe及其支持库,它始终会找到它的库并在尝试系统库之前加载它们,从而隔离您的二进制文件。

LD_LIBRARY_PATH得到了很多松弛,其中一些是应得的。我不喜欢它的原因是,你不能只是符号链接可执行文件并在任何地方运行它,可执行文件需要变量集,所以你总是必须运行一个shell脚本来引导具有该变量的环境,然后调用可执行文件。此外,它只采取绝对路径。所以它很脆。

其他人不喜欢它,因为有些人倾向于把它变成一个全球系统设置,以微妙的方式打破其他东西。另一个原因是,现在任何具有LD_LIBRARY_PATH设置的进程都会将相同的变量导出到它启动的任何子进程中,因此当其他进程生成时,您会再次在不知不觉中中断。

以下是 readelf 的一些输出,告诉它只显示动态部分。我修剪了大部分输出。

$ readelf -d ffmpeg 

Dynamic section at offset 0x12574 contains 27 entries:
 Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libavdevice.so.52]
 0x00000001 (NEEDED)                     Shared library: [libavformat.so.52]
 0x00000001 (NEEDED)                     Shared library: [libavcodec.so.52]
 0x00000001 (NEEDED)                     Shared library: [libavutil.so.49]
 0x00000001 (NEEDED)                     Shared library: [libm.so.6]
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
 0x0000000f (RPATH)                      Library rpath: [$ORIGIN/../lib]
 0x0000000c (INIT)                       0x804a438
etc.

断续器我G我N isaspecialvariablethatmeans'thisex ecutabl e′,and itmeanstheactualexecutable f ilename,asread linkwoul dseit,sosy mlinksare folllowed.我诺特赫沃尔ds,ORIGINisaspecialvariablethatmeans' thisexecutable′,anditmeanstheac tualexecutablefilename,asreadlin kwouldseeit,sosymlinksarefol哎呀。 Inotherwords,ORIGIN 是特殊的,可以解析到二进制文件在运行时所在的位置。

您可以在那里看到 RPATH。它是精灵部分常量池中的一个字符串。由于它是字符串常量池中的索引,因此只能在可执行文件生成为相同大小或更小后才能更改它。我使用名为chrpath的伟大程序,这是一个特殊用途的工具,用于更改可执行文件中的单个字符串。

chrpath,我使用的版本是0.13
http://ftp.tux.org/pub/X-Windows/ftp.hungry.com/chrpath/

因此,您必须编译可执行文件,以便它将RPATH放在标头中。您可以通过向gcc提供一个特殊标志来执行此操作,该标志将将其提供给ld,链接器。它是这样的:

-Wl,-rpath=$ORIGIN/../lib

将此值放入 gcc 并不容易。由于引用问题,你不能只是把它贴在任何地方,美元符号被shell解释,等等,所以我喜欢做的就是把它设置为这个:

-Wl,-rpath=XORIGIN/../lib

我用字母X替换了美元符号。在编译并制作二进制文件后,我将使用chrpath将字符串设置为我想要的内容,这与美元符号相同。请记住常量池,这就是为什么您需要在exe中保留空间的原因。这是一个技巧,可以避开网络上许多人(包括我自己)所遭受的引用地狱。幸运的是,我看到了一个整齐的旁路。

哄骗 ./配置以将其放入其中:

LDFLAGS="-Wl,-rpath=XORIGIN/../lib" ./configure --prefix=/blabla/place

看到 X 了吗?稍后,当您在生成的二进制文件上运行 chrpath 时,它将被美元符号取代。配置脚本将看到LDFLAGS并将其传递给gcc等,构建系统将包含该标志。看到 -Wl 和 -rpath 之间的逗号了吗?这也是必要的。

完成此操作后,您将获得上面的readelf输出,这将使exe在.中查找。/lib 在查找像 /lib 和 /usr/lib 这样的系统 lib dirs 之前,它是依赖项。

因此,这意味着您可以按如下方式设置二进制文件:

place/bin/ffmpeg
place/lib/libavcodec.so
place/lib/libavdevice.so
place/lib/libavformat.so
place/lib/libavutil.so


你可以移动地方/周围,ffmpeg exe总是会在尝试加载系统库之前找到它的库。这对于您可能实际上在系统lib dirs中拥有libs但不希望它们被加载的系统来说是很好的,也许它们没有使用您想要的选项进行编译。也适用于系统库甚至根本没有库的系统。它有效地隔离了二进制文件。

现在回到ldd有时没有给出正确的答案。我认为这是基于这样一个事实,即ldd是一个shell脚本,而不是可执行文件本身。通过执行"文件'哪个ldd'"并查看它是什么类型的文件来检查它。

如果我们在ffmpeg上做ldd,你会得到正确的答案:

user@debian:~/i/bin$ ldd ffmpeg
 linux-gate.so.1 =>  (0xb77c1000)
 libavdevice.so.52 => /home/user/i/bin/./../lib/libavdevice.so.52 (0xb77b9000)
 libavformat.so.52 => /home/user/i/bin/./../lib/libavformat.so.52 (0xb779e000)
 libavcodec.so.52 => /home/user/i/bin/./../lib/libavcodec.so.52 (0xb769c000)
 libavutil.so.49 => /home/user/i/bin/./../lib/libavutil.so.49 (0xb768b000)
 libm.so.6 => /lib/i686/cmov/libm.so.6 (0xb7657000)
 libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb7510000)
 /lib/ld-linux.so.2 (0xb77c2000)

但现在让我们创建一个指向此文件的符号链接,并将符号链接放在另一个目录中,然后在符号链接上运行ldd:

user@debian:~$ ldd ./symlinked-ffmpeg
 linux-gate.so.1 =>  (0xb77d5000)
 libavdevice.so.52 => /usr/lib/i686/cmov/libavdevice.so.52 (0xb77bb000)
 libavformat.so.52 => /usr/lib/i686/cmov/libavformat.so.52 (0xb76c2000)
 libavcodec.so.52 => /usr/lib/i686/cmov/libavcodec.so.52 (0xb6e77000)
 libavutil.so.49 => /usr/lib/i686/cmov/libavutil.so.49 (0xb6e67000)
 libm.so.6 => /lib/i686/cmov/libm.so.6 (0xb6e41000)
 libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb6cfa000)
 etc output trimmed.

现在它找到了系统库,所以即使rpath被放在精灵头中,它看起来也没有效果。但这实际上是错误的,可以通过执行以下操作来验证:

user@debian:~$ LD_TRACE_LOADED_OBJECTS=1 ./symlinked-ffmpeg
 linux-gate.so.1 =>  (0xb77fc000)
 libavdevice.so.52 => /home/user/i/bin/../lib/libavdevice.so.52 (0xb77f4000)
 libavformat.so.52 => /home/user/i/bin/../lib/libavformat.so.52 (0xb77d9000)
 libavcodec.so.52 => /home/user/i/bin/../lib/libavcodec.so.52 (0xb76d7000)
 libavutil.so.49 => /home/user/i/bin/../lib/libavutil.so.49 (0xb76c6000)
 libm.so.6 => /lib/i686/cmov/libm.so.6 (0xb7692000)
 libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb754b000)
 /lib/ld-linux.so.2 (0xb77fd000)

所以这个命令实际上是有效的。
此命令的作用是设置一个名为 LD_TRACE_LOADED_OBJECTS 的环境变量,然后运行可执行文件。当 linux 加载程序看到此 env 变量已设置时,它将输出它加载的库并退出,而不是运行 exe。所以你看到的是加载的"真正的"库,而不是一些shell脚本搞砸了,这就是我认为ldd是什么。

确切地告诉我加载了什么库:
LD_TRACE_LOADED_OBJECTS=1 ./ffmpeg
LD_TRACE_LOADED_OBJECTS=1 ./symlinked-ffmpeg
相同的输出

并使用详细标志查看更多信息:
LD_VERBOSE=1 LD_TRACE_LOADED_OBJECTS=1 ./ffmpeg
LD_VERBOSE=1 LD_TRACE_LOADED_OBJECTS=1 ./symlinked-ffmpeg
再次输出相同

另一种方法是拿出大枪,在exe上运行strace:
strace ./ffmpeg

strace输出告诉我,真正的文件(不是符号链接)是通过readlink找到的:
readlink("/proc/self/exe","/home/user/i/bin/ffmpeg",4096)= 30

然后它尝试找到libavdevice并成功,请记住,现在使用真实的文件路径,因为它确实读取了链接:
open("/home/user/i/bin/../lib/libavdevice.so.52", O_RDONLY) = 3

你有它,RPATH实际上有效。无需再设置LD_LIBRARY_PATH。

很难将RPATH放入精灵可执行文件中,因为引用$ORIGIN问题很猖獗,但这是值得的,特别是如果你回避引用并首先保留空间,然后使用chrpath。

我不确定,但您可能还想在编译生成的任何共享库上运行chrpath,以便可以解决后续的依赖关系级别,但这可能只是由您正在运行的可执行文件中的RPATH处理。我不知道。

兔子洞有多深?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值