如何将 ELF 解释器 (ld-linux.so.2/ld-2.17.so) 构建为静态库?

7

我很抱歉,我的问题可能不够精确,因为我没有很多与Linux相关的经验。我目前正在从头构建一个Linux系统(主要是按照linuxfromscratch.org版本7.3的指南进行)。我遇到了以下问题:当我构建可执行文件时,它会获得一个硬编码路径,指向名为ELF解释器的东西。

readelf -l program

显示类似于

[Requesting program interpreter: /lib/ld-linux.so.2]

我发现这个库ld-linux-so.2是glibc的一部分。我对此行为不太满意,因为它使得二进制文件非常不可移植——如果我更改/lib/ld-linux.so.2的位置,可执行文件就无法工作了,我唯一找到的“解决方法”是使用来自NixOS的patchelf实用程序将硬编码的路径更改为另一个硬编码的路径。因此,我希望链接静态版本的ld库,但是这样做并没有产生效果。因此,我的问题是,您能否请说明如何构建glibc,以便它会生成一个ld-linux.so.2的静态版本,我可以随后将其与我的可执行文件链接起来。我不完全理解这个ld库的用途,但我认为它是加载其他动态库(或至少glibc.so)的部分。我想要动态地链接我的可执行文件,但我希望动态链接器本身被静态地构建到它们中,这样它们就不会依赖硬编码的路径。或者,我希望能够通过类似于LD_LIBRARY_PATH的环境变量来设置解释器的路径,例如LD_INTERPRETER_PATH。目标是能够产生可移植的二进制文件,在任何目录结构下都能在具有相同ABI的任何平台上运行。一些相关背景:我正在使用Slackware 14 x86构建i686编译器工具链,因此总体来说它都是x86主机和目标。我使用的是glibc 2.17和gcc 4.7.x。

我认为更改ELF程序解释器是一个坏主意(除非你是Linux和binutils大师);它是使用Glibc构建的。你可以尝试其他东西(例如MUSL-Libc...)。你为什么想要更改它?要更改成什么动态加载器?如果你不想依赖它的位置和存在(这是一个坏主意),那就放弃动态链接,只使用静态链接的程序。而动态链接器/lib/ld-linux.so.2是静态构建的(但仍然是共享库,不使用任何外部库,除了内核提供的VDSO)。 - Basile Starynkevitch
动态链接应该是动态的,而不是依赖于静态位置。这种[硬编码]方法是完全错误的。我想在我的系统中修复它。解决方案很容易,只是我自己不够熟悉所有方面来修复它。 - bobef
然后,打补丁来适应你的需求。然后,你需要打补丁来让工具链(编译器和链接器)遵守它们。确实,所有这些都是自由软件,你可以进行改进。 - Basile Starynkevitch
当然,那是接下来要做的事情,但我希望经过这么多年的发展,Linux已经更加先进了,这就是我在问的原因。我没有预料到会遇到这样的[找不到合适的词]障碍。 - bobef
Stack Overflow不是讨论Linux内核设计或链接器特性的最佳场所。请使用更专业的论坛进行讨论。 - Basile Starynkevitch
你所认为的障碍,在上世纪90年代被视为对先前a.out解决方案的重大改进。而ELF并非仅适用于Linux,Solaris(以及可能是AIX)也同样使用它。 - Basile Starynkevitch
2个回答

