让C/C++程序一次编译,到处运行 (仅限Linux)

本文不考虑静态链接方式,很多库在静态链接的时候会有问题,比如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 0x25

这个问题在于,bash会fork新进程,而新进程采用哪个ld.so,是我无法控制的。

综上所述:用老的ld.so配合新的libc.so,只有两种结果:"ELF file OS ABI invalid"或 "unexpected reloc type 0x25"。

结论:ld.so的版本必须和glibc的版本匹配。

但是,大多数情况下,这不是一个问题。因为大部分程序(我写的)是不会fork的。

我想说一个什么事情呢? 我想说,Linux的这套动态链接库命名机制(soname、linker name、realname)并未能解决DLL hell的问题。参见:http://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html

我现在写好一个程序,当把它扔到别的系统上运行时,它对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:

{
    "agent": "10.4.1.14",
    "mountPoint": "/lib64",
    "initParameters": {
        "linuxlib64url": "http://10.4.1.27/~changming/glu/lib64tar.tar.gz",       
    },
    "entryState": "installed",
    "parent": "/",
    "metadata": {},
    "tags": [],
    "script": "http://10.4.1.27/~changming/glu/linuxlib64.groovy"   
}

今天试了一下,从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!

此博客中的热门博文

在windows下使用llvm+clang

少写代码,多读别人写的代码

tensorflow distributed runtime初窥