Linux平台同一动态库不同版本的加载顺序研究

本文主要研究同一动态库名称,但版本不同情况下,程序加载的表现。

背景

最近遇到一个业务程序迁移不同操作系统的问题:新的Linux操作系统原本已有某些动态库,如Qt、openssl等等,且版本较新。业务程序在原来的Linux系统也使用这些库,但版本较低,但十分稳定。从软件维护成本角度考量,软件不在新操作系统下重新编译。

问题简化为:同一套软件,在不同版本的Linux系统中能正常运行。

将问题抽象化:同一程序,调用不同版本的同一动态库,其表现如何?由哪些因素决定?

实现

本节实现一个简单的程序,该程序调用一个动态库,动态库实现2个版本,主程序打印其版本号,通过日志观察运行结果。

动态库头文件(dl.h):

#ifndef DL_H
#define DL_H

#ifdef __cplusplus
extern "C" {
#endif

int GetVersion(char *version);
int Foo();
int Bar();

#ifdef __cplusplus
}
#endif

#endif

动态库版本0.1实现(dl.cpp):

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "dl.h"

int GetVersion(char *version)
{
    int ver = 1;
    sprintf(version, "ver: 0.%d", ver);
    return 0;
}

int Foo()
{
    printf("This is Foo...\n");
    return 0;
}

动态库版本0.2实现(dl_new.cpp):

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "dl.h"

int GetVersion(char *version)
{
    int ver = 2;
    sprintf(version, "ver: 0.%d  NEW VERSION", ver);
    return 0;
}

int Foo()
{
    printf("This is Foo...  NEW VERSION\n");
    return 0;
}

测试主函数(main.cpp)如下:

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

#include <string.h>
#include <errno.h>

#include "dl.h"

int main(void)
{
    char version[64] = {0};
    printf("so test...\n");
    
    GetVersion(version);
    printf("in main : get ver: %s\n", version);
    
    Foo();

    return 0;
}

上述程序十分简单,因此不再着墨描述了。

编译脚本(mybuild.sh)如下:

g++ -Wall -Wfatal-errors -MMD -ggdb -rdynamic -DJIMKENT -I./ -I./inc -fpic  -std=c++11 -c dl.cpp -o dl.o

g++ -Wall -Wfatal-errors -MMD -ggdb -rdynamic -DJIMKENT -I./ -I./inc -fpic  -std=c++11 -c dl_new.cpp -o dl_new.o

g++ -Wall -Wfatal-errors -MMD -ggdb -rdynamic -DJIMKENT -I./ -I./inc -fpic  -std=c++11 -c main.cpp -o main.o

g++ dl.o -lpthread -lrt -ldl -shared -o libfoobar.so

g++ dl_new.o -lpthread -lrt -ldl -shared -o libfoobar_new.so

g++ main.o  -lpthread -lrt -ldl -lfoobar -L./ -o a.out

本着演示的目的,本文不遵守Linux常规的动态库命令规范,而是直接用libfoobar.solibfoobar_new.so来区分2个版本的动态库,版本号分别为0.1和0.2。通过重命名让程序认为是同一库文件。

需要说明的是,测试程序连接的库目录为当前目录(即通过-L./指定),这也是为了测试方便而设置的。

实验

经过上述编译后直接进行实验,无须再编译。

动态库在当前目录

首先查看其依赖库:

$ ldd a.out 
        linux-vdso.so.1 =>  (0x00007ffd18b45000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f35c92d8000)
        librt.so.1 => /lib64/librt.so.1 (0x00007f35c90d0000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007f35c8ecc000)
        libfoobar.so (0x00007f35c8cc9000)
        libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007f35c89c2000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f35c86c0000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f35c84aa000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f35c80dc000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f35c94f4000)

由于libfoobar.so位置为当前目录,故直接运行./a.out正常,运行结果:

$ ./a.out 
so test...
in main : get ver: ver: 0.1
Foo...

动态库在系统目录

libfoobar.so 移到 /lib64/目录::

$ sudo mv libfoobar.so /lib64

查看依赖库:

$ ldd a.out 
        linux-vdso.so.1 =>  (0x00007ffc599fb000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f1b45fdf000)
        librt.so.1 => /lib64/librt.so.1 (0x00007f1b45dd7000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007f1b45bd3000)
        libfoobar.so => /lib64/libfoobar.so (0x00007f1b459d0000)
        libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007f1b456c9000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f1b453c7000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f1b451b1000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f1b44de3000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f1b461fb000)

