我如何链接到一个特定的 glibc 版本?

当我在我的 Ubuntu Lucid 10.04电脑上编译一些东西时,它会链接到 glibc。Lucid 使用了2.11的 glibc。当我在另一台使用旧 glibc 的电脑上运行这个二进制文件时,命令失败说没有 glibc 2.11..。

据我所知,glibc 使用符号版本控制。我可以强制 gcc 链接到一个特定的符号版本吗?

在我的具体使用中,我尝试为 ARM 编译一个 gcc 交叉工具链。

157266 次浏览

您说得对,glibc 使用了符号版本控制。如果您感到好奇的话,我们将介绍 glibc 2.1中引入的符号版本控制实现 给你,它是 Sun 的符号版本控制方案 给你的扩展。

一种选择是静态链接你的二进制文件,这可能是最简单的选择。

您还可以在 chroot 构建环境中构建二进制文件,或者使用 glibc-新的 = > glibc-老了交叉编译器。

根据 http://www.trevorpounds.com的博客文章 < em > < strong > 链接到旧版本符号(glibc) ,可以强迫任何符号链接到一个旧的,只要它是有效的,使用相同的 .symver伪操作,用于定义版本的符号摆在首位。以下示例摘自 博客文章

下面的示例使用 glibc 的 realpath,但要确保它链接到较早的2.2.5版本。

#include <limits.h>
#include <stdlib.h>
#include <stdio.h>


__asm__(".symver realpath,realpath@GLIBC_2.2.5");
int main()
{
const char* unresolved = "/lib64";
char resolved[PATH_MAX+1];


if(!realpath(unresolved, resolved))
{ return 1; }


printf("%s\n", resolved);


return 0;
}

静电干扰连接。当你链接到 静电干扰时,链接器将库嵌入到可执行文件中,所以可执行文件会更大,但是它可以在使用旧版 glibc 的系统上执行,因为程序将使用它自己的库而不是系统的库。

安装1: 编译您自己的 glibc,不需要专门的 GCC 并使用它

因为仅仅使用符号版本控制技术似乎是不可能的,所以让我们更进一步,自己编译 glibc。

这个设置可能会工作并且很快,因为它不会重新编译整个 GCC 工具链,只是 glibc。

但是它不可靠,因为它使用主机 C 运行时对象,如 glibc 提供的 crt1.ocrti.ocrtn.o。这在: https://sourceware.org/glibc/wiki/Testing/Builds?action=recall&rev=21#Compile_against_glibc_in_an_installed_location中提到过,glibc 依赖这些对象进行早期设置,所以如果系统崩溃的方式非常精彩而且非常微妙,我也不会感到惊讶。

有关更可靠的设置,请参见下面的安装2。

构建 glibc 并在本地安装:

export glibc_install="$(pwd)/glibc/build/install"


git clone git://sourceware.org/git/glibc.git
cd glibc
git checkout glibc-2.28
mkdir build
cd build
../configure --prefix "$glibc_install"
make -j `nproc`
make install -j `nproc`

安装1: 验证构建

Test _ glibc. c

#define _GNU_SOURCE
#include <assert.h>
#include <gnu/libc-version.h>
#include <stdatomic.h>
#include <stdio.h>
#include <threads.h>


atomic_int acnt;
int cnt;


int f(void* thr_data) {
for(int n = 0; n < 1000; ++n) {
++cnt;
++acnt;
}
return 0;
}


int main(int argc, char **argv) {
/* Basic library version check. */
printf("gnu_get_libc_version() = %s\n", gnu_get_libc_version());


/* Exercise thrd_create from -pthread,
* which is not present in glibc 2.27 in Ubuntu 18.04.
* https://stackoverflow.com/questions/56810/how-do-i-start-threads-in-plain-c/52453291#52453291 */
thrd_t thr[10];
for(int n = 0; n < 10; ++n)
thrd_create(&thr[n], f, NULL);
for(int n = 0; n < 10; ++n)
thrd_join(thr[n], NULL);
printf("The atomic counter is %u\n", acnt);
printf("The non-atomic counter is %u\n", cnt);
}

