本文主要研究同一动态库名称,但版本不同情况下,程序加载的表现。
背景
最近遇到一个业务程序迁移不同操作系统的问题:新的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.so
和libfoobar_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版本,则会导致组件无法正常运行——至少笔者遇到过此类问题,由于当时还拷贝其它库,后果是系统启动失败,需重新安装系统。