本文不考虑静态链接方式,很多库在静态链接的时候会有问题,比如libunwind,它的异常处理API会和gcc原有的冲突。还有一个显著的问题就是nss。它根据配置文件/etc/nsswitch.conf来动态决定加载哪个so,然后用这个so执行名称解析服务等等。(nss是glibc的一部分,是系统很基本的东西)。还有,jni的so,想要静态链接很难。意思就是说,我要编译一个so,但是这个so所依赖的其它库又都必须是静态链接的,很难,而且也许会引入很多BUG。出于种种原因,我完全放弃了静态链接。(程序采用静态链接完美世界的传统)
即便你的程序简单到只是一个hello world,那么也需要链接到libc.so。很明显,不同的glibc版本之间,差别很大,经常不兼容。那么我能不能在低版本的Linux上使用高版本的Linux的libc.so呢?
于是我做了一个测试,我这边主要有两种Linux系统:CentOS 5和CentOS 6。
CentOS 5下ld-linux-x86-64.so.2指向的是ld-2.5.so
CentOS 6下ld-linux-x86-64.so.2指向的是ld-2.12.so
如果强行把CentOS 5的这个so替换成CentOS6的那个,那么会发现任何elf都执行不了,
relocation error: /lib64/libc.so.6: symbol
_dl_tls_get_addr_soft, version GLIBC_PRIVATE not defined in file
ld-linux-x86-64.so.2
系统基本是僵死状态。(还好我今天用/lib64/ld-2.5.so ln -s -f ld-2.5.so ld-linux-x86-64.so.2的方式救回来了)
如果我们不替换ld.so,而只是替换libc.so,例如:
LD_LIBRARY_PATH=/home/changming/apps/lib64
ls (/home/changming/apps/lib64放的是CentOS6的libc.so)
那么会报告:error while loading shared libraries:
/home/changming/apps/lib64/libc.so.6: ELF file OS ABI invalid
用file查看一下:
CentOS 6的libc.so.6:
ELF 64-bit LSB shared object, AMD x86-64, version 1 (GNU/Linux), for GNU/Linux
2.6.18, not stripped
CentOS 6的libc.so.6:
ELF 64-bit LSB shared object, AMD x86-64, version 1 (SYSV), for GNU/Linux
2.6.9, not stripped
CentOS 6的libstdc++.so.6:
ELF 64-bit LSB shared object, AMD x86-64, version 1 (SYSV), stripped
这个差别在于elftype。
于是我从FreeBSD 9下面,把brandelf.c复制到Linux,稍作修改后编译。然后用它更改elftype。
./brandelf -t SVR4
/home/changming/apps/lib64/libc.so.6 apps/lib64/libm.so.6
然后在CentOS 5 下面用CentOS 6的ld.so执行CentOS 6的bash
LD_LIBRARY_PATH=/home/changming/apps/lib64
/home/changming/apps/lib64/ld-linux-x86-64.so.2 ./bash
error while loading shared libraries:
/home/changming/apps/lib64/libc.so.6: unexpected reloc type 0×25
这个问题在于,bash会fork新进程,而新进程采用哪个ld.so,是我无法控制的。
综上所述:用老的ld.so配合新的libc.so,只有两种结果:"ELF file OS ABI invalid”或 “unexpected reloc
type 0×25”。
结论:ld.so的版本必须和glibc的版本匹配。
但是,大多数情况下,这不是一个问题。因为大部分程序(我写的)是不会fork的。
我现在写好一个程序,当把它扔到别的系统上运行时,它对OS的依赖应当越小越好,否则难道我为每个发行版的每个主版本都编译一次?虽然大部分开源项目都是这么做的,但是我实在是不想。我希望只编译一次,并且编译时所采用的so和运行时采用的so是完全一样的!综上所述,如果不fork,那么you can !
如果我就是非要fork,那么怎么办呢?
答:对于无源代码的程序,改ELF文件的Program Headers。将PT_INTERP的值设置为我自己的ld.so。(原来的默认值是/lib64/ld-linux-x86-64.so.2)。改完之后可以用readelf这个命令检查一下。对于自己有源代码的程序,可以重新编译,加上–dynamic-linker ./ld-linux-x86-64.so.2 这样的参数。注意,对于setuid程序,这里一定要写绝对路径,否则就是一个安全漏洞哇。
在解决这些问题之后,只要我的程序没有用到Linux Kernel的新特性,那么就可以在相当大范围内的Linux上,自由执行了。
show几个脚本:
打包so,并扔到http server上:
#!/bin/bash
rm -rf /tmp/lib64tar
mkdir /tmp/lib64tar
cp /lib64/ld-linux-x86-64.so.2 /tmp/lib64tar
cp /lib64/libc.so.6 /tmp/lib64tar
cp /lib64/libdl.so.2 /tmp/lib64tar
cp /lib64/libgcc_s.so.1 /tmp/lib64tar
cp /lib64/libm.so.6 /tmp/lib64tar
cp /lib64/libpthread.so.0 /tmp/lib64tar
cp /usr/lib64/libstdc++.so.6 /tmp/lib64tar
tar -zcvf /tmp/lib64tar.tar.gz -C /tmp/ lib64tar
scp /tmp/lib64tar.tar.gz 10.4.1.27:/home/changming/public_html/glu/
自动安装脚本
(glu script):
class LinuxLib64{
def install = {
log.info
"Installing…"
def skeleton =
shell.fetch(params.linuxlib64url)
def distribution =
shell.untar(skeleton)
shell.rmdirs(mountPoint)
shell.mv(shell.ls(distribution)[0], mountPoint)
shell.toResource(mountPoint.path).list().each{ f ->
log.info f.path
shell.chmodPlusX(f)
}
log.info "Install
complete."
}
def createChild
= { args ->
return args.script
}
}
glu static model:
今天试了一下,从icu的网站下载为RHEL6编译的二进制包(一个tar包),然后在CentOS 5上解压到任意目录,这么执行:
“ /home/changming/apps/lib64/ld-linux-x86-64.so.2 –library-path
../lib:/home/changming/apps/lib64 ./uconv –list”
All Things Works Fine!