LINUX静态库与动态库符号冲突问题分析与解决

技术交流
0 404

1. 问题重现模型为了重现问题并去掉无关干扰细节,我们将构建一个最简单的可执行模块和依赖模块的关系链,程序依赖模型如下:

1.1 解释(1)有一个名为RTSP的第三方库提供了公共接口RTSP_OPEN,RTSP可以编译为静态库libRTSP_STATIC.a也可以编译为动态库libRTSP_SHARED.so。(2)基于RTSP库封装了一个名为STREAM的库,该库以动态库libSTREAM.so的形式提供使用。STREAM库提供了1个名为STREAM_OPEN的接口,该接口在内部使用RTSP库提供的公共接口RTSP_OPEN。(3)用户程序调用了STREAM库的STREAM_OPEN接口。(4)整个依赖链关系为用户程序依赖STREAM库,STREAM库依赖RTSP库。

1.2 代码为了便于看到实验效果,RTSP库的静态库(.a)和动态库(.so)分别使用不同的源文件进行编译,但是他们提供相同的接口。(现实情况是使用同一套代码生成静态库和动态库,此处只是为了方便看到实验效果)。(1)RTSP库代码如下:1)rtsp.h

#ifndef __RTSP_H__#define __RTSP_H__int RTSP_OPEN();int RTSP_CLOSE();int RTSP_PARSE();#endif1234562)rtsp_static.c

#include "stdio.h"

int RTSP_OPEN(){    printf("[%s:%s:%d]\n" __FILE__ __FUNCTION__ __LINE__);    return 0;}

int RTSP_CLOSE(){    printf("[%s:%s:%d]\n" __FILE__ __FUNCTION__ __LINE__);    return 0;}

int RTSP_PARSE(){    printf("[%s:%s:%d]\n" __FILE__ __FUNCTION__ __LINE__);    return 0;}123456789101112131415161718193)rtsp_shared.c

#include "stdio.h"

int RTSP_OPEN(){    printf("[%s:%s:%d]\n" __FILE__ __FUNCTION__ __LINE__);    return 0;}

int RTSP_CLOSE(){    printf("[%s:%s:%d]\n" __FILE__ __FUNCTION__ __LINE__);    return 0;}

int RTSP_PARSE(){    printf("[%s:%s:%d]\n" __FILE__ __FUNCTION__ __LINE__);    return 0;}12345678910111213141516171819(2)STREAM库代码如下:1)stream.h

#ifndef __STREAM_H__#define __STREAM_H__int STREAM_OPEN();#endif12342)stream.c

#include "rtsp.h"int STREAM_OPEN(){    RTSP_OPEN();    return 0;}123456(3)用户程序代码如下:1)test_stream.c:

#include "stream.h"int main(int argc char *argv[]){    STREAM_OPEN();    while(1) sleep(1000);    return 0;}12345671.3 编译各模块编译语句如下:(1)libRTSP_SHARED.so

gcc -g -fPIC -shared rtsp_shared.c -o libRTSP_SHARED.so12(2)libRTSP_STATIC.a

gcc -c -g -fPIC rtsp_static.c -o rtsp_static.oar crv libRTSP_STATIC.a rtsp_static.o12(3)libSTREAM.soSTREAM库使用RTSP静态库,请注意STREAM库的编译方法,非常重要,后面解决符号冲突问题会修改此编译语句!

gcc -g -fPIC -shared stream.c -o libSTREAM.so -L./ -lRTSP_STATIC1(4)用户程序的编译放在下一节分析,因为用户程序的编译方法不同将导致用户程序运行结果不同,用户程序有可能调用到RTSP静态库中的RTSP_OPEN接口,也有可能调用到RTSP动态库中的RTSP_OPEN接口。

2. 问题重现分析现在我们再来看下上述代码的程序依赖模型图:

2.1 情景分析STREAM库在内部使用了第三方RTSP库提供的公共接口,如果用户程序也直接使用了RTSP库会出现什么情况?请看下面的情景分析。(1)情况1用户程序使用如下语句编译:

gcc -g test_stream.c -o test_stream -L./ -lSTREAM1程序运行结果如下:

可以看到用户程序最终调用了RTSP静态库中的RTSP_OPEN接口。分析:通常情况下,用户程序直接使用封装的STREAM库,STREAM库隐含的使用了第三方RTSP库的静态库,这个隐含关系对于用户来说是不可见的。如果用户程序不直接使用RTSP动态库,一切都没有问题。情况1模块加载关系如下:

