推广

C++库符号冲突杂谈

iseeyu3年前 (2024-02-21)推广164

WX20200827-111004@2x.png

上面情况,无论在xcode或是vs2015/ndk,app先链接谁就用谁的d函数,而且不会链接报错。
情形 2:

WX20200827-112737@2x.png

上面的情况:

  • 在xcode下,因为链接器会先链接a函数,他会遍历当前的静态库,发现在a.o里,然后将a.o里的所有符号都放进全局符号里;当链接d函数时,因为d已经在全局符号,因此不需要将b.o放进全局符号,所以无论链接顺序是怎样,app始终用的是liba.a的d;
  • 在vs2015/ndk下,当liba.a先链接时,链接器会发现a.o里存在着app需要的a,d函数,因此将a.o里的所有符号放进全局符号,因为app需要的函数都链接完了,所以不需要将b.o放进全局符号。当libb.a先链接时,链接器会发现b.o存在着app需要的d函数,因此将b.o所有符号放进全局符号。当链接到liba.a时,发现a.o里存在着app需要的a函数,当将a.o所有符号放进全局符号里时,发现已存在了d函数,因此就报符号冲突错误。
    情形 3:

    WX20200827-180339@2x.png

    上面情况:无论在xcode或是vs2015/ndk都会报链接出错,因为无论怎么链接,都需要将a.o和b.o里的符号放进全局符号里。

  1. 链接顺序可以确保app使用的是哪个库的符号吗。
    不同编译器结果不同;对于xcode不能保证,对于vs,ndk,只要不报错,app会用先链接的库的符号。
  2. 怎样查找静态库中的重复符号。
    默认情况下,链接器是按需链接静态库,如果app没有用到.o里的函数,.o不会被链接到app,可以添加链接选项,让链接器将所有静态库的.o都链进app。这样重复的符号就会暴露出来,导致链接出错,以便我们分析,修改。

    • 对于vs2015,在链接选项里加上/WHOLEARCHIVE:a.lib,这样会强制将a里的.o链接到app
    • 对于xcode clang,在链接选项加上-all_load会强制链接所有静态库库到app,也可以用-force_load liba.a,只将a强制链接。
    • 对于ndk
      • Android.mk: LOCAL_WHOLE_STATIC_LIBRARIES += a;或者通过LOCAL_LDFLAGS += -Wl,–whole-archive /path/liba.a -Wl,–no-whole-archive /path/libb.a
      • CMakeLists.txt : target_link_libraries(myapp -Wl,–whole-archive a -Wl,–no-whole-archive b)
  3. 如何解决静态库之间的符号冲突
    • 更改名字:最原始有效的方法。
    • 声明强弱符号:这种方法比较少用,也不太实际,有兴趣的自行查找使用方法

动态库与静态库之间符号冲突

  1. 可以将动态库视为只有一个.o的静态库,链接算法与静态库差不多,但有一点区别:
    • 对于xcode和ndk,当静态库遇到动态库符号时,动态符号会被覆盖掉,而不是报错
    • 对于vs,算法与静态库一样,发现有相同的符号时,一样会报错。
  2. 在编译链接不报错的情况下,静态库先链接,一定会优先用静态库的符号
  3. 如何解决动态库与静态库之间的符号冲突
    • 在xcode和ndk下,是没办法在编译链接时期将冲突暴露出来,所以只能查看动态库的导出符号和静态库的符号,然后更改相同符号的名字。
    • 对于动态库去除不必要的符号导出,这样能减少与静态库的冲突。
  4. 同一个静态库里有相同的符号是非常坑的,当编译源文件顺序不同时,最终链接的结果也不同。

动态库与动态库之间符号冲突

  1. 动态库之间相同的符号在链接时不会报错,先链接谁就用谁的符号。所以要解决他们之间的冲突,只能查看动态库的导出符号,更改相同的名字;其次是去除不必要的符号导出,减少冲突的可能性。
  2. app本质上也是一个动态库。

