又好久没有更新博客了………
这是一个困扰了我好久好久的问题.首先我们来说一下需求,我们需要在同一台linux主机上安装和运行多个版本的Glibc,为什么呢? 不知道大家有没有遇到这种情况,一般在公司里,多人共享一台开发机,开发编译都在同一台机子上,每个人需要安装一些自已喜欢的软件,比如tmux,ycm之类的,但是安装软件的时候又不想要影响其它人的工作,所以一般都是自已编译安装在自已的home目录下,然后修改自已用户的$PATH环境变量,事情到这里应该是比较顺利的.
但是呢,大部分公司的开发编译机呢,由于历史的原因,上面的各种库的版本都比较老,由于升级成本比较高,所以线上跑的程序依赖库的版本也是比较老的版本,所以你想要在开发编译机上安装一些比较新工具,体验一下新特性什么的,由于一些库的版本太低,就没有办法了.(就例如YCM,目前依赖最低的Glibc是2.14,但是很多公司的机器Glibc的版本是2.3,2.9,2.12…所以就gg了…)
一个比较常见的例子就是所安装的软件依赖较高的Glibc版本(libc.so: version `GLIBC_2.xx’ not found),有同学说那你就像安装其它软件一样,安装一个高版本Glibc在你自己的用户下修改$PATH和$LD_LIBRAY_PATH就可以了呗.至于你有没有试过,反正我是试过了n多次.如果新旧两个版本的Glibc不兼容的话(好像都不怎么兼容的样子…),修改生效后执行大部分的命令都会coredump掉.所以不要嫌我啰嗦,我们先来讲一下这个Glibc是干嘛用的.
从Glibc的官方介绍我们了解到,它是GNU系统和GNU/Linux系统的核心库,是一个C的API标准库,所以几乎运行在Linux系统上的所有程序都是依赖Glibc的..bla..bla..所以你升级掉了Glibc的话,open, read, write, malloc, printf...等等这些基本的函数的实现应该指向新版本Glibc,程序运行时就很有可能会挂掉...实测旧版本glibc2.12的系统,另安装2.14,修改$LD_LIBRAY_PATH,连执行ls命令都会coredump.
$LD_LIBRAY_PATH又是什么?为什么要修改它?知道了的同学直接跳过哈.我们先来看介绍 ,一个编译好的程序可能会依赖一些动态库,也就是.so文件,最基本的就是Glibc的动态库文件libc.so,那这些动态库文件要去哪是找呢?在运行程序的时候会先通过$LD_LIBRAY_PATH这个环境变量指定的目录去搜索动态库,所以修改$LD_LIBRAY_PATH,让所有的程序运行时先搜索到新版本的Glibc,程序就会加载它.所以在执行程序的时候遇到类似error while loading shared libraries: xxx.so: cannot open shared object file: No such file or directory的错误在两种原因:可能你没有安装这个库文件,也可能是已经安装的这个库文件不在搜索的目录目录内,可以把这个xxx.so文件所在的目录添加到$LD_LIBRAY_PATH中,再运行就可以了.
可以通过ldd命令查看程序依赖的动态库
但是在没有修改$LD_LIBRAY_PATH变量的时候,程序又是怎么找的呢?
$LD_LIBRARY_PATH为空
这里我们就先来看程序运行时动态库加载的过程,首先来看刚才用ldd命令列出来的/lib64/ld-linux-x86-64.so.2这个文件,查看wiki和man文档我们发现这个玩意动态链接器 (Dynamic linker),用来帮助程序在运行时寻找动态库文件.程序运行时先告诉ld-linux.so我要加载一个叫a.so的动态文件.ld-linux.so表示ok,我帮你找.一般情况下(在没有指定DT_RPATH和DT_RUNPATH的情况下
)
1.先查找$LD_LIBRARY_PATH中的目录
2.再查找缓存文件/etc/ld.so.cache
3.再默认的目录/lib,/usr/lib或者是/lib64和/usr/lib64
ld.so.cache缓存文件可以通过ldconfig命令来配置,具体就不多说了
而在用gcc编译一个程序的时候加载动态库文件的顺序又是怎么样的呢?
1.由gcc写死的路径,通过gcc –print-search-dirs看到,这里加载的是一些 libgcc_s.so的库
2.$LIBRARY_PATH变量指定目录或者是编译-L参数指定的目录
3.由ld命令设定的目录,这部分也是在编译ld这个程序时写死的,可以通过“ld –verbose | grep SEARCH”来查看
那么问题来了,程序运行的时候是怎么知道ld-linux.so的路径的呢?这个路径是在编译gcc的时候就写死的. 意思就是,在编译gcc的时候,会写死一个ld-linux.so路径,然后在用这个gcc编译所有程序的时候,就会把这个路径写死在所编译的程序的ELF的头里,可以通过readelf -l命令看到
回到刚才的问题,把新版本的Glibc添加到$LD_LIBRARY_PATH加会导致其它常用的程序如ls会挂掉,那我能不能把编译我想要的工具时指定使用新版的Glibc呢?当然可以,去google一下你可以看到so上的这个问题的答案 在编译的时候加上
-Wl,–rpath=/path/to/newglibc \
-Wl,–dynamic-linker=/path/to/newglibc/ld-linux.so.2
在编译的时候用rpath参数写死搜索动态库的路径来指定使用新版本Glibc
这个方法可行性有多高呢?如果你需要编译自已写的一个程序,测试它在不同Glibc版上的兼容性,这个方法再合适不过了.但是如果你需要的是编译一个别人写的工具,
1.首先这个工具是一套软件,有可能包括很多文件..需要修改巨多的make文件,有可能带来n多未知的错误
2.即使修改后可以成功编译通过,如果需要对工具进行升级,也会很麻烦
3.最重要的一点是,这个工具可能会依赖很多其它的库,所依赖的这些库的版本有可能也是需要升级到新版本Glibc,这样的依赖关系很复杂,你得一个一个的重新修改编译,最后很可能有一些未知的bug,根本编译不通过(不要问我为什么……..)
那还有什么办法可以一劳永逸的解决问题?google也查不到,于是我就在思考
别人在编译新版本的Linux发行版的时候,如果需要升级到高版本的Glibc时是怎么做的呢?
有没有可能在一个linux的host里运行不同的两套软件系统呢?
最后终于发现一个神奇的项目Linux From Scratch Project ,尝了无数次,终于成功了…请看下篇吧…
参考: