C++静态库符号冲突解决方案

背景

Cronet准备接入公司一个产品的iOS版本,由于需要兼容iOS7.0,所有sdk需提供静态库版本。

静态库包含了整个代码里所有全局可见的函数、变量,这些在编译后都是以符号的方式存在于xxx.a中,由于跟APP工程共用了一些相同的第三方库,造成在链接过程报duplicated symbols error错误。
而更为严重的一点是,在iOS的xcode进行链接时,它会自己替你解决一些符合冲突,解决的方式是按链接顺序,假设libA先链接,那么这些冲突符号就是按libA的定义来运行。
在我们集成过程中,问题最大的是已有的openssl 与我们的 boringssl库的冲突,由于boringssl做了许多定制化的修改,我们目前只能想办法让两个库共存,并且运行互不干扰。

问题调研

在调研解决符号冲突过程中,发现一个文章思路分享比较好,后面的问题解决大概就是按照这篇分享来做的。
文章内容可参见Avoiding Dependency Collisions in an iOS Library

解决方案

  1. dump lib库中所有自定义的符号

通过nm -U libboringssl.a, dump出所有类型为T(自定义函数),S(自定义全局变量)的符号,执行过程可能还会出现类型为 b、s、t的符号,这些都是static的,不对外暴露,可以过滤掉。

1
2
# example
nm -U libboringssl.a | grep -v ' t ' | grep -v ' s ' | grep -v ' d ' | grep -v ' b ' | awk '{print $3}' | tr -s '\n'

由此,我们得到了所有可能会产生冲突的符号列表。

  1. 符号改名
    通过脚本,将所有由boringssl自定义的全局函数和变量进行改名,最终生成一个头文件,文件中声明了一系列宏定义,例如:
    1
    #define kOpenSSLReasonValues _CRONET_kOpenSSLReasonValues

我们将所有需要修改的名称,通过这样的宏定义加前缀,来使其在编译期间动态修改。

  1. precompiled headers
    为了使宏定义生效,需要把第二步的头文件加到工程的precompiled headers中,使其在预编译阶段生效。
    precompile header在工程中的实现,可去参见工程目录/build/config:precompiled_headers

产生的风险及其应对

  1. 对于我们修改的库,通过宏定义define了函数名,可能导致内部一些原有的宏逻辑失效
    例如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // ssl.h中,会主动define funcA funcA, 这块目前看没有什么具体作用,应该是为了防止外界误define
    #define DTLSv1_get_timeout DTLSv1_get_timeout
    #define DTLSv1_handle_timeout DTLSv1_handle_timeout
    #define SSL_CTX_add0_chain_cert SSL_CTX_add0_chain_cert
    #define SSL_CTX_add1_chain_cert SSL_CTX_add1_chain_cert
    #define SSL_CTX_add_extra_chain_cert SSL_CTX_add_extra_chain_cert
    #define SSL_CTX_clear_extra_chain_certs SSL_CTX_clear_extra_chain_certs
    #define SSL_CTX_clear_chain_certs SSL_CTX_clear_chain_certs
    #define SSL_CTX_clear_mode SSL_CTX_clear_mode
    //...

这种情况,会在编译阶段报重复define错误,因此可以通过编译结果来发现。但需要人工确认这些符号是否可以被修改。

  1. 一些#ifdef、#ifndef、#elif等逻辑,可能会被我们的改名影响
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // md5.c中,会通过ifndef md5_block_data_order(已经被我提前define了) 来决定是否执行宏中逻辑,这块需要看一下怎么改
    #ifndef md5_block_data_order
    #ifdef X
    #undef X
    #endif
    void md5_block_data_order(uint32_t *state, const uint8_t *data, size_t num) {
    uint32_t A, B, C, D, l;
    uint32_t XX0, XX1, XX2, XX3, XX4, XX5, XX6, XX7, XX8, XX9, XX10, XX11, XX12,
    XX13, XX14, XX15;
    #define X(i) XX##i

这种情况,可以用脚本过滤出了boringssl库中所有的#ifndef、#ifdefined、#elif语句,与我们修改的名字进行匹配,确认哪些有影响,最终只发现了上面这段代码有影响。

最后

以上所有事情都完全,风险得到确认后,即可消除符号冲突问题,使得两个原本函数签名一样的库可以共存。亲测无问题。