c和c++开发人员或多或少都使用过Linux动态库,但是很多时候我们都不会去深入了解其中的一些细节和原理,直到自己的程序出现莫名其妙的问题后才会去着手解决,我也是在遇到一些动态库的问题后才去深入寻找解决办法。比如项目中加载了多个动态库,自己的动态库本来只想调用自己动态库中的函数,但是却调用到了其他动态库的同名函数上了,导致运行时的程序崩溃。还有项目中用到了多个版本的openssl,这些版本的openssl怎么共存的问题。

符号冲突暴露的时机可能是编译期,也可能是运行时。编译期的符号冲突一般不会产生太大的影响,如果是自己的项目代码, 只要找到冲突的符号,重命名其中某个符号就能解决,但是如果是其他外部库的代码,这个就不好改别人的代码,但是也可以将冲突的外部库进行封装来解决。如果是运行时的的符号冲突,一般就是动态库导致的。

在Linux下编译动态库的时候,所有的符号默认都是导出的,也就是动态库中的函数名,类名等,在外部都是可见的。我们在使用动态库没有出现问题之前,都不会去关注这些动态库中的符号是不是导出的。大多数时候,动态库中符号冲突也不会出现,因为出现不同动态库中相同的函数名或者类名的情况也是很少的。顺便提一下,windows下,dll动态库中的符号默认是不导出的,只有在需要导出的函数或者类名前加__declspec(dllexport)才会导出。

那linux下有没有类似window下的显式导出功能呢,有的。下面是介绍一种常用的只导出指定符号的方法。

1、加编译器选项fvisibility=hidden,加了这个选项后,默认的符号都不会导出

2、在需要导出的函数或者类名前加__attribute__ ((visibility("default")))

只导出几个必须的符号,就大大降低了多个动态库间的符号冲突问题。然而,这还不能完全解决问题,很多动态库在制作的时候都是默认的把所有符号导出,你没法保证自己的动态库不错误地引用到其他动态库中的符号。解决的办法也有两种:

一种是在编译期解决,就是在编译动态库的是加参数-Wl,-Bsymbolic 这个参数是传给链接器的,这个编译参数的作用是:优先使用本动态库中的符号,而不是全局符号。这样即使其他动态库导出的符号和自己动态库中的符号同名,冲突也不会发生,运行自己动态库程序的时候会使用自己本动态库中的函数和类。

一种是在加载动态的是时候解决,如果你没法重新编译动态,可以在加载动态库的时候自己使用dlopen函数加载动态库,然后在增加RTLD_DEEPBIND这个标志,这个标志的解释是这样的:

  • RTLD_DEEPBIND (since glibc 2.3.4)将符号的查找范围放在此共享对象的全局范围之前。这意味着自包含对象将优先使用自己的符号,而不是全局符号,这些符号包含在已加载的对象中

解决动态库符号冲突的两个方法,一个是减少导出的符号,一个是优先使用本动态库中的符号,这样就能最大限度的减少动态库间的符号冲突。

从以上的可以看出,解决符号冲突的一个利器是封装,把代码封装成动态库,只暴露几个必须的符号,对外部看来,表现得就像一个黑盒子。

关于多个openssl版本共存的解决方案

就举我自己遇到的例子,项目中需要用到sm2加密,因为项目很早就引入的openssl,因此原来项目中使用的openssl的版本很低,不支持sm2加密,而如果要升级openssl,因为兼容问题,则以前的很多代码需要修改,代价太大。因此只能找其他的实现方案,后来再找到gmssl这个开源库有sm2加密,但是这个库也依赖于openssl,而且是比较高的版本,这样就会出现项目中要用到两个不同的openssl版本的情况。经过艰苦的百度研究之后,找到解决办法,就是封装。编译gmssl这个库的时候把它编译成静态库,我们暂且认为生成的静态库为gmssl.a,这个静态库中的符号也是全部导出的,然后再在gmssl.a的基础上,进一步封装sm2加密和解密的方法,封装成gmutil.so动态库,完全屏蔽openssl的头文件,而且这个gmutil.so动态只导出sm2加密和sm2解密的函数符号,这样在gmutil.so的调用者看来,它内部引用的openssl对外部看来就是完全不可见的,而且gmutil.so编译的时候也指明-Wl,-Bsymbolic参数,这样就不会引用到外部的openssl版本的符号。

