C语言的打桩(Interpositioning)机制是一种用定制的函数替换链接库函数且不需重新编译的技术。甚至可用此技术替换系统调用(更确切地说,库函数包装系统调用)。说白了,编译时使用的动态库中的某个符号可能在运行时被可执行程序或另一个动态库的同名符号替换。如果发生替换的原因不是用户有意为之而是不同的库间偶然出现了符号重名,那就会带来Bug。C语言中另外2个因素加重了这种情况的发生。
而且不仅引用的其他动态库中的符号可能会被替换,动态库使用的自己内部定义的符号也可能会被替换。
我们在工作中就遇到过这个样的Bug。原始的问题描述起来比较繁琐,下面用一个简单的例子演示一下。
描述:
libtestso.so(testso.c)中有个testso()函数,它调用自己定义的foo()函数,它没准备把foo()函数给别的库用,更不准备用别的库里的foo(),
但是不幸的是,由于加载了libtestso.so的test中有个同名的foo(),导致testso()实际调用的是test中的foo()而不是它自己的。
代码:
test.c
testso.c
编译:
此时libtestso.so中的foo是公开符号
执行:
问题出现。testso()调用了test中的foo而不是libtestso.so中的。
解决方法有2个,一个方法是把foo()函数static化,但static也限制了foo()只能在同一文件内可见,更通用的方法是通过符号可见性把foo()函数隐藏。
下面演示一下通过隐藏libtestso.so中的foo符号的方法来回避上面的问题。
写一个符号定义脚本,除testso,其他符号全部不公开。
export.map
重新编译libtestso.so
此时libtestso.so中的foo是非公开符号
再次执行,问题解决
总结:
隐藏符号不仅可以保护别人,防止别人误引用了自己的符号;也能保护自己,防止自己误引用了别人的符号。编写动态库时应该尽量使用这个技巧。
参考:
http://blog.jobbole.com/73094/
http://zhongcong386.blog.163.com/blog/static/13472780420121190563268/
- C语言没有名称空间,更容易出现符号重名
- C语言中的符号默认是全局的,对其他库或可执行程序是公开的(Windows除外)
而且不仅引用的其他动态库中的符号可能会被替换,动态库使用的自己内部定义的符号也可能会被替换。
我们在工作中就遇到过这个样的Bug。原始的问题描述起来比较繁琐,下面用一个简单的例子演示一下。
描述:
libtestso.so(testso.c)中有个testso()函数,它调用自己定义的foo()函数,它没准备把foo()函数给别的库用,更不准备用别的库里的foo(),
但是不幸的是,由于加载了libtestso.so的test中有个同名的foo(),导致testso()实际调用的是test中的foo()而不是它自己的。
代码:
test.c
点击(此处)折叠或打开
- #include stdio.h>
-
- void foo()
- {
- printf("test's foo() called\n");
- }
-
- int main(int argc,char** argv)
- {
- foo();
- testso();
- return 0;
- }
testso.c
- #include stdio.h>
-
- void foo()
- {
- printf("testso's foo() called\n");
- }
-
- void testso()
- {
- printf("testso() called\n");
- foo();
- }
编译:
- [chenhj@node2 ~]$ gcc -shared -ldl -fPIC testso.c -o libtestso.so
- [chenhj@node2 ~]$ gcc test.c -L. -ltestso -o test
此时libtestso.so中的foo是公开符号
- [chenhj@node2 ~]$ nm libtestso.so
- …
- 00000000000005cc T foo
执行:
- [chenhj@node2 ~]$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
- [chenhj@node2 ~]$ ./test
- test's foo() called
- testso() called
- test's foo() called
问题出现。testso()调用了test中的foo而不是libtestso.so中的。
解决方法有2个,一个方法是把foo()函数static化,但static也限制了foo()只能在同一文件内可见,更通用的方法是通过符号可见性把foo()函数隐藏。
下面演示一下通过隐藏libtestso.so中的foo符号的方法来回避上面的问题。
写一个符号定义脚本,除testso,其他符号全部不公开。
export.map
- {
-
- global:testso;
-
- local:*;
-
- }
重新编译libtestso.so
- [chenhj@node2 ~]$ gcc -shared -ldl -fPIC testso.c -c
- [chenhj@node2 ~]$ gcc -shared -ldl -fPIC testso.o -Wl,--version-script=export.map -o libtestso.so
此时libtestso.so中的foo是非公开符号
- [chenhj@node2 ~]$ nm libtestso.so
- …
- 00000000000004bc t foo
再次执行,问题解决
- [chenhj@node2 ~]$ ./test
- test's foo() called
- testso() called
- testso's foo() called
总结:
隐藏符号不仅可以保护别人,防止别人误引用了自己的符号;也能保护自己,防止自己误引用了别人的符号。编写动态库时应该尽量使用这个技巧。
参考:
http://blog.jobbole.com/73094/
http://zhongcong386.blog.163.com/blog/static/13472780420121190563268/