【问题标题】:How to build the elf interpreter (ld-linux.so.2/ld-2.17.so) as static library?如何将精灵解释器(ld-linux.so.2/ld-2.17.so)构建为静态库?
【发布时间】:2013-07-29 04:10:50
【问题描述】:

如果我的问题不准确,我深表歉意,因为我没有很多 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 的位置,可执行文件没有 更长的工作时间,我发现的唯一“修复”是使用 patchelf 实用程序 从 NixOS 将硬编码路径更改为另一个硬编码路径。为了 这个原因我想链接到静态版本的 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之外不使用任何外部库)。
  • 动态链接是动态的,不依赖于静态位置。这种[硬连线]方法是完全错误的。我想在我的系统中修复它。解决方案很简单,只是我对所有方面都不够熟悉,无法自己解决。
  • 然后,修补内核以满足您的需要。然后你必须修补工具链(编译器和链接器)以服从它们。事实上,所有这些都是免费软件,您可以改进它。
  • 当然,这是下一步要做的事情,但我希望 Linux 在这么多年后能够更加进化,这就是我问的原因。没想到会遇到这样的[找不到合适的词]障碍。
  • Stack Overflow 不是讨论 Linux 内核设计或链接器功能的最佳场所。为此使用更专业的论坛。

标签: linux gcc glibc elf linux-from-scratch


【解决方案1】:

我遇到了同样的问题。就我而言,我想将我的应用程序与安装的系统不同的 GLIBC 捆绑在一起。由于 ld-linux.so 必须与 GLIBC 版本匹配,我不能简单地使用相应的 GLIBC 部署我的应用程序。问题是我无法在没有所需 GLIBC 版本的旧安装上运行我的应用程序。

加载器解释器的路径可以用 --dynamic-linker=/path/to/interp 修改。但是,这需要在编译时设置,因此需要将我的应用程序安装在该位置(或者至少我需要在该位置部署与我的 GLIBC 一起使用的 ld-linux.so,这与简单的xcopy 部署。

所以需要一个与 -rpath 选项可以处理的功能等效的 $ORIGIN 选项。这将允许完全动态部署。

鉴于缺少动态解释器路径(在运行时),有两种选择:

a) 在可执行文件启动前使用 patchelf 修改路径。 b) 使用可执行文件作为参数直接调用 ld-linux.so。

这两个选项都不像可执行文件本身中已编译的 $ORIGIN 路径那样“集成”。

【讨论】:

  • (咆哮)我相信 xcopy 指的是 MS-DOS。
  • 感谢您确认未处理 $ORIGIN。是的,很伤心。
【解决方案2】:

我希望能够使用类似于 LD_LIBRARY_PATH 的环境变量设置解释器的路径,可能是 LD_INTERPRETER_PATH。

这根本不可能。仔细阅读(多次)execve(2)elf(5) & ld.so(8) 手册页和 Linux ABI & ELF 规范。还有内核代码在做execve

ELF 解释器负责动态链接。它必须是文件层次结构中某个固定位置的文件(技术上是静态链接的 ELF 共享库)(通常是 /lib/ld.so.2/lib/ld-linux.so.2/lib64/ld-linux-x86-64.so.2

1990 年代的旧 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 - 路径被连接在内核内部本身。您可以在引导加载时将一些参数传递给内核 -e.g.使用 GRUB- 覆盖默认值。所以即使是内核也想要一些文件在这里!

正如我所评论的,您可能会查看 MUSL-Libc 以获取替代 Libc 实现(提供其自己的动态链接器)。另请阅读 VDSOASLRinitrd

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

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

顺便说一句,大多数(或全部)Linux 是free software,因此您可以改进它(但这需要您几个月或多年的工作)。请通过发布来分享您的改进。

另请阅读Drepper's Hwo to Write Shared Libraries 论文;和this question

【讨论】:

  • 定义我自己的二进制格式似乎并不完全可移植。而不是做这一切,我可以只在内核中公开一些 API,这些 API 将能够覆盖硬编码的位置(可能还有名称),并将这个 API 公开给用户程序,用户程序将能够通过简单地调用程序来配置位置shell... 非常类似于设置 LD_INTERPRETER_PATH,但更好,因为用户空间程序将能够被限制为 root 或类似的东西。无需将所有内容都设为静态或重写。
  • 花时间做这件事很有趣。在构建成千上万个使用 ELF 启用的所有复杂功能的 Linux 程序时,也许您会遇到一些意想不到的问题。
  • 解释器可以是相对路径。
  • 如果解释器是相对路径,我相信这会引发一些网络安全问题
猜你喜欢
  • 2015-01-08
  • 2018-01-25
  • 1970-01-01
  • 1970-01-01
  • 2012-12-11
  • 2016-04-06
  • 2015-11-14
  • 1970-01-01
  • 2013-09-13
相关资源
最近更新 更多