将高版本gcc生成的二进制部署到低版本Linux系统上
背景
近期做Linux开发,编译环境我用的gcc7.3,而生产环境是gcc4.4,很老的版本,连C++11都不支持,这样写起代码来会很憋屈。
于是就想着怎么能让我用上C++11。
直接升级系统/编译器自然是最好的选择,然而毕竟是生产环境,不那么容易动(哪怕是仅升级C和C++库)。于是研究目标就成为了,如何在完全不动系统环境的情况下,将高版本gcc编译产物部署到低版本系统上。
思路
查阅各种文献,一般有两种方案:
- 把C和C++库(libc.so和libstdc++.so)随程序一起发布,并配置
LD_LIBRARY_PATH
变量。 - 静态链接。
方法1的局限性比较大,只限于同进程的所有模块都在我控制下的情况。像是我实际的应用场景,生成一个so让别人加载的形式就不能使用这种方法,因为这实质上来说还是动了其他模块的“系统环境”。
方法2听起来比较完美,会把所有的依赖代码编译到bin内,从而不再需要依赖任何C/C++库——至少在MSVC上我们通常就是这么做的。
然而现实是只有C++库可以静态链接,C库是不行的,因为gcc的C库设计时就没打算让你静态链接,参考这里。
尝试
总之先尝试静态链接C++库,加上-static-libstdc++
参数,然后编译链接,查看符号
|
|
确认已经没有C++库的外部引用了,然后在服务器上尝试dlopen这个so,报错:
|
|
查看服务器GLIBC的最高支持版本,显示为2.12(即gcc4.4使用的版本)。
查看so所使用的C库函数,发现只有一个函数使用到了此版本的C库:
|
|
把这个符号重定向到低版本memcpy上吧,在编译时再加一个参数:
|
|
同时在工程中加入一个新的c文件:
|
|
再次编译,在服务器上执行dlopen,成功加载。
总结
通过加入-static-libstdc++
参数,以及重定向memcpy的方法,我们将编译产物引用的外部符号降低到GLIBC2.12之下,从而成功在只装有gcc4.4的机器上部署。
同时,使用这种方法时有一些需要注意的点:
- 跨模块的函数调用就全部使用C接口吧,C++的ABI从gcc5.X开始就已经不兼容了。
- 指针跨模块传递时,不要释放其他模块传过来的指针,因为可能使用的不是同一个堆。
- 此方法将2.14版本的memcpy换成了2.2.5版本的,这个版本的memcpy有个问题是,它其实是memmove……执行效率可能低上那么一丁点儿,如果非常介意这一点的话,可以从gcc源码中抠出memcpy.o文件编译到你工程里——不过这会牵扯上GPL协议,需要自己斟酌。