依赖的动态库冲突如何解?

项目场景:

在产品迭代中,需要对某依赖的sdk进行二代升级,拿到了第三方的二代sdk,编译,测试发现,当链接到该第三方sdk的so库后,原先的其他与之完全不相干的某功能竟然不能正常使用了。


原因分析:

LD_DEBUG ./XXX (XXX为app的可执行程序)

使用 LD_DEBUG 命令分析可行性程序,查看其so binding日志文件发现,第一个so调用的函数binding到了第二个so里面,导致函数调用出错。

在这里插入图片描述
在这里插入图片描述
两个版本的so(升级前和升级后),的对比表明,,字符集冲突了,有两个版本mbedtls实现,系统PATH下应该还有另外一个版本mbedtls实现

解决方案:

方案1

在不换so的情况下,还有一种解法,-lagora-rtc-sdk去掉,用lazy模式拉字符集

void* dlopen(const char* libfile,int flag);
void* dlsym(void* handler, const char* symbol);

第一步:把gcc link中的-lagora-rtc-sdk去掉
第二步:编译link报undefined symbol的函数用dlsym加载

方案2

重新生成新的sdk,解决字符集冲突。

知识点扩展

1、 LD_DEBUG

LD_DEBUG 是 glibc 中的 loader 设置的一个环境变量,可以方便的看到 loader 的加载过程。glibc是gnu 发布的libc库 ,即c运行库 。glibc是linux系统 中最底层的api ,几乎其它任何运行库都会依赖于glibc。

Valid options for the LD_DEBUG environment variable are:

  libs        display library search paths
  reloc       display relocation processing
  files       display progress for input file
  symbols     display symbol table processing
  bindings    display information about symbol binding
  versions    display version dependencies
  all         all previous options combined
  statistics  display relocation statistics
  unused      determined unused DSOs
  help        display this help message and exit

To direct the debugging output into a file instead of standard output
a filename can be specified using the LD_DEBUG_OUTPUT environment variable.

以memcached安装过程中遇到的问题为例:

命令: /usr/local/bin/memcached -p 11211 -m 64m -vv
错误:/usr/local/bin/memcached: error while loading shared libraries: libevent-2.0.so.5: cannot open shared object file: No such file or directory

使用LD_DEBUG显示库寻找信息

LD_DEBUG=libs /usr/local/bin/memcached -vv
     15759:    find library=libevent-2.0.so.5 [0]; searching
     15759:     search cache=/etc/ld.so.cache
     15759:     search path=/lib64/tls/x86_64:/lib64/tls:/lib64/x86_64:/lib64:/usr/lib64/tls/x86_64:/usr/lib64/tls:/usr/lib64/x86_64:/usr/lib64           (system search path)
     15759:      trying file=/lib64/tls/x86_64/libevent-2.0.so.5
     15759:      trying file=/lib64/tls/libevent-2.0.so.5
     15759:      trying file=/lib64/x86_64/libevent-2.0.so.5
     15759:      trying file=/lib64/libevent-2.0.so.5
     15759:      trying file=/usr/lib64/tls/x86_64/libevent-2.0.so.5
     15759:      trying file=/usr/lib64/tls/libevent-2.0.so.5
     15759:      trying file=/usr/lib64/x86_64/libevent-2.0.so.5
     15759:      trying file=/usr/lib64/libevent-2.0.so.5

最后一行可以看出,memcached去/usr/lib64目录下寻找libevent-2.0.so.5,先定位libevent-2.0.so.5实际目录

命令:whereis libevent-2.0.so.5
结果:libevent-2.0.so: /usr/lib/libevent-2.0.so.5

这时候需要将做一个软连接

命令:ln -s /usr/lib/libevent-2.0.so.5 /usr/lib64/libevent-2.0.so.5

如果要删除软连接

命令: rm -rf filename

2、 dlopen

dlopen这个函数加载由以null结尾的字符串文件名命名的动态共享对象(共享库)文件,并为加载的对象返回不透明的“句柄”。此句柄与 dlopen API 中的其他函数一起使用,例如dlsym(),dladdr(),dlinfo()和dlclose() [1] 。

如果 filename 为 NULL,则返回的句柄用于主程序。如果 filename 包含斜杠(“/”),则它被解释为(相对或绝对)路径名。否则,动态链接器将按如下方式搜索对象(有关详细信息,请参阅ld.so(8)):

  • (仅限ELF)如果调用程序的可执行文件包含 DT_RPATH 标记,并且不包含 DT_RUNPATH 标记,则会搜索 DT_RPATH标记中列出的目录。
  • 如果在程序启动时,环境变量 LD_LIBRARY_PATH 被定义为包含以冒号分隔的目录列表,则会搜索这些目录。(作为安全措施,set-user-ID 和 set-group-ID程序将忽略此变量。)
  • (仅限ELF)如果调用程序的可执行文件包含 DT_RUNPATH 标记,则搜索该标记中列出的目录。
  • 检查缓存文件/etc/ld.so.cache(由ldconfig(8)维护)以查看它是否包含filename的条目。 搜索目录 /lib和 /usr/lib(按此顺序)。

