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

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;
}

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;
}

(2)STREAM库代码如下:
1)stream.h

#ifndef __STREAM_H__
#define __STREAM_H__
int STREAM_OPEN();
#endif

2)stream.c

#include "rtsp.h"
int STREAM_OPEN()
{
    RTSP_OPEN();
    return 0;
}

(3)用户程序代码如下:
1)test_stream.c:

#include "stream.h"
int main(int argc, char *argv[])
{
    STREAM_OPEN();
    while(1) sleep(1000);
    return 0;
}
1.3 编译

各模块编译语句如下:
(1)libRTSP_SHARED.so

gcc -g -fPIC -shared rtsp_shared.c -o 
libRTSP_SHARED.so

(2)libRTSP_STATIC.a

gcc -c -g -fPIC rtsp_static.c -o rtsp_static.o
ar crv libRTSP_STATIC.a rtsp_static.o

(3)libSTREAM.so
STREAM库使用RTSP静态库,请注意STREAM库的编译方法,非常重要,后面解决符号冲突问题会修改此编译语句!

gcc -g -fPIC -shared stream.c -o libSTREAM.so -L./ -lRTSP_STATIC

(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

程序运行结果如下:
这里写图片描述
可以看到用户程序最终调用了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

程序运行结果如下:
这里写图片描述
可以看到用户程序最终调用了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

修改的用户程序编译语句:

gcc -g test_stream.c -o test_stream -L./ -lSTREAM -lRTSP_SHARED

重新编译编译后运行用户程序,输出如下:

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

3. 问题解决方案

3.1 解决方法

我们回过头来看一下STREAM库的编译语句:

gcc -g -fPIC -shared stream.c -o libSTREAM.so -L./ -lRTSP_STATIC

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

gcc -g -fPIC -shared stream.c -o libSTREAM.so -L./ -lRTSP_STATIC

修改的libSTREAM.so编译语句:

gcc -g -fPIC -shared stream.c -o libSTREAM.so -L./ -lRTSP_STATIC -Wl,-Bsymbolic

重新编译libSTREAM.so后查看重定位信息:
这里写图片描述
可以看到libSTREAM.so中的动态绑定符号中已经没有RTSP_OPEN这个符号了。

3.2 验证

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

gcc -g test_stream.c -o test_stream -L./ -lRTSP_SHARED –lSTREAM

运行结果:
这里写图片描述
(2)

gcc -g test_stream.c -o test_stream -L./ -lSTREAM -lRTSP_SHARED

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

3.3 其他

本文分析的情景是:有1个动态库和1个静态库同时导出了同名符号,用户程序又同时使用了这两个库(隐式或显示)从而导致的符号冲突问题。
本文提供的解决方案并不能解决两个动态库导出了同名符号,用户程序又同时使用了这两个动态库导致的符号冲突问题。

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值