图中蓝色部分代表用户程序运行期间被加载到内存中的模块,从图中可以看到,用户程序运行时只存在RTSP静态库,因此RTSP_OPEN是唯一的。(2)情况2用户程序使用如下语句编译:

gcc -g test_stream.c -o test_stream -L./ -lRTSP_SHARED -lSTREAM1程序运行结果如下:

可以看到用户程序最终调用了RTSP动态库中的RTSP_OPEN接口。分析:在这种情况下,用户程序使用了STREAM库,同时又直接链接了第三方RTSP库的动态库(编译语句中的红色部分)。因为STREAM库是使用RTSP库的静态库(libRTSP_STATIC.a)编译的,现在又链接了RTSP库的动态库(libRTSP_SHARED.so),因此在用户程序中会有两个相同的RTSP_OPEN符号,加载器在加载应用程序并绑定符号时就要做出决议,到底是要使用静态库中的RTSP_OPEN符号还是动态库中的RTSP_OPEN符号。从运行结果上来看,上述的编译语句编译出来的用户程序使用了动态库中的RTSP_OPEN符号。情况2模块加载关系如下:

图中蓝色部分代表用户程序运行期间被加载到内存中的模块,从图中可以看到,用户程序运行时同时存在RTSP静态库和RTSP动态库,因此RTSP_OPEN具有二义性。实际上,即使用户使用了RTSP动态库也不一定会导致用户程序调用到动态库中的RTSP_OPEN符号。例如,我们把上面的编译语句改为下面的:原来的用户程序编译语句:

gcc -g test_stream.c -o test_stream -L./ -lRTSP_SHARED –lSTREAM1修改的用户程序编译语句:

gcc -g test_stream.c -o test_stream -L./ -lSTREAM -lRTSP_SHARED1重新编译编译后运行用户程序,输出如下:

可以看到即使链接了RTSP动态库,用户程序最终还是调用了静态库中的RTSP_OPEN接口。具体原因在此处暂时不展开,但是可以说明一点,如果用户程序使用了RTSP动态库可能会产生符号冲突问题,并且这个行为是STREAM库提供者不能控制的!

3. 问题解决方案3.1 解决方法我们回过头来看一下STREAM库的编译语句:

gcc -g -fPIC -shared stream.c -o libSTREAM.so -L./ -lRTSP_STATIC1再看一下libSTREAM.so的重定位信息:

可以发现RTSP_OPEN是一个动态绑定符号,所谓动态绑定符号就是编译链接阶段并不确定符号地址,符号地址的解析和绑定推迟到装载阶段。因此解决问题的一种思路就是在编译链接阶段将使用的RTSP静态库中的符号地址确定下来。解决的办法就是在编译libSTREAM.so的时候加上-Wl-Bsymbolic编译选项,该编译选项的含义是在链接过程中优先使用本模块内部的符号。原来的libSTREAM.so编译语句:

gcc -g -fPIC -shared stream.c -o libSTREAM.so -L./ -lRTSP_STATIC1修改的libSTREAM.so编译语句:

gcc -g -fPIC -shared stream.c -o libSTREAM.so -L./ -lRTSP_STATIC -Wl-Bsymbolic1重新编译libSTREAM.so后查看重定位信息:

可以看到libSTREAM.so中的动态绑定符号中已经没有RTSP_OPEN这个符号了。

3.2 验证重新编译用户程序并使用新的libSTREAM.so,验证符号冲突问题是否解决:(1)

gcc -g test_stream.c -o test_stream -L./ -lRTSP_SHARED –lSTREAM1运行结果:

(2)

gcc -g test_stream.c -o test_stream -L./ -lSTREAM -lRTSP_SHARED1运行结果:

从上面2个例子可以看出,用户程序加载了动态库libRTSP_SHARED.so,但是使用的都是RTSP静态库(libRTSP_STATIC.a)中提供的RTSP_OPEN接口,符号冲突问题已经解决。

3.3 其他本文分析的情景是:有1个动态库和1个静态库同时导出了同名符号,用户程序又同时使用了这两个库(隐式或显示)从而导致的符号冲突问题。本文提供的解决方案并不能解决两个动态库导出了同名符号,用户程序又同时使用了这两个动态库导致的符号冲突问题。————————————————版权声明:本文为CSDN博主「g_handle」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/freeyond/article/details/77621826