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

假如libgetthree.so libgetseven.so , 同时这两个so内部都用了internal_do_calculation()函数,并且各自定义了自己的internal_do_calculation()的实现,你会想当然的认为他们各自不影响,libgetthree和libgetseven会分别用自己的internal_do_calculation(),但事与愿违,你会发现都只会用其中一个so的符号

他经历的过程如下:

  1. 当exe执行的时候,他会去寻找PublicGetThree符号,于是dynamic loader就会在libgetthree.so种进行reslove
  2. 当exe执行的时候,他会去寻找PublicGetSeven符号,于是dynamic loader就会在libgetseven.so种进行reslove
  3. 接下来开始寻找internal_do_calculation符号,他发现在libgethree.so中有,于是就全部用他的了

于是你会发现,libgetseven.so用的是其他库的这个符号,错误就产生了。取决于你怎么链接顺序,经过测试,发现跟第一个库的顺序有关

489a449c2998d70e4165b30192273deb.png

但是如果我们一旦隐藏比如three.cpp的符号

0db912f710fc93307584df92fd4afc83.png

我们再重新链接,发现就正常了

d52692f2c606415af33ba1e04a618721.png

可以总结出如果你对某个so库中的符号做了hidden,他实际上有两个作用:

  1. 对于自己来说,他告诉这个动态库对于这个符号不能从其他库中去读取,只能读取自己的这个internal符号(-Bsymbolic / -Bsymbolic-functions仅仅起到这个第一个作用)
  2. 对于其他库来说,这个库中的这个符号被隐藏了,自然只能用自己的符号了

通过上面这两个效果,你可以发现,无论你是hidden libgetthree.so还是libgetseven.so都是work的。

除了以上的好处之外,如果你控制得体,比如全局使用-fvisibility=hidden,对对应的public API使用__attribute__((visibility("default")))来暴露,这样有以下额外的好处:

  1. 你的库的load time会减少
  2. 你的整体的运行speed会提高,因为编译器知道他可以对devirtualization / inline functions做额外的优化
  3. 你的shared library的size大小会减小,因为编译器会从exported symbols table中丢失hidden symbols

坏处:

他会让你做单元测试更加困难,因为你已经把你的内部实现符号都给隐藏了,因此当你做unit testing的时候,你需要用default visibility来重新build.你可以借此来重新配置你的debug / release build flags:

Debug:

  • -g - 加上debug info
  • -O0 - 不提供任何优化(可以提供在开发阶段的debug使用体验)

Release:

  • -fvisibility=hidden - 上面说的,可以提高效率
  • -O2 - 优化大小和提高速度

有一个需要注意的地方就是关于异常C++ Exceptions,当binary code捕获住了一个exception的时候,他需要typeinfo的查找,但是typeinfo的symbols会随着你本身symbols的hidden而hidden.

通过linker的-Bsymbolic / -Bsymbolic-functions同样可以解决上面的问题(gcc是要加上-Wl,-Bsymbolic),比如你可以用如下的命令来进行编译

78de852aa2fe80733f56cd5e0cb41163.png

他会带来正确的结果:

f18a5bfd0028d62e506f639235612c1a.png

但是如果那他跟hidden visibility做比较的话还是有诸多缺点:

  1. -Bsymbolic的方式仍然会export他们的符号,因此可以认为他们只解决了我们上面提到的两个作用的第一个作用,因此仍然有可能其他库会去使用他,如果我们交换了libseven.so以及libthree.so的位置,可能就会出现问题
5a4a3116e3ac6688e5b3e0a720ce877d.png
d9394887e9c4b41edb5882c9086515f2.png
  1. hidden visibility比-B的方式优化了对应的空间以及提高了运行速度
  2. 也不是说-Bsymbolic没有任何好处,他相对hidden visibility的手法让你写单元测试的时候只需要进行一次库的编译

除了以上提供的方法之外,你还能利用-fvisibilty=protected来达到效果,他跟hidden类似,可以保护你当前shared library的库的符号不会被其他库方便,但是他不保证你的库会去污染其他库的符号表,比如我们来看例子:

3b06d17459112865f785a0cc40ac3778.png

这个情况下是OK的,因为我们保护了Seven本身,因此他的内部符号只会用自己的

我们来看另外一个例子

2418db0bf18a4f6bbe09a8b36501c5f2.png

这就出问题了,因为我们虽然保护了three本身,但是他的符号也确实污染到了libseven.so,因此输出了3

你也可以通过匿名空间来达到效果

1a41592bb6f801b5f92027765cacd42b.png

注意不要把你的public API也给包了,仅仅包你的实现,然后你通过nm查看发现他们默认变成小t了

a679bbe1c82b6b52a8d774ce189bfd98.png

但是如果你不用匿名空间,而是带名字的空间,他是大T的会做污染

242af3b41ba2013d603718ce9b1fbc85.png

因此可以看到匿名空间的一个作用就是自动帮你把符号隐藏. 同样,如果你的函数定义成static静态的,你的函数符号默认也是hidden的,也能起到同样的效果

Dynamic Linking解决相关问题:

试想一下如果你的library A和B都对C有依赖,其中A用的是新的C,B用的是老的C。这里面可能存在数据结构的不一致,就会出现问题,但是正是因为有了dynamic linking,如果A和B对于major version的C是兼容的,那么dynamic linking会帮你解决这个问题,因为他们所有遇到过的C的符号通过上面的解释,都会保持一致,因为有override的行为在里面。 但是并不是说dynamic linking就是万能解药,静态编译static linking的一个原因就是拥有尽可能小的分发依赖。 另外一个原因就是你要测试所有版本的依赖非常困难,通过静态编译到一个特定的版本允许让你拥有这个依赖的一致行为

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值