Linux动态库冲突问题

一、问题起源

项目开发中需要用到了客户提供的第三方动态库,将链接第三方库的操作添加到makefile中之后,发现无论如何调用第三方库中的接口都会返回错误。
后来通过查看第三方库的符号信息,发现第三方库中的符号与OPENSSL库的符号冲突,而makefile中链接OPENSSL的库在前第三方的库在后,所以在程序运行时自然的链接到了OPENSSL库中的符号并调用了OPENSSL库中的接口,最终导致返回失败。

二、初步解决

既然是库链接顺序的问题,那么调整库的链接顺序应该可以解决该问题。实际上也确实如此,调整链接顺序后第三方库中的所有接口都可以正确返回。本以为问题可以这样简单又完美的得到解决,殊不知更大的坑还在后面。

三、新的问题

涉及第三方库的接口调试完成后开始进行整个系统的测试,发现每当系统进行某项业务时都会发生内存泄漏。而在调用第三方库之前是明显没有这样的问题的,取消第三方库的链接和相关业务后果然就不存在内存泄漏的问题了。所以问题终究还是调用的库有关系,因为库的符号冲突,哪个库链接在前就采用哪个库的接口,而两项业务又不能使用同样的接口,要想解决该问题就必须解决库冲突的问题。

四、问题研究

为了更好的研究问题,制作两个简单的库libA.so和libB.so,其实现如下:

/* func_A.c */
#include<stdio.h>

//内部函数,被out_A()函数调用
int out()
{
    printf("a\n");    
    return 0;
}
//用于外部调用 
int out_A()
{
    out();
    printf("A\n");
    return 0;    
}
/* func_B.c */
#include<stdio.h>

//内部函数,被out_B()函数调用
int out()
{
    printf("b\n");    
    return 0;
}
//用于外部调用 
int out_B()
{
    out();
    printf("B\n");
    return 0;    
}

其中的外部调用函数分别为out_A()和out_B();调用目标:调用out_A()输出a和A;调用out_B()输出b和B。

动态库测试
编译动态库

gcc -fPIC -shared -o libA.so func_A.c
gcc -fPIC -shared -o libB.so func_B.c

然后对单个动态库分别进行测试,发现均可以得到预期结果。 当两个库同时调用时,测试代码如下:

#include<stdio.h>

extern int out_A();
extern int out_B();
int main()
{
    out_A();
	out_B();
    return 0;
}

编译并链接两个库:

gcc -o exeAB test.c -L. -lA -lB

执行后发现调用out_B()输出a和B。
在这里插入图片描述
改变库的链接顺序:

gcc -o exeAB test.c -L. -lB -lA

执行后发现调用out_A()输出b和A。
在这里插入图片描述
使用nm或者readelf指令查看库的符号信息:

nm libA.so | grep out

在这里插入图片描述
可以看到out函数为全局符号(default),即其对所有调用均可见。

五、解决方案

1、使用动态加载库方式处理:
既然是库冲突,那么在调用到的时候再加载库(采用动态加载库的方式)应该可以解决冲突的问题。这里需要使用dlopen(动态加载库)函数,使用时引入头文件 dlfcn.h,编译时增加库链接 -ldl。
这里libA.so采用动态加载的方式,libB.so依然采用普通调用的方式:

#include<stdio.h>
#include<dlfcn.h>

extern int out_A();
extern int out_B();

typedef int (*func_pt)();
int main()
{
    void *handle = NULL;
    func_pt func = NULL;
    
    if((handle = dlopen("./libA.so", RTLD_LAZY)) == NULL)
    {
        printf("dlopen %s\n", dlerror());    
        return -1;
    }

    func = dlsym(handle, "out_A");
    func();
    dlclose(handle);
    
    printf("~~\n");
    out_B();
}

在这里插入图片描述

可以看到,动态加载依然没有输出预期结果。
其实,可以在打开动态加载库的时候加上RTLD_DEEPBIND选项;RTLD_DEEPBIND选项可以设定dlopen载入的库首先从自己和它的依赖库中查找符号,然后再去全局符号中去查找。

#include<stdio.h>
#include<dlfcn.h>

extern int out_A();
extern int out_B();

typedef int (*func_pt)();
int main()
{
    void *handle = NULL;
    func_pt func = NULL;
    
    if((handle = dlopen("./libA.so", RTLD_LAZY | RTLD_DEEPBIND)) == NULL)
    {
        printf("dlopen %s\n", dlerror());    
        return -1;
    }

    func = dlsym(handle, "out_A");
    func();
    dlclose(handle);
    
    printf("~~\n");
    out_B();
}

在这里插入图片描述
可以看到,加上RTLD_DEEPBIND选项后输出了预期结果。

虽然动态加载库的方法可以解决库冲突的问题,但是,这种方法写起来太折腾,如果一个库中含多个外部接口,写起来简直是噩梦。

2、使用编译选项控制符号导出:
既然是符号冲突导致的异常,那么可以把库符号信息进行修改,把库内部调用的符号限定为只能内部调用,外部调用的符号限定为可公共调用。
要想达到上面的目的,需要在函数前增加__attribute__ 前缀来控制。
修改libA.so和libB.so的实现,并重新编译生成libA1.so和libB1.so。

/*func_A1.c*/
#include<stdio.h>

#define DLL_PUBLIC __attribute__((visibility("default")))
#define DLL_LOCAL  __attribute__((visibility("hidden")))

//内部函数,被out_A()函数调用
DLL_LOCAL int out()
{
    printf("a\n");    
    return 0;
}
//用于外部调用 
DLL_PUBLIC int out_A()
{
    out();
    printf("A\n");
    return 0;    
}
/*func_B1.c*/
#include<stdio.h>

#define DLL_PUBLIC __attribute__((visibility("default")))
#define DLL_LOCAL  __attribute__((visibility("hidden")))

//内部函数,被out_B()函数调用
DLL_LOCAL int out()
{
    printf("b\n");    
    return 0;
}
//用于外部调用 
DLL_PUBLIC int out_B()
{
    out();
    printf("B\n");
    return 0;    
}
gcc -fPIC -shared -o libA1.so func_A1.c
gcc -fPIC -shared -o libB1.so func_B1.c

此时再用nm指令查看符号信息:

nm libA1.so | grep out

在这里插入图片描述

看到out符号标志变为t,表示该符号只能库内部自己使用。使用新的库再次测试两个库同时调用的情况:

#include<stdio.h>

extern int out_A();
extern int out_B();
int main()
{
    out_A();
	out_B();
    return 0;
}

编译:

gcc -o exeAB test.c -L. -lA -lB

运行:./exeAB
得到了预期结果:
在这里插入图片描述
上面的方法虽然可以解决问题,但是如果库中符号(函数)很多,一个个修改显然不现实。其实可以编译时设置默认函数不导出,只在需要导出的函数前面加前缀。以FuncA.c为例:

/*func_A2.c*/
#include<stdio.h>

#define DLL_PUBLIC __attribute__((visibility("default")))

//内部函数,被out_A()函数调用
int out()
{
    printf("a\n");    
    return 0;
}
//用于外部调用 
DLL_PUBLIC int out_A()
{
    out();
    printf("A\n");
    return 0;    
}

编译时,增加-fvisibility=hidden 参数,这样未增加前缀的函数都不会导出

gcc -fPIC -shared -fvisibility=hidden -o libA2.so func_A2.c

用nm指令查看符号信息:

nm libA2.so | grep out

在这里插入图片描述

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值