【技术】version GLIBC_2.14 not found

作者: | 更新日期:

对于 glibc 版本问题本以为只有升级这一个方案,没想到竟然有这么完美的不升级的方案,但是网上又是少之又少,甚是可惜

本文首发于公众号:天空的代码世界,微信号:tiankonguse
原文地址:https://mp.weixin.qq.com/s/r7a3hm09yerbmfimT2yQzw

零、背景的背景

在Linux 后台开发中,最常见的问题就是符号问题了。
之前曾写过不少相关主题的文章,比如《符号的那些问题》、以及《静态库遇到静态库系列》,都是介绍符号的某个问题,今天遇到了另一个问题,简单记录一下。

一、背景

最近一年一直处于搭建中台的过度期,其中一步就是开发机 docker 化。
之前几百号人都在一台机器上开发编译程序,甚至一些新的毕业生或者实习生还在上面跑程序,往往会因为一个人的操作开发机要么CPU满了,要么磁盘满了,影响所有人开发与编译。
现在计划使用 docker 开发机,每人都是独立的环境:CPU、内存、磁盘都是独立的,再也没人来影响自己了。

开开心心的搭建自己的 docker 开发编译环境,实现计划中的逻辑功能,传到测试环境发现服务跑不起来。
原来遇到了 libc 版本不兼容问题,报这样的错误: /lib64/libc.so.6: version GLIBC_2.14' not found

二、问题分析

这个问题看错误提示就可以知道,是由于运行环境 Glibc 版本 比 编译环境 Glibc 版本低的缘故。

在运行环境下查看 glib 版本

> strings /lib64/libc.so.6 |grep "^GLIBC_" | sort -u
GLIBC_2.10
GLIBC_2.11
GLIBC_2.12
GLIBC_2.2.5
GLIBC_2.2.6
GLIBC_2.3
GLIBC_2.3.2
GLIBC_2.3.3
GLIBC_2.3.4
GLIBC_2.4
GLIBC_2.5
GLIBC_2.6
GLIBC_2.7
GLIBC_2.8
GLIBC_2.9
GLIBC_PRIVATE

在编译环境下查看 glib 版本

> strings /lib64/libc.so.6 |grep "^GLIBC_" | sort -u
GLIBC_2.10
GLIBC_2.11
GLIBC_2.12
GLIBC_2.13
GLIBC_2.14
GLIBC_2.15
GLIBC_2.16
GLIBC_2.17
GLIBC_2.2.5
GLIBC_2.2.6
GLIBC_2.3
GLIBC_2.3.2
GLIBC_2.3.3
GLIBC_2.3.4
GLIBC_2.4
GLIBC_2.5
GLIBC_2.6
GLIBC_2.7
GLIBC_2.8
GLIBC_2.9
GLIBC_PRIVATE

原来运行环境的 glic 最高版本还是 2.12, 编译环境已经到2.17 了。
那看看编译出的程序呢
查看运行环境编译出的程序

> objdump -p libspp_union_dim_more_center_worker.so
  required from libc.so.6:
    0x06969194 0x00 13 GLIBC_2.14
    0x0d696914 0x00 11 GLIBC_2.4
    0x0d696913 0x00 08 GLIBC_2.3
    0x09691974 0x00 05 GLIBC_2.3.4
    0x09691a75 0x00 03 GLIBC_2.2.5

>  objdump -T libspp_union_dim_more_center_worker.so | grep GLIBC_2.14
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.14  memcpy

> objdump -T libspp_union_dim_more_center_worker.so | grep memcpy
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.3.4 __memcpy_chk
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.14  memcpy

> nm  libspp_union_dim_more_center_worker.so | grep GLIBC_2.14
                 U memcpy@@GLIBC_2.14

> nm  libspp_union_dim_more_center_worker.so | grep memcpy
                 U __memcpy_chk@@GLIBC_2.3.4
                 U memcpy@@GLIBC_2.14

这里可以确定了, memcpy 符号可能有问题,用到了 glic2.14,运行机器没有这个版本。

再来看看 glibc里面 memcpy 的相关信息。

运行环境符号如下:

> nm /lib64/libc.so.6 | grep " memcpy"
0000000000088320 T memcpy
00000000000a4030 t memcpy_uppcase

> objdump -T  /lib64/libc.so.6 | grep " memcpy"
0000000000088320 g    DF .text  0000000000000465  GLIBC_2.2.5 memcpy

编译环境符号如下:

> nm /lib64/libc.so.6 | grep " memcpy"
000000000008e860 i memcpy@@GLIBC_2.14
0000000000089650 i memcpy@GLIBC_2.2.5

