问题描述
-
程序结构:
如上图所示,main.o调用了sdpf.o中的函数,sdpf调用了base.o中的函数,base.o和crypot.o相互调用。 -
base.o被编译为了base.a,crypto.o编译为crypto.a,sdpf.o与base.a和crypto.a一起编译为动态库scss.so。如下图所示:
-
对于scss.so,使用了
-Wl,--version-script,
将接口以外的其他符号全部隐藏(变为LOCAL属性) -
在a.out中调用scss.so(base.a中的函数)的一个接口函数,接口函数中初始化了一个类的静态成员变量(一个字符串),记为_s_var;
-
在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
。初始时,这三个集合都是空的:
- ld先接收sdpf.o文件,由于是一个目标文件,ld会将sdpf.o加入到
集合E
,并将sdpf.o中的符号加入到集合D
,将sdpf引用的且不在sdpf.o中的符号加入到集合U
中; - 之后将crypot.a传递给ld,这是一个存档文件,ld会尝试匹配U中未解析的符号和存档文件成员定义的符号。如果存档文件中的某个成员m,定义了一个符号来解析U中的一个引用,那么就将m加入到E中,并且修改U和D来反映m中的符号定义和引用。由于sdpf.o不直接引用crypto.a中的函数,所以crypto.a中的符号不会被加入到U、D、E中,而是被丢弃
- 之后将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。过程如下:
- 先将main.o传递给ld,这是一个目标文件,且引用了base.a中的符号,ld会将这些被引用的符号加入到集合U中
- 之后传递scss.so给ld,scss.so定义了集合U中的base.a相关的符号,但同时也有未解析的符号(crypto相关的符号,比如_s_var),这些符号也会加入到集合U中
- 之后由于
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中 - 之后将base.a传递给ld,base.a中定义了符号_s_var,可以用于解析集合U中的引用_s_var,所以ld会将其加入到集合D中
- 至此,a.out的全局符号表中包含了符号_s_var,scss.so中也包含了_s_var,而只有scss.so中的_s_var被初始化了,a.out中的_s_var初始时的默认值
- 当crypto.a读取_s_var时,由于ld在进行引用解析时,重定位到的是a.out的符号表中的_s_root,所以crypto.a拿到的是默认状态的_s_var,导致了数据的读取内容错误
解决
这个问题的产生,有两方面的原因:
- 编译scss.so时,传递给ld的静态库顺序不对,导致了crypto.a中的符号没有被加入到scss.so的集合D中,如果编译a.out时只传递scss.so给ld,会发生链接错误,找不到符号
TARGET_LINK_LIBRARIES
语句的依赖自动传递特性,将crypto.a和base.a又传递给了链接器,这就掩盖了本应发生的链接错误。
找出原因,要解决问题,也使有两个方面:
- 在编译scss.so时,正确传递静态库给ld,比如按顺序 base.a crypto.a base.a的顺序传递,这样scss.so的集合U就不会包含crypto.a中的相关符号,之后在编译a.out时,即使cmake传递crypto.a和base.a给链接器,链接器也不会将符号_s_var加入到集合D中
- 在调用cmake的语句
TARGET_LINK_LIBRARIES
时,加入PRIVATE
关键字,阻止依赖的自动传递,及时发现scss.so的符号引用错误