如果 filename 指定的对象依赖于其他共享对象,则动态链接器也会使用相同的规则自动加载这些对象。 (如果这些对象依次具有依赖性,则此过程可以递归地发生)
flags 参数必须包括以下两个值中的一个:

  • RTLD_LAZY执行延迟绑定。仅在执行引用它们的代码时解析符号。如果从未引用该符号,则永远不会解析它(只对函数引用执行延迟绑定;在加载共享对象时,对变量的引用总是立即绑定)。自glibc 2.1.1,此标志被LD_BIND_NOW环境变量的效果覆盖。
  • RTLD_NOW如果指定了此值,或者环境变量LD_BIND_NOW设置为非空字符串,则在dlopen()返回之前,将解析共享对象中的所有未定义符号。如果无法执行此操作,则会返回错误。flags 也可以通过以下零或多个值进行或运算设置
  • RTLD_GLOBAL此共享对象定义的符号将可用于后续加载的共享对象的符号解析。
  • RTLD_LOCAL这与RTLD_GLOBAL相反,如果未指定任何标志,则为默认值。此共享对象中定义的符号不可用于解析后续加载的共享对象中的引用
  • RTLD_NODELETE (since glibc2.2)在dlclose()期间不要卸载共享对象。因此,如果稍后使用dlopen()重新加载对象,则不会重新初始化对象的静态变量
  • RTLD_NOLOAD (since glibc 2.2)不要加载共享对象。这可用于测试对象是否已经驻留(如果不是,则dlopen()返回NULL,如果是驻留则返回对象的句柄)。此标志还可用于提升已加载的共享对象上的标志。

例如,以前使用RTLD_LOCAL加载的共享对象可以使用RTLD_NOLOAD| RTLD_GLOBAL重新打开。 RTLD_DEEPBIND (since glibc 2.3.4)将符号的查找范围放在此共享对象的全局范围之前。这意味着自包含对象将优先使用自己的符号,而不是全局符号,这些符号包含在已加载的对象中。

基本定义编

功能

打开一个动态链接库,并返回动态链接库的句柄
包含头文件
#include <dlfcn.h>

函数定义

void * dlopen( const char * pathname, int mode);

函数描述

mode是打开方式,其值有多个,不同操作系统上实现的功能有所不同,在linux下,按功能可分为三类:

  1. 解析方式
    RTLD_LAZY:在dlopen返回前,对于动态库中的未定义的符号不执行解析(只对函数引用有效,对于变量引用总是立即解析)。
    RTLD_NOW: 需要在dlopen返回前,解析出所有未定义符号,如果解析不出来,在dlopen会返回NULL,错误为::
    undefined symbol: xxxx…
  2. 作用范围,可与解析方式通过“|”组合使用。
    RTLD_GLOBAL:动态库中定义的符号可被其后打开的其它库解析。 RTLD_LOCAL:
    与RTLD_GLOBAL作用相反,动态库中定义的符号不能被其后打开的其它库重定位。如果没有指明是RTLD_GLOBAL还是RTLD_LOCAL,则缺省为RTLD_LOCAL。
  3. 作用方式 RTLD_NODELETE:
    在dlclose()期间不卸载库,并且在以后使用dlopen()重新加载库时不初始化库中的静态变量。这个flag不是POSIX-2001标准。
    RTLD_NOLOAD:
    不加载库。可用于测试库是否已加载(dlopen()返回NULL说明未加载,否则说明已加载),也可用于改变已加载库的flag,如:先前加载库的flag为RTLD_LOCAL,用dlopen(RTLD_NOLOAD|RTLD_GLOBAL)后flag将变成RTLD_GLOBAL。这个flag不是POSIX-2001标准。
    RTLD_DEEPBIND:在搜索全局符号前先搜索库内的符号,避免同名符号的冲突。这个flag不是POSIX-2001标准。

返回值:

  • 打开错误返回NULL
  • 成功,返回库引用

编译时候要加入 -ldl (指定dl库) -rdynamic(通知链接器将所有符号添加到动态符号表中(目的是能够通过使用 dlopen 来实现向后跟踪)

例如

gcc test.c -o test -ldl-rdynamic

流程

打开库:void* dlopen(const char* libfile,int flag);
取函数:void* dlsym(void* handler, const char* symbol);
运行函数:func
关闭库:int dlclose(void* handler);

使用 dlopen

dlopen()是一个强大的库函数。该函数将打开一个新库,并把它装入内存。该函数主要用来加载库中的符号,这些符号在编译的时候是不知道的。比如 Apache Web 服务器利用这个函数在运行过程中加载模块,这为它提供了额外的能力。一个配置文件控制了加载模块的过程。这种机制使得在系统中添加或者删除一个模块时,都不需要重新编译了。
可以在自己的程序中使用 dlopen()。dlopen() 在 dlfcn.h 中定义,并在 dl 库中实现。它需要两个参数:一个文件名和一个标志。文件名可以是我们学习过的库中的 soname。标志指明是否立刻计算库的依赖性。如果设置为 RTLD_NOW 的话,则立刻计算;如果设置的是 RTLD_LAZY,则在需要的时候才计算。另外,可以指定 RTLD_GLOBAL,它使得那些在以后才加载的库可以获得其中的符号。
当库被装入后,可以把 dlopen() 返回的句柄作为给 dlsym() 的第一个参数,以获得符号在库中的地址。使用这个地址,就可以获得库中特定函数的指针,并且调用装载库中的相应函数。

实例

runlib.c

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#define DLL_PATH "./libsd.so"
typedef int (*func)(int, int);
int main(){
    
    
    void *dlhandler;
    char *error;
    func func = NULL;
    dlhandler = dlopen(DLL_PATH,RTLD_LAZY);
    if(dlhandler == NULL){
    
    
        fprintf(stderr,"%s\n",dlerror());
        exit(-1);
    }
    dlerror();
    func = dlsym(dlhandler,"sumab");
    printf("%d\n",func(1,2));
    dlclose(dlhandler);
    return 0;
}

编译:
gcc -rdynamic -o runlib runlib.c -ldl
运行:
[teanee@localhost runlib]$ ./runlib
成功调用sumab函数。

猜你喜欢

转载自blog.csdn.net/u011942101/article/details/127358446