> objdump -T  /lib64/libc.so.6 | grep " memcpy"
000000000008e860 g   iD  .text  0000000000000055  GLIBC_2.14  memcpy
0000000000089650 g   iD  .text  000000000000004b (GLIBC_2.2.5) memcpy

这里大概可以确定,编译程序的 memcpy 依赖于 GLIBC_2.14,而我们的运行系统版本太低,不支持 GLIBC_2.14

三、符号版本(Symbol Versioning)

看到memcpy@@GLIBC_2.14第一眼时,我们很容易粗略的猜测这个是符号的版本控制。
但是这些信息又是什么意思呢? 你有没有想去了解这些符号的版本到底是如何工作呢?

在这个文档(https://akkadia.org/drepper/symbol-versioning) 里,我们可以查阅到符号版本的详细信息。

原来,从glibc 2.1开始,引入了一种兼做 Symbol Versioning 的机制。
具体来说每个符号会对应一个版本号,在一个libc.so.6文件中可以包含一个函数的多个版本。

> nm /lib64/libc.so.6 | grep " memcpy"
000000000008e860 i memcpy@@GLIBC_2.14
0000000000089650 i memcpy@GLIBC_2.2.5

例如在上面的这个数据中,memcpy@@GLIBC_2.14 代表默认版本(两个@),memcpy@GLIBC_2.2.5则代表最低版本。
所以在编译机上,链接的可执行程序会指向 memcpy@@GLIBC_2.14,当它在另一台低版本的低于 2.14 版本的 glibc 机器上运行时就会找不到改符号。
在低版本的glibc机器上链接出来的可执行文件指向 memcpy@GLIBC_2.2.5,依然可以在高版本 glibc 上运行,因为高版本 glibc 库中包含了该符号。

四、解决方案

我们面对一个问题,肯定需要去想办法解决了。
google 之后,看了大量的资料,发现有三种解决方案。

搜索结果中,99% 解决方案都是升级运行环境的 glibc 。
这个在个人的 VPS 或者 小公司只有几台机器的情况下,还可以接受,折腾一下就行了。
但是这个在几十上百台服务器上,升级 glic 是可能的事情。
所以这个方法对我来说不可行。

然后剩下的 1% 解决方案中,又有 9 成的方法是在用到 memcpy 的 cpp 文件里加上 __asm__(“.symver memcpy,memcpy@GLIBC_2.2.5”);

然而,我的机器的 glic 版本没有版本控制,只有一个 memcpy。
当初我以为是 __asm__(“.symver memcpy,memcpy”);,结果没有解决问题。
经过查资料,最终在这里(https://gcc.gnu.org/wiki/SymbolVersioning )找到方法。

原来我只需要在对应的头文件里加上 __asm__(“.symver memcpy,memcpy@”); 即可。那个@必须加上。

这个方法的可行性测试通过了。
然而,我的项目文件不是一个,有几十个。各种库里都使用了 memcpy, 这时候上面的方法也不行了。

又经过大量查阅资料,在 stackoverflow 上(http://stackoverflow.com/a/20065096/1881299)了解到链接器有一个 wrap 参数,可以在链接的时候动态的替换符号。

简单来说,我们需要写一个通用的符号替换函数,然后在链接目标文件时,使用命令 -Wl,--wrap=memcpy 来主动指定 memcpy 的符号号版本,这样就可以对所有的文件进行符号替换了。

这样实现后,编译出的程序终于可以正常的运行了。

五、最后

linux 下的符号问题有很多,每次都会遇到不一样的。
对于 glibc 版本问题本以为只有升级这一个方案,没想到竟然有这么完美的不升级的方案,但是网上又是少之又少,甚是可惜。

这个问题查询资料时,发现一本不错的链接相关的电子书,分享给大家,公众号后台发送“The GNU linker”免费领取。
注:需要发送引号里面完整的名称,建议长按复制,然后去后台发送。
注2:这篇文章的参考文章,可以点击原文阅读,在原文里面可以得到相关链接。

六、参考资料

https://akkadia.org/drepper/symbol-versioning
https://blog.blahgeek.com/glibc-and-symbol-versioning/
https://gcc.gnu.org/wiki/SymbolVersioning
http://www.jmpcrash.com/?p=1120
https://blog.csdn.net/ilittleone/article/details/24460559
https://gist.github.com/nicky-zs/7541169
http://stackoverflow.com/a/20065096/1881299

-EOF-

本文首发于公众号:天空的代码世界,微信号:tiankonguse
原文地址:https://mp.weixin.qq.com/s/r7a3hm09yerbmfimT2yQzw

点击查看评论

关注公众号,接收最新消息

关注小密圈,学习各种算法

tiankonguse +
穿越