程序上经常有在这台Linux上编译,然后放到另一个Linux上运行的情况。
如果Linux版本差别不大或都是ubuntu或centos系列还好。
如果不是一个系列很容易出现GLIBC 找不到的情况。
尤其是ubuntu上编译,然后放到centos系列。因为centos为了追求所谓的稳定,基本用的都是N年前的东西,生怕用新的东西把它给搞的不安全了。
ImportError: /lib64/libm.so.6: version `GLIBC_2.29' not found (required by /home/ma-user/_openjar.so)
这个问题的本质就是,编译所用的操作系统中GLIBC的版本高,但是运行的机子上的GLIBC版本低(glibc版本低于2.29),没有这个函数接口。
比如这里 pow函数,存在于libm.so中,
目标系统上libm.so中pow函数是 pow@@GLIBC_2.17 ,但所用的是pow
# readelf -s /usr/lib64/libm.so.6 |grep pow
37: 000000000003a398 2240 FUNC GLOBAL DEFAULT 14 __pow_finite@@GLIBC_2.17
113: 000000000004a250 864 FUNC WEAK DEFAULT 14 powf32@@GLIBC_2.27
117: 0000000000031dd0 48 FUNC WEAK DEFAULT 14 cpowf64x@@GLIBC_2.27
194: 0000000000010bb8 320 FUNC WEAK DEFAULT 14 powf64@@GLIBC_2.27
291: 000000000000ef38 680 FUNC WEAK DEFAULT 14 powf128@@GLIBC_2.27
306: 000000000004a250 864 FUNC GLOBAL DEFAULT 14 __powf_finite@@GLIBC_2.17
336: 0000000000010bb8 320 FUNC WEAK DEFAULT 14 pow@@GLIBC_2.17
但程序所需的是 pow@@GLIBC_2.29, 所以就会运行的时候找不到GLIBC_2.29的版本。
[ma-user tdf]$nm _openbbf.so |grep pow
000000000074a180 u _ZZN8nlohmann12json_v3_11_16detail9dtoa_impl36get_cached_power_for_binary_exponentEiE13kCachedPowers
U pow@@GLIBC_2.29
所以应该怎么办呢? 通过ldd可以看到程序所依赖的.so库
/tmp> ldd myapp
linux-vdso.so.1 => (0x00007fff7a1ff000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f1f8a765000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f1f8a4e3000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f1f8a2cc000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f1f89f45000)
/lib64/ld-linux-x86-64.so.2 (0x00007f1f8aaa9000)
/tmp>
可以看到有libstdc++, libm, libgcc, and libc
其中libstdc++是gcc的c++ 动态库。
libc.so 和libm.so 都是GLIBC的一部分。
通过objdump可以看到 我们的程序myapp所调用的一些接口函数的版本号。
/tmp> objdump -T myapp | grep GLIBC_
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 ungetc
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.3 __ctype_toupper_loc
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fputc
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 free
…
/tmp>
查看程序中所用函数的GLIBC、GLIBC++版本号
ppl@dell:~/python$ nm --dynamic --undefined-only --with-symbol-versions _openbbf.so \
| grep GLIBC | sed -e 's#.\+@##' | sort --unique
GLIBC_2.14
GLIBC_2.15
GLIBC_2.16
GLIBC_2.17
GLIBC_2.2.5
GLIBC_2.25
GLIBC_2.28
GLIBC_2.29
GLIBC_2.3
GLIBC_2.3.2
GLIBC_2.3.4
GLIBC_2.4
GLIBC_2.7
GLIBC_2.8
GLIBC_2.9
GLIBCXX_3.4
GLIBCXX_3.4.11
GLIBCXX_3.4.14
GLIBCXX_3.4.15
GLIBCXX_3.4.18
GLIBCXX_3.4.19
GLIBCXX_3.4.20
GLIBCXX_3.4.21
GLIBCXX_3.4.26
GLIBCXX_3.4.9
第一点可以做的是把gcc的 C++库/C库 给静态链接了。防止这些链接有问题。
-static-libstdc++ 选项可以让g++静态链接c++库,就是 libstdc++.so
-static-libgcc 可以静态链接gcc的C库 就是libgcc_s.so
g++ -static-libstdc++ -static-libgcc
第二点是 static 生成静态二进制,但这可能存在问题
-static 选项会把所有的库静态链接,但是这个可能存在问题,比如不兼容。
还有一个问题是License的问题,有些GPL的代码如果静态链接了,你的程序是不是也要GPL。
虽然GLIBC对静态链接是有豁免license的。但是其他代码可说不准。
虽然License有时候也不是问题。(原因你懂的)
g++ -static
搞一个跟目标机一样的系统,进行编译。
如果目标机不确定,你又希望最好的兼容性,就尽量用低版本的系统进行编译。比如有人用ubuntu-16.04进行编译,编译出来的在ubuntu20.04上肯定是能用的。
现在docker技术已经非常发达了,都不需要装虚拟机。
直接在docker 镜像中进行编译就可以了。
比如:kroggen/ubuntu-16.04-gcc 这个镜像。
升级gcc
但是这个时候又会有另外一个问题。 就是旧版本系统上的编译器有点旧。
比如centos 7.7上的默认编译是gcc -v4.8.2,太老掉牙了。
现在谁不是c++14 c++17特性用的飞起。
随便一个都要gcc v7.0 v9.3甚至更高。
当然自己编译一个高版本的gcc是可以的。但centos上有epel已经提供了可以yum的高版本gcc,直接进行yum就可以了。
# install gcc 9 on centos 7
# https://gist.github.com/superzscy/ea619f881c92b8cdae8faaf782d0f031
yum install -y centos-release-scl
yum install -y devtoolset-9
scl enable devtoolset-9 bash
ubuntu上也有提供一些低版本系统上用的gcc 9
#!/usr/bin/env sh
sudo apt-get update -y && \
sudo apt-get upgrade -y && \
sudo apt-get dist-upgrade -y && \
sudo apt-get install build-essential software-properties-common -y && \
sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y && \
sudo apt-get update -y && \
sudo apt-get install gcc-9 g++-9 -y && \
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 60 --slave /usr/bin/g++ g++ /usr/bin/g++-9 && \
sudo update-alternatives --config gcc
# select gcc-9
常见的系统所用的glibc的版本.
Distribution | glibc version |
---|---|
Debian 7 | 2.13 |
Debian 8 | 2.19 |
Debian 9 | 2.24 |
CentOS 6 | 2.12 |
CentOS 7 | 2.17 |
Ubuntu 14.04 | 2.19 |
Ubuntu 16.04 | 2.23 |
Ubuntu 18.04 | 2.27 |
Ubuntu 20.04 2.31
查看系统的glibc的版本可以用以下命令:
ubuntu 上 apt search glibc
apt search glibc
abicheck/focal,focal 1.2-5ubuntu1 all
binary compatibility checking tool
clisp/focal 1:2.49.20180218+really2.49.92-3build3 amd64
GNU CLISP, a Common Lisp implementation
fakeroot-ng/focal 0.18-4build2 amd64
Gives a fake root environment
glibc-doc/focal-updates,focal-updates 2.31-0ubuntu9.12 all
GNU C 库:文档
glibc-doc-reference/focal,focal 2.30-1ubuntu1 all
GNU C 库:文档
glibc-source/focal-updates,focal-updates 2.31-0ubuntu9.12 all
GNU C 库:源代码
欧拉系统:
yum info glibc-devel
[root@host-13 ~]# yum info glibc-devel
Last metadata expiration check: 2:18:51 ago on Fri 15 Dec 2023 08:56:55 AM CST.
Installed Packages
Name : glibc-devel
Version : 2.28
Release : 49.oe1
Architecture : aarch64
Size : 9.4 M
Source : glibc-2.28-49.oe1.src.rpm
Repository : @System
From repo : anaconda
Summary : The devel for glibc
URL : http://www.gnu.org/software/glibc/
License : LGPLv2+ and LGPLv2+ with exceptions and GPLv2+ and GPLv2+ with exceptions and BSD and Inner-Net and ISC and Public Domain and GFDL
Description : The glibc-devel package contains the object files necessary for developing
: programs which use the standard C libraries. Besides, it contains the
: headers. Thus, it is necessory to install glibc-devel if you ned develop programs.
用patchelf把版本的信息删掉,自动链接系统上的相关函数。
patchelf位于:
GitHub - NixOS/patchelf: A small utility to modify the dynamic linker and RPATH of ELF executables
用法如下:
先进行查看,然后去掉函数携带的版本号。
$ 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
但这种太猛了,不一定起作用。需要谨慎。
还有一个办法,就是gcc提供了一种指定库函数版本号的方法。
__asm__(".symver SYM,SYM@GLIBC_VERSION");
详情参见这个项目:
GitHub - wheybags/glibc_version_header: Build portable Linux binaries without using an ancient distroBuild portable Linux binaries without using an ancient distro - GitHub - wheybags/glibc_version_header: Build portable Linux binaries without using an ancient distrohttps://github.com/wheybags/glibc_version_header
这个项目有点老了,有些函数不一定行,但是思路是没有问题的。
像这样,在程序代码中指定链接的GLIBC的版本号。
#include
__asm__(".symver pow,pow@GLIBC_2.17");
int fun(){
return std::pow(2, 3);
}
参考:
linux - How can I link to a specific glibc version? - Stack Overflow
Florian Weimer - Re: how to compile a lower gcc/glibc version compatible binary?
https://insanecoding.blogspot.com/2012/07/creating-portable-linux-binaries.html