12
我希望能够设置解释器的路径,就像 LD_LIBRARY_PATH 一样,可能是 LD_INTERPRETER_PATH,但这是不可能的。请仔细阅读execve(2), elf(5) & ld.so(8)的手册,以及Linux的ABI & ELF规范,并且还需要了解执行execve的内核代码。
The ELF解释器负责动态链接。它必须是一个文件(技术上是静态链接的ELF共享库),位于文件系统层次结构中的某个“固定”位置(通常为/lib/ld.so.2或/lib/ld-linux.so.2或/lib64/ld-linux-x86-64.so.2)。
20世纪90年代的旧a.out格式具有内置的动态链接器,部分实现在旧Linux 1.x内核中。它的灵活性和功能要差得多。
内核通过这种(原则上任意的)动态链接器路径启用,以拥有各种动态链接器。但大多数系统只有一个。这是参数化动态链接器的好方法。如果您想尝试另一个动态链接器,请将其安装在文件系统中,并生成提及该路径的ELF可执行文件。
经过巨大的痛苦和努力,您可以制作自己的类似ld.so的动态链接器,实现LD_INTERPRETER_PATH的愿望,但该链接器仍必须是坐落在文件树中某个“固定”的ELF共享库。
如果您想要一个系统不需要任何文件(在一些预定义和有线定位,如/lib/ld.so/dev/null/sbin/init ...),您需要构建所有可执行二进制文件的静态版本。您可能希望(但目前的Linux发行版通常不这样做)有一些静态链接的可执行文件(如/sbin/init/bin/sash...),使您能够修复到没有任何动态链接器的地步崩溃的系统。

顺便说一句,/sbin/init -或者/bin/sh-路径已经被硬编码在内核中。您可以在引导时传递一些参数给内核 -例如使用GRUB- 来覆盖默认值。所以即使内核也需要一些文件在这里!

正如我所评论的,您可以考虑使用MUSL-Libc作为另一种Libc实现(提供自己的动态链接器)。还要阅读有关VDSOASLRinitrd的内容。

在实践中,接受现代Linux和Unix期望存在非空文件系统的事实...请注意,动态链接和共享库是一个巨大的进步(在1990年代的Linux内核和发行版中要困难得多)。

或者,定义您自己的二进制格式,然后创建一个内核模块或binfmt_misc条目来处理它。

BTW,大多数(或全部)的Linux是免费软件,因此您可以改进它(但这需要您花费几个月甚至几年的工作时间)。请通过发布来分享您的改进。
阅读Drepper的如何编写共享库论文;以及这个问题

定义自己的二进制格式似乎不是很可移植。与其这样做,我可以在内核中公开一些API,以便能够覆盖硬编码的位置(和可能的名称),并将此API公开给用户程序,以便能够通过从shell调用程序来配置位置...非常类似于设置LD_INTERPRETER_PATH,但更好,因为用户空间程序将能够被限制为root或类似的东西。无需使所有内容静态或重写所有内容。 - bobef
玩得开心。也许在构建使用ELF启用的所有复杂功能的数千个Linux程序时,您会遇到一些意想不到的问题。 - Basile Starynkevitch
1
解释器可以是相对路径。 - Johan Boulé
如果解释器是相对路径,我认为这会引起一些网络安全方面的担忧。 - Basile Starynkevitch

1
我遇到了同样的问题。在我的情况下,我想要使用与系统安装的不同的GLIBC捆绑我的应用程序。由于ld-linux.so必须匹配GLIBC版本,因此我不能简单地使用相应的GLIBC部署我的应用程序。问题是我无法在没有所需GLIBC版本的旧安装上运行我的应用程序。
可以使用--dynamic-linker=/path/to/interp修改加载器解释器的路径。然而,这需要在编译时设置,因此需要将我的应用程序安装在该位置(或者至少需要在该位置部署与我的GLIBC相对应的ld-linux.so),这与简单的xcopy部署相矛盾。
因此,需要一个$ORIGIN选项,相当于-rpath选项可处理的内容。这将允许完全动态的部署。
由于缺乏动态解释器路径(在运行时),留下了两个选择:
a)在可执行文件启动之前使用patchelf修改路径。 b)直接使用可执行文件作为参数调用ld-linux.so。
这两个选项都不像编译后的$ORIGIN路径在可执行文件本身中那么“集成”。

(抱怨)我认为xcopy是指MS-DOS。 - Johan Boulé
感谢确认 $ORIGIN 没有被处理。是的,这很遗憾。 - Johan Boulé

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