这里需要提到一个很有用的编译选项:-Wl,--exclude-libs,ALL  这个选项的作用是隐藏依赖的静态库符号。因为我编译gmssl.a静态库的时候,是用别人制作的makefile编译的,它里面的符号全部导出,即使在编译gmutil.so的时候,使用编译选项fvisibility=hidden,gmssl.a里面的符号也还是全部导出的。因此,加了-Wl,--exclude-libs,ALL后才隐藏了gmssl.a里面所有的符号。

下面是我参考的博文:

(18条消息) 多个库使用openssl库 导致编译不过 - CSDN

(20条消息) linux c解决多个第三方so动态库包含不同版本openssl造成的符号冲突_found的博客-CSDN博客_openssl 多版本共存

主要是参考上面这篇文章的解决思路来展开

Shared Library Symbol Conflicts (on Linux) (holtstrom.com)

避免踩坑系列!关于GmSSL与OpenSSL的兼容安装 - 简书 (jianshu.com) -- 这个推荐gmssl库静态链接openssl库

C++库符号冲突杂谈 - 简书 (jianshu.com)

(20条消息) linux动态链接库导出函数控制_zz460833359的博客-CSDN博客_linux 动态库导出函数

上面这篇文章告诉我们在动态库中怎么只导出指定的符号(函数),其中一个办法是通过gcc命令的-fvisibility=hidden 选项和 "__attribute__ ((visibility("default")))" 语法扩展 可以得到 vc中的__declspec(dllexport)"的效果

(20条消息) linux下动态库的符号冲突、隐藏和强制优先使用库内符号_mandagod的博客-CSDN博客

上面这篇文章就写了怎么在编译期间就指定优先使用动态库自己的符号,即编译选项-Wl,-Bsymbolic

怎么避免G++导出SO的符号? - 知乎 (zhihu.com)

上面的文章给出了一个很重要的编译参数:-Wl,--exclude-libs,ALL 隐藏依赖的静态库符号

还有查看so库的导出符号,使用:

readelf -s so文件 | grep GLOBAL

关于动态库导出符号为什么要加 extern "C"

因为如果用的是C++编译器来编译代码,C++会有重载,编译某个函数的时候会改变这个函数的名称,导致原本的函数名称找不到,外部在调用这个函数的时候会找不到符号。比如test()这个函数,C++编译后的函数名称可能变成testEF_,而不是原本的test符号。extern “C”就是使用C的编译方式,这样就不会发生函数重命名

关于动态库加-fPIC编译选项

是为了生成位置无关的代码,这样多个程序就有可能共享同一个动态库。如果不加这个选项,动态库被加载的时候都要进行地址重定向到自己的进程空间,这样导致每一个使用这个so的进程都会拷贝一份副本。而加了-fPIC这个选项,动态库加载的时候就不需要重定向地址,及位置无关代码,这样多个进程就可以共享同一个so。

关于导出函数带命名空间的问题

如果想导出带命名空间的函数,那么在定义的时候一定要在前面加上命名空间,否则编译器会生成不正确的符号

例如.h头文件:

namespace GM{extern "C" UNIX_EXPORT void Test();}

则在cpp定义文件上必须这么写:

void GM::Test(){cout << "this is a test" << endl;}

如果定义的时候没有加GM::,则生成的符号为:

[root@KF-CFT-AP7 gmutil]# readelf -s libgmutil.so | grep Test
  5449: 000000000003b165    46 FUNC    LOCAL  DEFAULT   11 _Z4Testv

如果加了GM::,生成的符号为:

[root@KF-CFT-AP7 gmutil]# readelf -s libgmutil.so | grep Test
   152: 000000000003b165    46 FUNC    GLOBAL DEFAULT   11 Test
  6914: 000000000003b165    46 FUNC    GLOBAL DEFAULT   11 Test