动态库的符号查找问题。

  1. 动态库是如何查找他依赖的函数呢?
    • 对于win,动态库会有个导入表,里面存储着他链接时所依赖的库和对应依赖的符号;如下图;可以用 dumpbin a.dll /IMPORTS 来列出所依赖的导入信息。当动态库被加载时,加载器会读取这个表,依次加载所依赖的动态库,从依赖的库中拿到依赖函数的地址填入表中。

      wim.png

    • 对于android,动态库存在2个表来存储这些信息。

      • 一个是链接时所依赖的so的导入库表,这个表的顺序是:直接依赖的编译链接顺序,间接依赖用户库链接顺序,间接依赖系统库顺序,例如,如果a库直接依赖b,c,而b又依赖d和系统库e,那么a的导入库表将是b,c,d,e;
      • 一个是所依赖的导入符号表。
      • 当需要使用一个符号时,加载器会去导入库表查找so,会用先找到的so里的符号,找不到则报错
      • 我们可以用arm-linux-androideabi-readelf -d liba.so来列出所依赖的动态库。然后用arm-linux-androideabi-nm liba.so -D来列出所依赖的导入符号。

        aim.png

  • 对于ios/macos,动态库的导入信息与win类似;如下图;我们可以用/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/dyldinfo -lazy_bind liba.dylib 来列出所依赖的导入符号信息。

    mim.png

  1. 动态库链接的一些问题
  • vs导入表里存的是他需要的信息,如果将一个无关的库b加入c库编译链接过程里,c库里不会储存b的信息,而且存的是他直接依赖的库信息;
  • ndk,xcode,只要参加了链接都会保存对其的依赖信息,而且间接依赖库也会保存其中。
  • ndk依赖的符号与库是没有明显的对应关系,这会存在一个问题,如果a.so,b.so同时存在a符号,而c同时依赖a和b库;在开始时c调用的a符号是属于a库的,但如果在某次升级中将a库中的a符号去掉,此时a的调用就会跑到b库去了。vs和xcode在这种情况下,程序直接会出错,反馈说在a库里找不到a符号。

总结

  1. 别以静态库形式提供给客户,静态库的符号冲突比较隐蔽,机率比较大,而且修改成本也大;优先用动态库。
  2. 通过去掉不必要的导出符号,能降低动态库符号冲突的机率,但是代价比较大,特别是多团队合作的时候。从上面看出在vs和xcode里,依赖符号和依赖库是有明确的对应关系,因此可以将接口和核心功能分成两个动态库,只让接口动态库参加到客户的编译链接。这样客户的代码就不会链接到我们的核心库,冲突的几率会降低很多,万一接口库与客户有相同的符号,要修改的范围也小很多。

查看动态库的导出符号

  • vs:在vc bin目录下的dumpbin可以查看;例如:dumpbin a.dll /EXPORTS
  • ndk:在ndk工具目录下的xxx-nm可以查看;例如:arm-linux-androideabi-nm liba.so -D,其中类型“D”表示的是导出变量,“T”表示的是导出函数,“U”表示依赖别的动态库符号,即导入符号
  • xcode:在xcode工具目录下也有个dyldinfo可以查看;例如:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/dyldinfo -export liba.dylib

查看静态库的符号

  • vs:使用dumpbin工具;例如:dumpbin a.lib /SYMBOLS /ARCHIVEMEMBERS 会列出所有.o的符号
    类似02F 00000000 SECTA notype () External | _printf 这种,表示“printf”是.o的符号;
    类似029 00000000 UNDEF notype () External | _foo这种,表示“foo”是引用别的模块的符号
  • ndk : 使用ndk工具中的readelf;例如 arm-linux-androideabi-readelf liba.a -s
    类似 00000001 28 FUNC GLOBAL DEFAULT 11 test这种,表示“test”是.o的符号;
    类似 * 00000000 0 NOTYPE GLOBAL DEFAULT UND foo*这种,表示“foo”是引用别的模块的符号
  • xcode: 使用xcode工具中的objdump;例如 objdump liba.a –syms
    类似:0000000000000020 g F __TEXT,__text _test这种,表示“test”是.o的符号;
    类似: 0000000000000000 UND _foo这种,表示“foo”是引用别的模块的符号

