我有以下错误Ubuntu 16.04(适用于 Linux 的 Windows 子系统)
/lib/x86_64-linux-gnu/libc.so.6: version 'GLIBC_2.25' not found
我已经尝试过了:
apt-get update
apt-get install libc6
看来,系统有最新的 libc6,因为它输出
libc6 is already the newest version (2.23-0ubuntu10)
但我想要运行的程序却失败了。
我如何解决它?
答案1
现在这个答案可能会来得有点晚,但让我尝试给你提供一些你在这里遇到的技术背景以及可能的解决方法。
首先,评论的内容是正确的。glibc 是 Ubuntu 系统的核心 C 运行时。但是,其他发行版会选择其他运行时,并且程序也可以选择静态链接更自由许可的运行时(musl-libc 就是一个例子)。不过,后一种方法也有其局限性。
根据经验,您可以运行在您的系统上构建的软件。这对于某些 BSD 或 Gentoo Linux 等系统来说完全没问题,但对于 Debian 和 Ubuntu 等预装发行版来说,这开始成为一个问题。
系统调用
你的内核提供了许多系统调用。这些系统调用中的某个子集标准化有些将特定于您的系统,即在这种情况下为 GNU/Linux。
大多数系统调用(例如“打开文件”)在 C 运行时中都有直接对应部分,但 C 运行时通常会在顶部提供额外的东西(例如动态链接设施......)。
C 运行时
glibc 是所谓的共享对象。其他系统称之为动态库或动态链接库。尽管实现和功能不同,但目标大致相同。
术语“共享对象”指的是目标代码(“库函数”)在系统中的进程之间共享。
另一个优点是只需要更新这一个库,而不是每个与该库的特定版本静态链接的应用程序。
专有软件(即非牙线) 存在许可问题,因为 glibc 许可证为不愿意披露其专有源代码的人带来了某些“问题”。现在这显然不是您的代码的问题,因为您可以继续构建你自己的。
缺少符号
您遇到的错误:
/lib/x86_64-linux-gnu/libc.so.6: version 'GLIBC_2.25' not found
这是我经常开玩笑地与那些抱怨 Windows 上“DLL 地狱”的人对峙的话题。Linux 并没有更好,而且传统的链接器几乎无法控制您链接的符号。
好吧,让我们开始动手吧。确保您已binutils
安装(apt install binutils
),我假设您已安装标准 Bash shell,并且可以在中找到它/bin/bash
。
我们将从以下调用开始(而不是readelf
,objdump
也可以,但它有自己的命令行参数):
readelf --dyn-syms /bin/bash|grep -P '\WUND\W'
它的作用是列出 Bash 二进制文件中的所有动态符号,然后将输出限制为未定义的项 ( )。这将为我们提供 Bash 从 glibc 使用的函数列表。我们可以通过添加 来UND
删除所有引用版本的条目,从而进一步完善此列表:GLIBC_2.2.5
grep -v 'GLIBC_2\.2\.5'
$ readelf --dyn-syms /bin/bash|grep -P '\WUND\W'|grep -v 'GLIBC_2\.2\.5'
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __ctype_toupper_loc@GLIBC_2.3 (3)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND tgetflag@NCURSES_TINFO_5.0.19991023 (4)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __snprintf_chk@GLIBC_2.3.4 (5)
14: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __vfprintf_chk@GLIBC_2.3.4 (5)
20: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
22: 0000000000000000 0 FUNC GLOBAL DEFAULT UND tgetent@NCURSES_TINFO_5.0.19991023 (4)
25: 0000000000000000 0 FUNC GLOBAL DEFAULT UND tputs@NCURSES_TINFO_5.0.19991023 (4)
37: 0000000000000000 0 FUNC GLOBAL DEFAULT UND faccessat@GLIBC_2.4 (6)
50: 0000000000000000 0 FUNC GLOBAL DEFAULT UND tgoto@NCURSES_TINFO_5.0.19991023 (4)
60: 0000000000000000 0 FUNC GLOBAL DEFAULT UND eaccess@GLIBC_2.4 (6)
63: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail@GLIBC_2.4 (6)
72: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __fdelt_chk@GLIBC_2.15 (7)
80: 0000000000000000 0 FUNC GLOBAL DEFAULT UND tgetnum@NCURSES_TINFO_5.0.19991023 (4)
98: 0000000000000000 0 FUNC GLOBAL DEFAULT UND tgetstr@NCURSES_TINFO_5.0.19991023 (4)
101: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __asprintf_chk@GLIBC_2.8 (8)
104: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __memmove_chk@GLIBC_2.3.4 (5)
106: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __memcpy_chk@GLIBC_2.3.4 (5)
108: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
114: 0000000000000000 0 FUNC GLOBAL DEFAULT UND memcpy@GLIBC_2.14 (9)
138: 0000000000000000 0 FUNC GLOBAL DEFAULT UND regexec@GLIBC_2.3.4 (5)
145: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __vsnprintf_chk@GLIBC_2.3.4 (5)
147: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __strncpy_chk@GLIBC_2.3.4 (5)
153: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __strcpy_chk@GLIBC_2.3.4 (5)
157: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __printf_chk@GLIBC_2.3.4 (5)
160: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __longjmp_chk@GLIBC_2.11 (11)
195: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __fprintf_chk@GLIBC_2.3.4 (5)
198: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
215: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __ctype_tolower_loc@GLIBC_2.3 (3)
216: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __ctype_b_loc@GLIBC_2.3 (3)
219: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __sprintf_chk@GLIBC_2.3.4 (5)
2.2.5 版本是第一个支持 x86-64 架构的版本,这就是我们对它不感兴趣的原因。第一个可用版本将绝不提出一个问题。
但是,如果您想在“仅”具有 glibc 2.14 的系统上运行这个 Bash 二进制文件,它将无法工作,因为__fdelt_chk@GLIBC_2.15
至少需要 2.15 版本。
插曲:符号版本控制
@GLIBC_2.3.4
符号名称上的后缀(等)描述版本范围(另见这里)。这使 ELF 共享对象能够支持符号的多个版本。例如,函数foo()
在库的 1.0 版本中可能采用 2 个参数,但在 2.x 版本范围内采用 3 个参数。动态链接到这些符号的软件包含符号版本,这意味着它将始终链接到函数的适当版本。但是,缺点就是您遇到的情况。如果所使用的符号之一需要比您的系统上可用的库版本更新的库版本,则在具有较新 glibc(或实际上任何其他也使用符号版本控制的库)的系统上链接的软件将无法在您的(较旧的)系统上运行。
解决此问题的传统方法始终是构建一个足够老的系统,以便提供足够低的库版本共同点。这甚至允许您在某种程度上运行在一个发行版上构建的二进制文件,以便在一系列其他发行版上运行。缺点是/曾经是这意味着较旧的编译器、较旧的工具链、较旧的库(因为除了 C 运行时之外还有更多的库)。
存在某些替代方案(见下文),但它们都要求您准确分析您想要的是什么。例如,如果您的程序提供插件接口并与插件交换缓冲区,则静态链接到 musl-libc(具有更自由的许可条款)将不起作用……在这种情况下,动态链接到相同的 C 运行时很重要。此外,musl-libc 和 glibc 可能会在同一进程中相互干扰,因为
sbrk
。
解决难题的可能方法
- 在虚拟机中运行软件,该虚拟机使用更现代的发行版,可满足现成的库依赖性。由于虚拟机允许您运行所需的任何内核,因此这甚至可以用于需要非常现代的系统调用的软件。
- 在 chroot jail 或容器中运行你的软件(类似于上面的 VM 主题,但是这里如果内部运行的代码使用主机内核上不可用的较新的系统调用,则可能会遇到问题,LXD 4.0 尝试通过为这些系统调用提供存根来缓解此问题)。
- 使用 AppImage(但有人需要注意 AppImage 的存根尽可能向后兼容。
- 使用 snap 包。
更复杂的解决方案,或者如果你是开发人员
- 除了旧版本外,还提供较新的 glibc 版本。由于这是一个守护进程,因此有可能某物启动它。无论是 systemd 还是老旧的 SysV init,您都应该能够设置
LD_LIBRARY_PATH
将其指向你的更新了 glibc。 - 如果你是该软件的维护者,这种方法会提供一个可行的替代方案。它的作用是指示链接器选择同一符号的早期版本,而不是最新版本(默认选择最新版本)。GitHub
上有一个名为wheybags/glibc_version_header,它试图将其形式化并可能帮助您启动。 - 曾经有一个名为 autopackage 的项目,它在包装器中有效地使用了这种方法亚太天然气合作委员会随附的。您仍可以在很多地方下载相应的文件以了解实施细节。
- 到目前为止,最细致入微的解释是这个. glibc 2.14 的新
memcpy
功能是GNU_IFUNC
, 意思就是一次在运行时,将选择最有效的版本(例如针对特定处理器模型进行了优化!)。这与简单地提供直接的引用要重定位的函数。主要结论是,在这种特殊情况下,您不能只是修改符号名称,因为这种形式的“延迟加载”机制的 ABI 与该符号的旧版本的 ABI 不同。- 这是另一种解释由同一用户。
如果你真的对链接器的内部工作原理感兴趣,你可以买一本关于这个主题的书,或者参考 Ian Lance Taylors 的优秀文章系列,你可以在此处查看概述。