test_glibc.sh编译并运行:

#!/usr/bin/env bash
set -eux
gcc \
-L "${glibc_install}/lib" \
-I "${glibc_install}/include" \
-Wl,--rpath="${glibc_install}/lib" \
-Wl,--dynamic-linker="${glibc_install}/lib/ld-linux-x86-64.so.2" \
-std=c11 \
-o test_glibc.out \
-v \
test_glibc.c \
-pthread \
;
ldd ./test_glibc.out
./test_glibc.out

程序输出预期的结果:

gnu_get_libc_version() = 2.28
The atomic counter is 10000
The non-atomic counter is 8674

命令改编自 https://sourceware.org/glibc/wiki/Testing/Builds?action=recall&rev=21#Compile_against_glibc_in_an_installed_location,但 --sysroot使其失败:

cannot find /home/ciro/glibc/build/install/lib/libc.so.6 inside /home/ciro/glibc/build/install

所以我把它取出来了。

ldd输出证实了我们刚刚构建的 ldd和库实际上正在按预期使用:

+ ldd test_glibc.out
linux-vdso.so.1 (0x00007ffe4bfd3000)
libpthread.so.0 => /home/ciro/glibc/build/install/lib/libpthread.so.0 (0x00007fc12ed92000)
libc.so.6 => /home/ciro/glibc/build/install/lib/libc.so.6 (0x00007fc12e9dc000)
/home/ciro/glibc/build/install/lib/ld-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007fc12f1b3000)

gcc编译调试输出显示使用了我的主机运行时对象,正如前面提到的那样,这很糟糕,但我不知道如何绕过它,例如,它包含:

COLLECT_GCC_OPTIONS=/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crt1.o

设置1: 修改 glibc

现在让我们用以下方式修改 glibc:

diff --git a/nptl/thrd_create.c b/nptl/thrd_create.c
index 113ba0d93e..b00f088abb 100644
--- a/nptl/thrd_create.c
+++ b/nptl/thrd_create.c
@@ -16,11 +16,14 @@
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>.  */


+#include <stdio.h>
+
#include "thrd_priv.h"


