库冲突
时间:2010-07-23 来源:hqin6
原文地址:http://blog.csdn.net/hqin6/archive/2010/06/27/5698228.aspx
最近遇到一个问题:应用程序在加载了一个.a(libtest.a)之后,又同时加载了一个.so(-ltest -L./)。而.a和.so拥有同一个.o文件。该.o文件中有一个class的static string。运行该程序,发现该string有double free的问题。
论坛上也有这么个类似的问题:
http://topic.csdn.net/u/20100610/12/c8738f93-153d-40d2-998b-0b48213ac255.html
先说一下结论,下面再逐步论证。
结论:
1、应用程序在链接阶段时,会顺序生成符号表。也就是说,在应用程序中涉及到的符号,会在链接文件中逐个顺次查找
2、一旦查找到符号,就停止本符号的查找工作,转向第二个符号的查找
3、如果没有用到.a里的符号,即查找的过程中没有涉及到该.a,则不会在程序中链接该.a
4、对于.so,无论是否涉及到符号查找,均会进行加载
5、so的加载和卸载会涉及到自身内存分配和释放,而.a不会(.a相当于.o的集合,.o直接静态编译到应用程序,成为程序一部分)
6、.a和.o有不同,.a是.o的集合,但是,.o必定会加载,.a不一定会加载(只加载符号表相关的.o)
由上,不难得出,对于冲突的两个库(.a & .so)里的全局变量,应用程序只会生成一个符号表,而所有涉及到该符号到代码,都会通过该符号表连到同一块内存地址。这样,对于上述问题,应用程序本身 释放一次string的内存,而在so卸载的时候,也会释放一次(so认为该string是自己的)。所以出现double free。
解决方案:
1、最好不要出现如此的混乱代码。
2、先加载so,然后加载.a(即顺序-ltest libtest.a)
参考文章:
http://gcc.gnu.org/ml/gcc-bugs/1999-12n/msg00621.html
A library is searched for missing symbols. Objects and libraries are processed from left to right. When no symbol is missing, the library is not used.
http://wendang.baidu.com/view/e75b3a2e453610661ed9f47f.html
不像在命令行上传递OBJ文件那样,链接器并不把一个库文件里面包含的所有OBJ文件都链接到最终的可执行文件中。事实上,源文件从哪些OBJ文件中 引用了符号,链接器才会将这些OBJ文件链接到最终的可执行文件中。换句话说,链接器命令行中明确指定的OBJ文件一定会被链接到最终的可执行文件中(不 管实际有没有用到),而LIB文件中的OBJ文件只有在被引用时才被链接进去。
当查找符号时,链接器按命令行上给出的LIB文件的顺序进行搜索。一旦在某个库中找到一个符号,那么这个库就变成了首选库,首先在它里面搜索其它所有 符号。一旦某个符号在这个库中找不到,这个库就失去了它的首选地位。此时链接器搜索它的列表中的下一个库。(要获得更详细的技术信息,请参考 Microsoft知识库文章Q31998,网址为http://www.microsoft.com/kb
-----------------
下面逐步论证:
以下测试环境为:
[root@hqin6-PC projects]# uname -a
Linux hqin6-PC 2.6.29.4-167.fc11.i686.PAE #1 SMP Wed May 27 17:28:22 EDT 2009 i686 athlon i386 GNU/Linux
[root@hqin6-PC projects]# gcc -v
使用内建 specs。
目标:i586-redhat-linux
配置为:../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --enable-plugin --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-1.5.0.0/jre --enable-libgcj-multifile --enable-java-maintainer-mode --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --disable-libjava-multilib --with-ppl --with-cloog --with-tune=generic --with-arch=i586 --build=i586-redhat-linux
线程模型:posix
gcc 版本 4.4.0 20090506 (Red Hat 4.4.0-4) (GCC)
[root@hqin6-PC projects]#
示例程序:
[root@hqin6-PC projects]# cat test.h test.cpp main.cpp
#include <string> |
编译:
[root@hqin6-PC projects]# g++ -shared -fPIC test.cpp -o libtest.so -g
[root@hqin6-PC projects]# g++ test.cpp -g -c; ar r libtest2.a test.o; ranlib libtest2.a
运行:
[root@hqin6-PC projects]# g++ main.cpp -g -ltest -L. -Wl,-rpath=.
[root@hqin6-PC projects]# ./a.out
B:address 0xc10e4c
~B:address 0xc10e4c
[root@hqin6-PC projects]# g++ main.cpp libtest2.a -g
[root@hqin6-PC projects]# ./a.out
B:address 0x8049ef8
~B:address 0x8049ef8
[root@hqin6-PC projects]#
可以看到,均没有任何问题!
但是------
方案一:.a + .so
[root@hqin6-PC projects]# g++ main.cpp libtest2.a -ltest -L. -Wl,-rpath=.
[root@hqin6-PC projects]# ./a.out
B:address 0x80491f8
B:address 0x80491f8
~B:address 0x80491f8
~B:address 0x80491f8
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x09a2a008 ***
得出结论:
应用程序在libtest2.a中查找到m_b和m_str,加载动态库libtest.so。
应用程序构造一次m_b,m_str。
加载libtest.so的时候,so构造一次m_b,m_str。
两次构造在同一块内存(在符号表中是同一个符号)构造两次,析构两次。string多次析构,造成core。
方案二:.so + .a
[root@hqin6-PC projects]# g++ main.cpp -ltest -L. -Wl,-rpath=. libtest2.a
[root@hqin6-PC projects]# ./a.out
B:address 0x329e4c
~B:address 0x329e4c
[root@hqin6-PC projects]#
得出结论:
应用程序在libtest.so中查找到m_b和m_str,未加载库libtest2.a。
应用程序加载libtest.so,构造一次m_b,m_str。
方案三:.a + .a
[root@hqin6-PC projects]# cp libtest2.a libtest2_1.a
[root@hqin6-PC projects]# g++ main.cpp libtest2.a libtest2_1.a
[root@hqin6-PC projects]# ./a.out
B:address 0x8049ef8
~B:address 0x8049ef8
[root@hqin6-PC projects]#
得出结论:
应用程序在第一个libtest2.a中查找到m_b和m_str,未加载库libtest2_1.a。
方案四:.so + .so
[root@hqin6-PC projects]# cp libtest.so libtest3.so
[root@hqin6-PC projects]# g++ main.cpp -ltest -ltest3 -L. -Wl,-rpath=.
[root@hqin6-PC projects]# ./a.out
B:address 0x5b7e4c
B:address 0x5b7e4c
~B:address 0x5b7e4c
~B:address 0x5b7e4c
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x08d87008 ***
得出结论:
应用程序加载两个so。
libtest.so构造一次m_b,m_str,析构一次m_b,m_str
libtest3.so构造一次m_b,m_str,析构一次m_b,m_str
两次构造在同一块内存(在符号表中是同一个符号)。构造两次,析构两次。string多次析构,造成core。
方案五-1:.o + .so
[root@hqin6-PC projects]# g++ main.cpp test.cpp -ltest -L. -Wl,-rpath=./
[root@hqin6-PC projects]# ./a.out
B:address 0x80491f8
B:address 0x80491f8
~B:address 0x80491f8
~B:address 0x80491f8
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x09bf9008 ***
方案五-2:.so + .o
[root@hqin6-PC projects]# g++ main.cpp -ltest -L. -Wl,-rpath=./ test.cpp
[root@hqin6-PC projects]# ./a.out
B:address 0x80491f8
B:address 0x80491f8
~B:address 0x80491f8
~B:address 0x80491f8
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x08615008 ***
方案五-3:无用.o的加载
[root@hqin6-PC projects]# cat t.cpp
void fun()
{
return ;
}
[root@hqin6-PC projects]# g++ main.cpp t.cpp test.cpp
[root@hqin6-PC projects]# nm a.out | grep fun
08048868 T _Z3funv
得出结论:
以.o形式编入应用程序,即使在main中不涉及到符号表,一样会加载该.o