【问题标题】:Why is statically linking glibc discouraged?为什么不鼓励静态链接 glibc?
【发布时间】:2019-12-19 22:32:06
【问题描述】:

大多数在线资源表明您可以静态链接 glibc,但不鼓励这样做;例如centos package repo:

The glibc-static package contains the C library static libraries
for -static linking.  You don't need these, unless you link statically,
which is highly discouraged.

这些消息来源很少(或从不)说明为什么这是一个坏主意。

【问题讨论】:

  • 具有讽刺意味的是,CentOS 的软件包 repo 已经过时,有时会迫使开发人员静态链接。

标签: c++ c linker glibc static-linking


【解决方案1】:

其他答案给出的理由是正确的,但不是最重要的原因。

glibc 不应该被静态链接的最重要原因是它在内部广泛使用dlopen,以加载NSS (Name Service Switch) 模块和iconv 转换。模块本身引用 C 库函数。如果主程序与C库动态链接,那没问题。但如果主程序与 C 库静态链接,dlopen 必须去加载 C 库的第二个副本以满足模块的加载要求。

这意味着您的“静态链接”程序仍然需要在文件系统上存在 libc.so.6 的副本,加上 NSS 或 iconv 或任何模块本身,以及模块可能需要的其他动态库,例如ld-linux.so.2libresolv.so.2等。这不是人们在静态链接程序时通常想要的。

这也意味着静态链接的程序在其地址空间中有两个 C 库副本,它们可能会争夺要使用谁的 stdout 缓冲区,谁可以使用非零参数调用 sbrk,即那类的东西。 glibc 内部有一堆防御性逻辑试图使这项工作发挥作用,但从来没有保证能够工作。

您可能认为您的程序不需要担心这一点,因为它从不调用getaddrinfoiconv,但语言环境支持在内部使用iconv,这意味着任何stdio.h 函数 可能会触发对dlopen 的调用,而您无法控制它,用户的环境变量设置可以。

如果你的程序确实调用iconv,那么情况会变得更糟,尤其是当“静态链接”的可执行文件构建在一个发行版上,然后复制到另一个发行版时。 iconv 模块有时位于不同发行版上的不同位置,因此在 Red Hat 发行版上构建的可执行文件可能无法在 Debian 上正常运行,这与人们静态想要的完全相反链接的可执行文件。

【讨论】:

  • 请注意,是否需要第二个 glibc 副本是设计决定。如果静态 glibc 库在 NSS 和 iconv 中静态链接,则没有必要。当然,缺点是您只能使用静态链接的那些 NSS 模块和 iconv 转换,但这从静态链接的定义中非常明显。
  • @MSalters 最近在 glibc 开发列表上有一些关于这样做的讨论。这个设计决定是在 1990 年代做出的,有一个强有力的论点是,我们不再需要在终端输出的字符编码中具有如此多的灵活性,尤其是在人们想要的那种程序中静态链接。 NSS 的灵活性仍然很重要,但有其他方法可以处理它(例如nscd)。
  • 这不太完全对;我发现使用 stdio.h 的静态链接 C 程序在 /lib 中没有库的情况下工作。我必须使用的程序是 lilo。
  • @zwol:如果无法加载库,它只会切换到 LANG=C。这种行为是早期启动所必需的。
  • @Joshua 那么听起来它会加载库和另一个 glibc 副本,如果可以的话。
【解决方案2】:

program/glibc 接口由 POSIX、C 和 C++ 标准等标准化和文档化。例如,fopen() 函数的行为符合 C 标准,pthread_mutex_lock() 符合 POSIX。

glibc/kernel 接口没有标准化。 fopen() 在后台使用 open() 吗?还是它使用openat()?或者是其他东西?明年用什么?你不知道。

如果glibc/kernel 接口发生更改,则使用任何更改但静态链接glibc 的程序将不再工作。

15 多年前,出于这个原因,Solaris 删除了所有静态版本的 libc

Static Linking - where did it go?

在 Solaris 10 中,您无法再构建静态可执行文件。并不是 ld(1) 不允许静态链接或使用存档,只是不再提供 libc.a,即 libc.so.1 的存档版本。这个库提供了用户空间和内核之间的接口,没有这个库就很难创建任何形式的应用程序。

一段时间以来,我们一直在警告用户不要进行静态链接,而针对 libc.a 的链接尤其成问题。每个 solaris 版本或更新(甚至一些补丁)都会导致某些针对 libc.a 构建的应用程序失败。问题是 libc 应该将应用程序与用户/内核边界隔离开来,这个边界会随着版本的不同而发生变化。

如果应用程序是针对 libc.a 构建的,那么它所引用的任何内核接口都会从存档中提取并成为应用程序的一部分。因此,此应用程序只能在与所使用的内核接口同步的内核上运行。如果这些接口发生变化,应用程序将步履蹒跚。

...

编辑:

似乎严重高估了 Linux 内核接口的稳定性。有关详细信息,请参阅Linux kernel API changes/additions。总结一下:

【讨论】:

  • yarchive.net/comp/linux/gcc_vs_kernel_stability.html: 我们非常关心用户空间界面。我们竭尽全力维护设计糟糕或无意的界面。破坏用户程序是不可接受的。
  • @MaximEgorushkin 现实是不同的。 The Linux ABI isn't very stable, to the point it's been mocked relatively recently:“在你的硬件上运行 Linux 发行版有两种基本方法,这不是什么秘密。要么你使用一个稳定的发行版,它的内核版本已经过时,可能不支持你的硬件,要么你运行最新的稳定版版本,但您会失去稳定性,并且容易出现回归。”
  • 您引用的引用是关于内核驱动程序 API,而不是用户空间 API。
  • 我从未声称 Linux API 是标准化的。只是它(相对)稳定。并且大多数 Linux POSIX 函数实现都非常兼容。
  • "glibc 接口是标准化的。按照 POSIX。按照 C 标准。等等。" 编程接口是,但二进制接口不是!
猜你喜欢
  • 2012-10-03
  • 2015-02-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-05
  • 2019-06-07
相关资源
最近更新 更多