int
thrd_create (thrd_t *thr, thrd_start_t func, void *arg)
{
+  puts("hacked");
_Static_assert (sizeof (thr) == sizeof (pthread_t),
"sizeof (thr) != sizeof (pthread_t)");

然后重新编译并重新安装 glibc,再重新编译并重新运行我们的程序:

cd glibc/build
make -j `nproc`
make -j `nproc` install
./test_glibc.sh

我们看到 hacked像预期的那样印了几次。

这进一步证实了我们实际上使用了我们编译的 glibc,而不是主机的 glibc。

在 Ubuntu 18.04上测试。

设置2: 十字凳 -NG 原始设置

这是设置1的另一种选择,也是迄今为止我做到的最正确的设置: 就我所能观察到的一切都是正确的,包括 C 运行时对象,如 crt1.ocrti.ocrtn.o

在这个设置中,我们将编译一个完整的专用 GCC 工具链,该工具链使用我们想要的 glibc。

此方法的唯一缺点是构建将花费更长的时间。但我不会冒险用更少的东西来制作。

交叉凳 -NG 是一组脚本,可以从源代码中为我们下载和编译所有内容,包括 GCC、 glibc 和 binutils。

是的,海湾合作委员会的建设系统是如此糟糕,我们需要一个单独的项目。

这个设置并不完美,因为 交叉凳 -NG 不支持在没有额外 -Wl标志的情况下构建可执行文件感觉很奇怪,因为我们已经构建了 GCC 本身。但似乎一切都正常,所以这只是一个不便。

获取并配置十字凳 -NG:

git clone https://github.com/crosstool-ng/crosstool-ng
cd crosstool-ng
git checkout a6580b8e8b55345a5a342b5bd96e42c83e640ac5
export CT_PREFIX="$(pwd)/.build/install"
export PATH="/usr/lib/ccache:${PATH}"
./bootstrap
./configure --enable-local
make -j `nproc`
./ct-ng x86_64-unknown-linux-gnu
./ct-ng menuconfig

我能看到的唯一强制性选项是使其与您的主机内核版本匹配,以使用正确的内核标头。查找主机内核版本:

uname -a

它告诉我:

4.15.0-34-generic

因此,在 menuconfig中,我做到了:

    • Version of linux

所以我选择:

4.14.71

这是第一个相等或更老的版本。它必须更老,因为内核是向后兼容的。

现在你可以用:

env -u LD_LIBRARY_PATH time ./ct-ng build CT_JOBS=`nproc`

现在等待大约三十分钟到两个小时来编译。

安装2: 可选配置

我们用 ./ct-ng x86_64-unknown-linux-gnu生成的 .config有:

CT_GLIBC_V_2_27=y

要改变这一点,请在 menuconfig中执行以下操作:

  • C-library
  • Version of glibc

保存 .config,然后继续构建。

或者,如果您想使用自己的 glibc 源代码,例如从最新的 git 中使用 glibc,那么继续 像这样:

    • 设置为真
      • 答应吧
        • 指向包含 glibc 源代码的目录

其中 glibc 被克隆为:

git clone git://sourceware.org/git/glibc.git
cd glibc
git checkout glibc-2.28

设置2: 测试它

一旦您构建了所需的工具链,就可以使用以下方法进行测试:

#!/usr/bin/env bash
set -eux
install_dir="${CT_PREFIX}/x86_64-unknown-linux-gnu"
PATH="${PATH}:${install_dir}/bin" \
x86_64-unknown-linux-gnu-gcc \
-Wl,--dynamic-linker="${install_dir}/x86_64-unknown-linux-gnu/sysroot/lib/ld-linux-x86-64.so.2" \
-Wl,--rpath="${install_dir}/x86_64-unknown-linux-gnu/sysroot/lib" \
-v \
-o test_glibc.out \
test_glibc.c \
-pthread \
;
ldd test_glibc.out
./test_glibc.out

除了现在使用了正确的运行时对象之外,一切似乎都和安装1中一样:

COLLECT_GCC_OPTIONS=/home/ciro/crosstool-ng/.build/install/x86_64-unknown-linux-gnu/bin/../x86_64-unknown-linux-gnu/sysroot/usr/lib/../lib64/crt1.o

安装2: 有效 glibc 重新编译尝试失败

这似乎不可能与交叉凳 -NG,如下所述。

如果你只是重建;

env -u LD_LIBRARY_PATH time ./ct-ng build CT_JOBS=`nproc`

然后您对自定义 glibc 源代码位置的更改将被考虑在内,但是它从头开始构建所有内容,使其不能用于迭代开发。

如果我们这样做:

./ct-ng list-steps

它给出了构建步骤的一个很好的概述:

Available build steps, in order:
- companion_tools_for_build
- companion_libs_for_build
- binutils_for_build
- companion_tools_for_host
- companion_libs_for_host
- binutils_for_host
- cc_core_pass_1
- kernel_headers
- libc_start_files
- cc_core_pass_2
- libc
- cc_for_build
- cc_for_host
- libc_post_cc
- companion_libs_for_target
- binutils_for_target
- debug
- test_suite
- finish
Use "<step>" as action to execute only that step.
Use "+<step>" as action to execute up to that step.
Use "<step>+" as action to execute from that step onward.

因此,我们看到 glibc 步骤与几个 GCC 步骤交织在一起,最明显的是 libc_start_filescc_core_pass_2之前,cc_core_pass_2可能是与 cc_core_pass_1一起最昂贵的步骤。

为了只构建一个步骤,您必须首先为初始构建设置 .config选项中的“保存中间步骤”:

      • Save intermediate steps

然后你可以试试:

env -u LD_LIBRARY_PATH time ./ct-ng libc+ -j`nproc`

但不幸的是,所需的 +在提到: https://github.com/crosstool-ng/crosstool-ng/issues/1033#issuecomment-424877536

但是请注意,在中间步骤重新启动会将安装目录重置为该步骤中的状态。也就是说,您将拥有一个重新构建的 libc ——但是没有使用这个 libc 构建的最终编译器(因此,也没有 libstdc + + 这样的编译器库)。

基本上仍然使重建太慢,不可行的发展,我不知道如何克服这一点,不修补跨凳 -NG。

此外,从 libc步骤开始似乎没有从 Custom source location再次复制源代码,这进一步使得该方法无法使用。

奖励: stdlibc + +

如果你也对 C++标准程式库感兴趣的话,还有一个奖励: 如何编辑和重建 gCC libstdc + + C++标准程式库源代码?

在我看来,最懒惰的解决方案(特别是如果你不依赖最新的前沿 C/C + + 特性,或最新的编译器特性)还没有被提及,所以这里是:

只需使用您仍然希望支持的最老的 GLIBC 构建系统即可。

实际上,如今使用 chroot、 KVM/Virtualbox 或 docker 这样的技术很容易做到这一点,即使你并不真的想在任何电脑上直接使用这样一个旧版本。详细地说,为了使您的软件成为最大可移植的二进制文件,我建议遵循以下步骤:

  1. 只要选择你的沙盒/虚拟化/... 什么的毒药,并使用它得到一个虚拟的旧 Ubuntu LTS 和编译与 gcc/g + + 它在那里默认。这会自动将 GLIBC 限制为该环境中可用的 GLIBC。

  2. 避免依赖于外部库之外的基础库: 比如,你应该动态链接地面级系统的东西,比如 glibc,libGL,libxcb/X11/wayland things,libasound/libpulseAudio,如果你使用的话可能还有 GTK + ,但是如果可以的话,最好静态链接外部库/发布它们。尤其是像图像加载器、多媒体解码器等自包含的库,如果你静态地发布它们,它们可以在其他发行版上造成较少的破坏(破坏可能会造成,例如,如果只是在不同的主版本中出现)。

通过这种方法,你可以得到一个与 GLIBC 兼容的二进制文件,不需要任何手动符号调整,不需要完全静态的二进制文件(这可能会破坏更复杂的程序,因为 glibc 讨厌这样,这可能会给你带来许可问题) ,不需要设置任何自定义工具链,任何自定义 glibc 副本,或者其他任何东西。

这份回购协议:
Https://github.com/wheybags/glibc_version_header

提供一个头文件,用于处理接受的答案中描述的细节。

基本上:

  1. 下载要链接到的相应 GCC 的 标题
  2. -include /path/to/header.h添加到编译器标志中
  3. 如果要链接 pthread,可能还需要添加 -D_REENTRANT

我还添加了链接器标志: -static-libgcc -static-libstdc++ -pthread

但这些都取决于你的应用程序的需求。

另一种方法是丢弃版本信息,让链接器默认为它拥有的任何版本。

为此,您可能需要查看 PatchELF1:

$ nm --dynamic --undefined-only --with-symbol-versions MyLib.so \
| grep GLIBC | sed -e 's#.\+@##' | sort --unique
GLIBC_2.17
GLIBC_2.29
$ nm --dynamic --undefined-only --with-symbol-versions MyLib.so | grep GLIBC_2.29
U exp@GLIBC_2.29
U log@GLIBC_2.29
U log2@GLIBC_2.29
U pow@GLIBC_2.29
$ patchelf --clear-symbol-version exp   \
--clear-symbol-version log   \
--clear-symbol-version log2  \
--clear-symbol-version pow   MyLib.so

如果您手头没有源代码(或者很难理解代码 2) ,这是非常有用的。

尽管 patchelf在许多发行版上都可用,但它们很可能已经过时了。 --clear-symbol-version标志是在版本 0.12中添加的,不幸的是,它没有完全消除版本要求。 在合并之前,需要从 合并请求手动编译。

对了,我正在交叉编译 LuaJIT