由于对CMake掌握不熟导致的多重符号问题

由于对CMake掌握不熟导致的多重符号问题

问题描述

  1. 程序结构:
    在这里插入图片描述
    如上图所示,main.o调用了sdpf.o中的函数,sdpf调用了base.o中的函数,base.o和crypot.o相互调用。

  2. base.o被编译为了base.a,crypto.o编译为crypto.a,sdpf.o与base.a和crypto.a一起编译为动态库scss.so。如下图所示:
    在这里插入图片描述

  3. 对于scss.so,使用了-Wl,--version-script,将接口以外的其他符号全部隐藏(变为LOCAL属性)

  4. 在a.out中调用scss.so(base.a中的函数)的一个接口函数,接口函数中初始化了一个类的静态成员变量(一个字符串),记为_s_var;

  5. 在crypto.a中也用了这个类静态变量_s_var

问题:base.a中初始化_s_var的值后,printf可以正确输出,但是crypto.a获取到的_s_var的值是错误的,是默认值。

原因

经过一番调试,发现是有两个同名的_s_var符号,一个在a.out中,另一个在scss.so中。

但是scss.so明明已经进行了符号隐藏,那a.out中的符号是哪儿来的呢?

经过一翻搜索,发现原来是我们使用的CMake为目标链接库的语句导致的:TARGET_LINK_LIBRARIES语句会默认传递依赖关系。

也就是说编译scss.so时的依赖关系,会自动传递给a.out,如下图所示:
在这里插入图片描述
那_s_var符号又为何会在a.out中也有一份呢?原来是生成scss.so时,传递给连接器的静态库的顺序错误导致的。在传递给连接器时,先传递了crypto.a,再传递了base.a,而sdpf.o直接依赖于base.a。

回顾下链接器使用静态库解析引用的过程(深入理解计算机系统 第2版 7.6.3小节),结合我们的情形,链接器在编译scss.so时,引用解析的过程如下:

链接器会维持一个可重定位目标文件的集合E,一个未解析的符号集合U,一个在前面输入文件中已定义的符号集合D。初始时,这三个集合都是空的:

  1. ld先接收sdpf.o文件,由于是一个目标文件,ld会将sdpf.o加入到集合E,并将sdpf.o中的符号加入到集合D,将sdpf引用的且不在sdpf.o中的符号加入到集合U中;
  2. 之后将crypot.a传递给ld,这是一个存档文件,ld会尝试匹配U中未解析的符号和存档文件成员定义的符号。如果存档文件中的某个成员m,定义了一个符号来解析U中的一个引用,那么就将m加入到E中,并且修改U和D来反映m中的符号定义和引用。由于sdpf.o不直接引用crypto.a中的函数,所以crypto.a中的符号不会被加入到U、D、E中,而是被丢弃
  3. 之后将base.a传递给ld,这也是一个存档文件,ld会将其定义的同时被sdpf.o引用的符号加入到集合D中,并将对应的目标文件加入到集合E中,将base.a中引用的且不在集合D中的符号加入到集合U中,由于base.a引用了crypto.a中的符号,所以集合U中会有crypto.a中的部分符号

处理完三个文件,scss.so的集合U不为空(crypto相关的符号引用解析失败)

之后将main.o和scss.so传递给链接器,用于生成a.out。过程如下:

  1. 先将main.o传递给ld,这是一个目标文件,且引用了base.a中的符号,ld会将这些被引用的符号加入到集合U中
  2. 之后传递scss.so给ld,scss.so定义了集合U中的base.a相关的符号,但同时也有未解析的符号(crypto相关的符号,比如_s_var),这些符号也会加入到集合U中
  3. 之后由于TARGET_LINK_LIBRARIES的依赖自动传递,会将crypto.a传递给ld,crypto.a定义了集合U中crypto相关的符号,所以可以解析相关的应用,但由于crypto.a还引用了base.a中的函数,而scss.so又做了符号隐藏,所以此时ld无法找到对应的函数来解析cryto.a对base.a的引用,ld会将这些引用加入到集合U中,比如符号_s_var,此时就将符号_s_var引入到了a.out中
  4. 之后将base.a传递给ld,base.a中定义了符号_s_var,可以用于解析集合U中的引用_s_var,所以ld会将其加入到集合D中
  5. 至此,a.out的全局符号表中包含了符号_s_var,scss.so中也包含了_s_var,而只有scss.so中的_s_var被初始化了,a.out中的_s_var初始时的默认值
  6. 当crypto.a读取_s_var时,由于ld在进行引用解析时,重定位到的是a.out的符号表中的_s_root,所以crypto.a拿到的是默认状态的_s_var,导致了数据的读取内容错误

解决

这个问题的产生,有两方面的原因:

  1. 编译scss.so时,传递给ld的静态库顺序不对,导致了crypto.a中的符号没有被加入到scss.so的集合D中,如果编译a.out时只传递scss.so给ld,会发生链接错误,找不到符号
  2. TARGET_LINK_LIBRARIES语句的依赖自动传递特性,将crypto.a和base.a又传递给了链接器,这就掩盖了本应发生的链接错误。

找出原因,要解决问题,也使有两个方面:

  1. 在编译scss.so时,正确传递静态库给ld,比如按顺序 base.a crypto.a base.a的顺序传递,这样scss.so的集合U就不会包含crypto.a中的相关符号,之后在编译a.out时,即使cmake传递crypto.a和base.a给链接器,链接器也不会将符号_s_var加入到集合D中
  2. 在调用cmake的语句TARGET_LINK_LIBRARIES时,加入PRIVATE关键字,阻止依赖的自动传递,及时发现scss.so的符号引用错误
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值