C语言的打桩(Interpositioning)机制导致的符号重名Bug

简介: C语言的打桩(Interpositioning)机制是一种用定制的函数替换链接库函数且不需重新编译的技术。甚至可用此技术替换系统调用(更确切地说,库函数包装系统调用)。说白了,编译时使用的动态库中的某个符号可能在运行时被可执行程序或另一个动态库的同名符号替换。
C语言的打桩(Interpositioning)机制是一种用定制的函数替换链接库函数且不需重新编译的技术。甚至可用此技术替换系统调用(更确切地说,库函数包装系统调用)。说白了,编译时使用的动态库中的某个符号可能在运行时被可执行程序或另一个动态库的同名符号替换。如果发生替换的原因不是用户有意为之而是不同的库间偶然出现了符号重名,那就会带来Bug。C语言中另外2个因素加重了这种情况的发生。
  1. C语言没有名称空间,更容易出现符号重名
  2. C语言中的符号默认是全局的,对其他库或可执行程序是公开的(Windows除外)

而且不仅引用的其他动态库中的符号可能会被替换,动态库使用的自己内部定义的符号也可能会被替换。
我们在工作中就遇到过这个样的Bug。原始的问题描述起来比较繁琐,下面用一个简单的例子演示一下。


描述:
libtestso.so(testso.c)中有个testso()函数,它调用自己定义的foo()函数,它没准备把foo()函数给别的库用,更不准备用别的库里的foo(),
但是不幸的是,由于加载了libtestso.so的test中有个同名的foo(),导致testso()实际调用的是test中的foo()而不是它自己的。


代码:
test.c

点击(此处)折叠或打开

  1. #include stdio.h>

  2. void foo()
  3. {
  4.     printf("test's foo() called\n");
  5. }

  6. int main(int argc,char** argv)
  7. {
  8.     foo();
  9.     testso();
  10.     return 0;
  11. }

testso.c

  1. #include stdio.h>

  2. void foo()
  3. {
  4.     printf("testso's foo() called\n");
  5. }

  6. void testso()
  7. {
  8.     printf("testso() called\n");
  9.     foo();
  10. }

编译:

  1. [chenhj@node2 ~]$ gcc -shared -ldl -fPIC testso.c -o libtestso.so
  2. [chenhj@node2 ~]$ gcc test.c -L. -ltestso -o test

此时libtestso.so中的foo是公开符号

  1. [chenhj@node2 ~]$ nm libtestso.so

  2. 00000000000005cc T foo

执行:

  1. [chenhj@node2 ~]$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
  2. [chenhj@node2 ~]$ ./test
  3. test's foo() called
  4. testso() called
  5. test's foo() called

问题出现。testso()调用了test中的foo而不是libtestso.so中的。


解决方法有2个,一个方法是把foo()函数static化,但static也限制了foo()只能在同一文件内可见,更通用的方法是通过符号可见性把foo()函数隐藏。
下面演示一下通过隐藏libtestso.so中的foo符号的方法来回避上面的问题。

写一个符号定义脚本,除testso,其他符号全部不公开。
export.map

  1. {

  2. global:testso;

  3. local:*;

  4. }

重新编译libtestso.so

  1. [chenhj@node2 ~]$ gcc -shared -ldl -fPIC testso.c -c
  2. [chenhj@node2 ~]$ gcc -shared -ldl -fPIC testso.o -Wl,--version-script=export.map -o libtestso.so

此时libtestso.so中的foo是非公开符号

  1. [chenhj@node2 ~]$ nm libtestso.so

  2. 00000000000004bc t foo

再次执行,问题解决

  1. [chenhj@node2 ~]$ ./test
  2. test's foo() called
  3. testso() called
  4. testso's foo() called

总结:
隐藏符号不仅可以保护别人,防止别人误引用了自己的符号;也能保护自己,防止自己误引用了别人的符号。编写动态库时应该尽量使用这个技巧。

参考:
http://blog.jobbole.com/73094/
http://zhongcong386.blog.163.com/blog/static/13472780420121190563268/




相关文章
|
6月前
|
安全 算法 程序员
【C/C++ 文件操作】深入理解C语言中的文件锁定机制
【C/C++ 文件操作】深入理解C语言中的文件锁定机制
193 0
|
6月前
|
C语言
c语言编程练习题:7-16 计算符号函数的值
请编写程序计算该函数对任一输入整数的值。
114 0
|
3月前
|
存储 程序员 C语言
C语言的错误处理机制
C语言的错误处理机制
107 0
|
1月前
|
程序员 编译器 数据处理
【C语言】深度解析:动态内存管理的机制与实践
【C语言】深度解析:动态内存管理的机制与实践
|
6月前
|
C语言 C++ 索引
C语言符号——操作符详解
C语言符号——操作符详解
C语言符号——操作符详解
|
6月前
|
C语言
【C 言专栏】C 语言中的错误处理机制
【5月更文挑战第3天】本文探讨了C语言中的错误处理机制,涵盖错误类型(语法和运行时错误)、基本处理方法(返回值、全局变量和自定义异常)及常见策略(检查返回值、设置标志位和记录错误信息)。还介绍了perror和strerror函数,并强调自定义错误处理函数的重要性。注意不要忽略错误,保持处理一致性,避免过度处理。通过实例说明错误处理在文件操作和网络编程中的关键作用。错误处理是提升程序稳定性和可靠性的必备技能,需要在实践中不断学习和完善。
150 4
|
6月前
|
编译器 程序员 Linux
深入理解C语言中的return关键字与函数返回机制
深入理解C语言中的return关键字与函数返回机制
527 1
|
6月前
|
存储 机器学习/深度学习 自然语言处理
【进阶C语言】编译与链接、预处理符号详解
【进阶C语言】编译与链接、预处理符号详解
64 0
|
6月前
|
C语言
C语言陷阱——无符号数和有符号数的大小比较
C语言陷阱——无符号数和有符号数的大小比较
|
6月前
|
C语言
【C语言】C语言中的符号重载
【C语言】C语言中的符号重载
87 0