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

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();
#endif
1
2
3
4
5
6
2)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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
3)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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(2)STREAM库代码如下:
1)stream.h

#ifndef __STREAM_H__
#define __STREAM_H__
int STREAM_OPEN();
#endif
1
2
3
4
2)stream.c

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

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

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

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

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

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


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

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

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

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

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

可以看到用户程序最终调用了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 –lSTREAM
1
修改的用户程序编译语句:

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

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

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

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

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

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

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

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

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

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

(2)

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

从上面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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值