解决Linux多个动态库间的符号冲突问题相关推荐

  1. 【Linux学习】动态库和静态库

    目录 一.动静态库的概念和原理 1. 认识动静态库 2. 动静态库的概念 3. 动静态库的原理 二.动静态库的生成与打包 1. 静态库的生成与打包 2. 动态库的生成与打包 三.动静态库的使用 1. ...

  2. Linux中的动态库和静态库(.a/.la/.so/.o)

    为什么80%的码农都做不了架构师?>>>    Linux中的动态库和静态库(.a/.la/.so/.o) Linux中的动态库和静态库(.a/.la/.so/.o) C/C++程序 ...

  3. Linux下的动态库和静态库

    什么是库? 在 Linux 开发时,我们经常会看到一些形如 xxx.so 的名称出现,其中 so 是 Shared Object 的缩写,即可以共享的目标文件,也就是我们所称为的动态链接库,和在 Wi ...

  4. linux加载动态库问题

    当我们在linux系统引用动态库时,经常会遇到一个问题,加入我们需要的动态库没有在系统的默认目录下,我们编译时使用-L指定了动态库的路径,编译时没有问题,但是执行调用该动态库的可执行文件时,却提示找不 ...

  5. Linux系统中动态库和静态库的区别

    Linux系统中"动态库"和"静态库"那点事儿 今天我们主要来说说Linux系统下基于动态库(.so)和静态(.a)的程序那些猫腻.在这之前,我们需要了解一下源 ...

  6. linux gcc 制作动态库

    编译与位置无关的代码,生成.o,关键参数 -fPIC createlibso目录下 ├── cheng.c ├── chu.c ├── head │ └── test.h ├── jia.c └── ...

  7. 查看so库中是否有某个定义_论Linux ELF中动态库符号重定义利用 属性/Linker 做隐藏的手法...

    假如libgetthree.so libgetseven.so , 同时这两个so内部都用了internal_do_calculation()函数,并且各自定义了自己的internal_do_calc ...

  8. linux 下的动态库制作 以及在python 中如何调用 c 函数库

    linux 下的动态库制作 以及在python 中如何调用 c 函数库 动态库: 动态库又称动态链接库英文为DLL,是Dynamic Link Library 的缩写形式,DLL是一个包含可由多个程序 ...

  9. 【linux】程序找不到动态库.so的解决办法|查看.so动态库信息|.so动态库加载顺序

    目录 找不到.so解决方法 方法一:添加环境变量 方法二:复制so文件到lib路径 方法三:(推荐)添加ldconfig寻找路径 方法四:在编译目标代码时指定该程序的动态库搜索路径 让程序在本目录找到 ...

最新文章

  1. 【怎样写代码】对象克隆 -- 原型模式(三):原型模式
  2. 运维分级发布_华为杨超斌发布面向“1+N”目标网的5G全系列解决方案
  3. 休眠锁定模式– OPTIMISTIC_FORCE_INCREMENT锁定模式如何工作
  4. Java+Jmeter接口测试
  5. 5 SD配置-企业结构-定义-定义销售组
  6. 整数分解为若干项之和
  7. 你可能不知道的关于 Git stash 的技巧
  8. Shutdown In Period 1.0
  9. revit 转换ifc_Revit官方教程:Revit模型如何导成IFC格式?
  10. BooKu手机电子书阅读器,正式推出了
  11. 【无标题】灵遁者沉思:每个人都有“第三只眼睛”
  12. 一阶、二阶和三阶随机占优
  13. 联想台式机无法从U盘启动的解决方案
  14. linux定时任务crond那些事!
  15. java-jna 怪物遍历涉及到的二叉树
  16. 图像超分辨率综述学习之:Deep Learning for Image Super-resolution A Survey
  17. vue进阶:基于vue-cli3.x创建项目(搭建手脚架)
  18. PT静态时序分析 第三课 第四课
  19. elementui登录界面的详细介绍
  20. c语言计算时间差的程序小时和分钟,C语言输入两个时间(同一天的两个时和分),计算其时间差,输出相差几小时几分钟?...

热门文章

  1. 时序分析基本概念介绍Skew
  2. OPC协议学习笔记?
  3. 机器字长,存储字长,指令字长及其关系
  4. 手把手教你用Python搭建IP代理池
  5. selenium 代理
  6. 同城零售胜负手:饿了么把阿里新零售串联落地
  7. 基于Java毕业设计中学生视力系统源码+系统+mysql+lw文档+部署软件
  8. 绿卡日记:2020-09-25
  9. 蘑菇代理调用函数,可以申请试用,给你个url,然后直接用我写好的函数
  10. 我的创作二周年纪念日