去除不必要的符号导出

  • vs:vs默认是不会将符号导出的,所有要导出的符号都必须声明为__declspec(dllexport),所以只要将不必要导出的符号去掉这个声明就可以了
  • ndk: 可以增加编译选项-fvisibility=hidden,这样默认所有符号就不会导出,对于要导出的符号显示声明__attribute__ ((visibility (“default”)))
  • xcode:可以通过ndk方式实现;也可以在xcode的设置中将Symbols Hidden by Default设置为Yes,但是这方式有个坑要注意,当Enable Testability为Yes时,前面的设置无效,所以最直接的方式是ndk那种。

扫描二维码推送至手机访问。

版权声明:本文由西安泽虎代运营发布,如需转载请注明出处。

转载请注明出处https://www.0291.com.cn/post/56681.html

相关文章

西安企业做百度推广,一个月花三千和花三万到底差在哪?

4|最近跟几个西安的老板聊天,发现一个挺有意思的现象:同样做百度推广,有人一个月花三千块,询盘不断;有人一个月砸三万,水花都没见着。问他们区别在哪,大多数人说不清楚,只知道'钱花了,没效果'。 5| 6|百度推广这东西,说复杂也复杂,说简单也简单。核心就一件事:让你的潜在客户搜某个词的时候,你的信息...

我想当策划,大学应该选什么专业?

我想当策划,大学应该选什么专业?

学,广告,传播,公关,这些专业其实都可以。另外也可以学像汉语言文学,以及一些跟文学相关的一些专业,会对你找工作有帮助。但是,你想从事这个这份职业,除了专业的学习之外,更多的是,你要在上学的时候积极参与一些更营销策划相关的社会实践。比如说你可以做一些兼职,比如说帮别人去写稿,...

服装市场营销策略分析,“定位不慎满盘皆输”

服装市场营销策略分析,“定位不慎满盘皆输”

不同的区域需要不同的策略,而不同的营销策略需要不同的销售思路来配合。因此,在研究女装产品销售思路之前,最好先对于市场与营销策略做一次深入地了解。1.市场区域化(market segmentation):目标营销的第一步,是将整个市场拆分成若干个同性质的小市场。2.目标市场的...

晚间悦读丨你的思维方式,决定你的人生高度!

晚间悦读丨你的思维方式,决定你的人生高度!

越长大,越发现,能够决定你人生高度的,往往是你的认知层次。 你的认知在哪个层级,你的人生就处在什么状态。你认知的高度,决定了你人生的高度。 那如何才能升级方式、提升认知能力呢?可以从这三个方面做出改变。 NO. 1 改变态度 有人...

编写项目营销策划方案六大要点

编写项目营销策划方案六大要点

现在商业发展非常迅速,的需求非常的大,但是要做好营销策划是有一定难度的,我把自己做营销策划的一些体会在这里分享给朋友们。营销策划方案六大要点我们先说项目营销策划方案这个话题。大部分的营销策划人都是在企业里工作,大部分项目之初都是来源于一个想法,创始人、老板或者创始团队的一个...

【商场营销策划】如何进行商场营销策划?

【商场营销策划】如何进行商场营销策划?

商场是策划者们关注的永恒话题,那么究竟该如何作好商场营销策划呢?掌握以下这几点营销策略设计原则,至关重要!1.义利兼顾的原则。商场在获取利润的同时要充分考虑到消费者的合法利益和商场的长远利益。在作商场活动营销策略方案的时候应该将这一点作为首要的原则。对商场有利而对顾客不利的...

现在,非常期待与您的又一次邂逅

我们努力让每一部企业宣传片和抖音短视频成为商业大片