依赖库指向正常:libfoobar.so => /lib64/libfoobar.so

因此运行结果正常:

$ ./a.out 
so test...
in main : get ver: ver: 0.1
Foo...

删除系统目录动态库

libfoobar.so 移到/tmp目录:

 sudo mv /lib64/libfoobar.so /tmp/  

运行失败:

$ ./a.out 
./a.out: error while loading shared libraries: libfoobar.so: cannot open shared object file: No such file or directory

查看其依赖库:

$ ldd a.out 
        linux-vdso.so.1 =>  (0x00007ffe7bbe4000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fc31c542000)
        librt.so.1 => /lib64/librt.so.1 (0x00007fc31c33a000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007fc31c136000)
        libfoobar.so => not found
        libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007fc31be2f000)
        libm.so.6 => /lib64/libm.so.6 (0x00007fc31bb2d000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fc31b917000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fc31b549000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fc31c75e000)

赫然看到libfoobar.so => not found,因此运行失败属正常现象。

更新动态库

恢复 libfoobar.so/lib64目录:

sudo mv /tmp/libfoobar.so /lib64/

libfoobar_new.so 拷贝为libfoobar.so

 $ cp libfoobar_new.so libfoobar.so  

运行正常,版本号变更,结果如下:

$ ./a.out 
so test...
in main : get ver: ver: 0.2  NEW VERSION
This is Foo...  NEW VERSION

可以看到使用新版本库。 但此时系统目录存在旧版本文件:

$ ls /lib64/libfoobar.so  -lh
-rwxrwxrwx. 1 root root 9.3K 8月   7 15:13 /lib64/libfoobar.so

继续查依赖库:

$ ldd a.out 
        linux-vdso.so.1 =>  (0x00007ffca617c000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f5ee236a000)
        librt.so.1 => /lib64/librt.so.1 (0x00007f5ee2162000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007f5ee1f5e000)
        libfoobar.so (0x00007f5ee1d5b000)
        libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007f5ee1a54000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f5ee1752000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f5ee153c000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f5ee116e000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f5ee2586000)

libfoobar.so (0x00007f5ee1d5b000)可以看出直接使用的动态库位于当前目录,因此使用新版本。

设置环境变量LD_LIBRARY_PATH

删除程序当前目录的 libfoobar.so ,将 libfoobar_new.so拷贝到 /tmp 目录并重命名。

cp libfoobar_new.so /tmp/libfoobar.so 

/tmp添加到环境变量LD_LIBRARY_PATH,且放到原值的前面:

export LD_LIBRARY_PATH=/tmp:$LD_LIBRARY_PATH

运行正常,版本号变更,使用新版本库。

$ ./a.out 
so test...
in main : get ver: ver: 0.2  NEW VERSION
This is Foo...  NEW VERSION

查看依赖库:

$ ldd a.out 
        linux-vdso.so.1 =>  (0x00007ffe6ef99000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fad25254000)
        librt.so.1 => /lib64/librt.so.1 (0x00007fad2504c000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007fad24e48000)
        libfoobar.so => /tmp/libfoobar.so (0x00007fad24c45000)
        libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007fad2493e000)
        libm.so.6 => /lib64/libm.so.6 (0x00007fad2463c000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fad24426000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fad24058000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fad25470000)

libfoobar.so => /tmp/libfoobar.so可以看到此时依赖的正是在/tmp目录下的动态库。注意,此时在/lib64目录下依然存在一个同名但不同版本的libfoobar.so文件。由于/tmp优先级较高,因此使用新版本库。

小结

从实验结果可以得到结论:

  • 可以在编译时指定程序所用的动态库目录。
  • 但也可以将动态库存储于系统目录,如/lib64/。(注:不同系统,目录有所不同)
  • 也可以通过设置LD_LIBRARY_PATH环境变量,添加自定义目录到该环境变量的方式,将程序找到所需的版本。另外可以调整该环境变量中的值,达到使用不同版本动态库的目的。

最后值得一提的是,对于系统核心库,如Qt、C库、等,万不可直接拷贝不同版本的库到系统目录。如某桌面组件使用的Qt库版本是5.12版本,如果拷贝了4.8版本,则会导致组件无法正常运行——至少笔者遇到过此类问题,由于当时还拷贝其它库,后果是系统启动失败,需重新安装系统。

猜你喜欢

转载自blog.csdn.net/subfate/article/details/141039408