Linux 动态库 soname 实践

xredis

因为项目中使用到了 xredis (C++开发的redis客户端,是对hiredis的C++封装),在 makefile 中发现使用到了 -Wl,-soname 这个语法,之前没怎么了解过,特此记录

makefile 节选如下:

XREDIS_MAJOR=1
XREDIS_MINOR=10.1

# Fallback to gcc when $CC is not in $PATH.
CC:=g++
OPTIMIZATION?=-O3
WARNINGS=-Wall -W -Wwrite-strings
DEBUG?= -g -ggdb
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG) $(ARCH)
REAL_LDFLAGS=$(LDFLAGS) $(ARCH)

DYLIBSUFFIX=so
STLIBSUFFIX=a
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(XREDIS_MAJOR).$(XREDIS_MINOR)
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(XREDIS_MAJOR)
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
STLIB_MAKE_CMD=ar rcs $(STLIBNAME)

尝试 make 一下

[root@localhost xredis-1.10.1]# make
g++ -O3 -fPIC  -Wall -W -Wwrite-strings -g -ggdb  -c src/xRedisClient.cpp -o src/xRedisClient.o
g++ -O3 -fPIC  -Wall -W -Wwrite-strings -g -ggdb  -c src/xRedisClient_keys.cpp -o src/xRedisClient_keys.o
g++ -O3 -fPIC  -Wall -W -Wwrite-strings -g -ggdb  -c src/xRedisClient_sets.cpp -o src/xRedisClient_sets.o
g++ -O3 -fPIC  -Wall -W -Wwrite-strings -g -ggdb  -c src/xRedisClient_strings.cpp -o src/xRedisClient_strings.o
g++ -O3 -fPIC  -Wall -W -Wwrite-strings -g -ggdb  -c src/xRedisClient_connection.cpp -o src/xRedisClient_connection.o
g++ -O3 -fPIC  -Wall -W -Wwrite-strings -g -ggdb  -c src/xRedisClient_hashs.cpp -o src/xRedisClient_hashs.o
g++ -O3 -fPIC  -Wall -W -Wwrite-strings -g -ggdb  -c src/xRedisClient_lists.cpp -o src/xRedisClient_lists.o
g++ -O3 -fPIC  -Wall -W -Wwrite-strings -g -ggdb  -c src/xRedisClient_sortedsets.cpp -o src/xRedisClient_sortedsets.o
g++ -O3 -fPIC  -Wall -W -Wwrite-strings -g -ggdb  -c src/xRedisPool.cpp -o src/xRedisPool.o
g++ -O3 -fPIC  -Wall -W -Wwrite-strings -g -ggdb  -c src/xRedisFunc.cpp -o src/xRedisFunc.o
g++ -shared -Wl,-soname,libxredis.so.1.10.1 -o libxredis.so  src/xRedisClient.o src/xRedisClient_keys.o src/xRedisClient_sets.o src/xRedisClient_strings.o src/xRedisClient_connection.o src/xRedisClient_hashs.o src/xRedisClient_lists.o src/xRedisClient_sortedsets.o src/xRedisPool.o src/xRedisFunc.o
ar rcs libxredis.a src/xRedisClient.o src/xRedisClient_keys.o src/xRedisClient_sets.o src/xRedisClient_strings.o src/xRedisClient_connection.o src/xRedisClient_hashs.o src/xRedisClient_lists.o src/xRedisClient_sortedsets.o src/xRedisPool.o src/xRedisFunc.o

关键信息为

g++ -shared -Wl,-soname,libxredis.so.1.10.1 -o libxredis.so

这里编译生成的对象是 libxredis.so 这个动态库,其 soname 为 libxredis.so.1.10.1

可以看看编译后生成的产物:

# 有一个 libxredis.a 静态库和一个 libxredis.so 动态库
[root@localhost xredis-1.10.1]# ll libxredis.*
-rw-r--r--. 1 root root 3405976 Oct  5 17:40 libxredis.a
-rwxr-xr-x. 1 root root 1319008 Oct  5 17:40 libxredis.so

可以使用 readelf 命令查看动态库的 soname

# 为了简洁些,使用了 grep 作过滤
[root@localhost xredis-1.10.1]# readelf -d libxredis.so  | grep soname
 0x000000000000000e (SONAME)             Library soname: [libxredis.so.1.10.1]

soname

soname 是 Short for shared object name 的缩写,直译就是共享库(动态库)的缩写。

-Wl,-soname -Wl 告诉编译器将后面的参数传递到连接器。而 -soname 指定了共享库的 soname。

那 soname 是怎么怎么产生作用的呢?

使用 ldd 命令或者 readelf 都可以查看应用程序依赖的动态库

# 为了简洁些,使用了 grep 作过滤
[root@localhost lib]# ldd /usr/xxx/bin/yyy | grep xredis
	libxredis.so.1.10.1 => /usr/xxx/lib/libxredis.so.1.10.1 (0x00007f2c4ea68000)
# 在 ldd 命令打印的结果中,“=>”左边的表示该程序需要连接的共享库之 so 名称,右边表示由 Linux 的共享库系统找到的对应的共享库在文件系统中的具体位置。
# 为了简洁些,使用了 grep 作过滤
[root@localhost lib]# readelf -d /usr/xxx/bin/yyy | grep xredis
 0x0000000000000001 (NEEDED)             Shared library: [libxredis.so.1.10.1]

可以看到 yyy 这个模块是依赖于 libxredis.so.1.10.1 这个动态库,但是还记得我们编译生成的产物么?我们编译生成的可是 libxredis.so 这个库啊~

# 将编译生成的 libxredis.so 放到 /home/wuxt/lib 下
[root@localhost lib]# pwd
/home/wuxt/lib
[root@localhost lib]# ls
libxredis.so

# 将 /home/wuxt/lib 加入动态库查找路径
[root@localhost lib]# cat /etc/ld.so.conf
include ld.so.conf.d/*.conf
/home/wuxt/lib

执行 ldconfig 命令,将动态库加入缓存中

[root@localhost lib]# ldconfig -v
/home/wuxt/lib:
	libxredis.so.1.10.1 -> libxredis.so (changed)

我们发现这里做了个软链接

[root@localhost lib]# ll
total 1292
-rwxr-xr-x. 1 root root 1319008 Oct  5 18:00 libxredis.so
lrwxrwxrwx. 1 root root      12 Oct  5 19:35 libxredis.so.1.10.1 -> libxredis.so

将动态库的 soname 指向动态库本身,这样当 yyy 模块需要依赖 libxredis.so.1.10 时,就会找到具体的 libxredis.so 这个库。

问题

但我认为这个 xredis 的 makefile 编写有问题,回到 xredis 的编译

g++ -shared -Wl,-soname,libxredis.so.1.10.1 -o libxredis.so

soname 应该是 libxredis.so.1 或者是 libxredis.so,生成的对象是带完整版本的 libxredis.so.1.10.1,这样当 xredis 升级至 1.10.2 时,还是由 libxredis.so.1 或者是 libxredis.so 指向 libxredis.so.1.10.2。

举例几个常用的库的 soname

cJSON

cJSON

看下 makefile 编写

# 节选 cjson 的 makefile
LIBVERSION = 1.7.15
CJSON_SOVERSION = 1
UTILS_SOVERSION = 1

CJSON_SO_LDFLAG=-Wl,-soname=$(CJSON_LIBNAME).so.$(CJSON_SOVERSION)
UTILS_SO_LDFLAG=-Wl,-soname=$(UTILS_LIBNAME).so.$(UTILS_SOVERSION)

编译 cJSON

[root@localhost cJSON]# ll libcjson.so*
lrwxrwxrwx. 1 root root    13 Oct  5 21:10 libcjson.so -> libcjson.so.1
lrwxrwxrwx. 1 root root    18 Oct  5 21:10 libcjson.so.1 -> libcjson.so.1.7.15
-rwxr-xr-x. 1 root root 46731 Oct  5 21:10 libcjson.so.1.7.15

查看 libcjson.so.1.7.15 的 soname

[root@localhost cJSON]# readelf -d libcjson.so.1.7.15 | grep soname
 0x000000000000000e (SONAME)             Library soname: [libcjson.so.1]

系统自带的库:

libz.zo

[root@localhost lib]# ll libz.so*
lrwxrwxrwx. 1 root root     13 Jan 19  2021 libz.so -> libz.so.1.2.7
lrwxrwxrwx. 1 root root     13 Jan 19  2021 libz.so.1 -> libz.so.1.2.7
-rwxr-xr-x. 1 root root 109093 Sep 27  2020 libz.so.1.2.7
[root@localhost lib]# 
[root@localhost lib]# readelf -d libz.so.1.2.7 | grep soname
 0x000000000000000e (SONAME)             Library soname: [libz.so.1]

libc.so 不过这个有点奇怪,libc-2.17.so 的 soname 是 libc.so.6?

[root@localhost lib64]# ll libc.so*
-rw-r--r--. 1 root root 253 Jan 19  2015 libc.so
lrwxrwxrwx. 1 root root  12 May 19  2017 libc.so.6 -> libc-2.17.so
[root@localhost lib64]# 
[root@localhost lib64]# readelf -d libc-2.17.so | grep soname
 0x000000000000000e (SONAME)             Library soname: [libc.so.6]
[root@localhost lib64]# 

总结

Linux 系统的这种动态库管理方式值得我们在实际项目的动态库管理中使用,既保证了动态库的升级,又能得到方便地使用。

Linux 的动态库的命名格式是 libbar.so.x.y.z,最后一个 z 版本的变动一定是兼容的。y 版本升级一般向前兼容。所以这个 y 和 z 不能写死。x 版本变动一般是不兼容升级。所以使用 soname 是最为合理的。

参考:
linux下动态库中的soname

linux下动态库soname简介

Linux动态库soname的使用

Linux下动态链接库文件的realname、soname和linkname

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 什么是静态?什么是动态? - 静态是在编译时被链接到可执行文件中的,它包含了所有的函数和数据,因此可执行文件较大,但运行时不需要加载额外的文件。 - 动态是在程序运行时被加载的,它包含了多个可执行文件可以共享的函数和数据,因此可执行文件较小,但需要在运行时加载额外的文件。 2. 静态动态的优缺点是什么? - 静态的优点是可靠性高,因为所有的代码都被编译到可执行文件中,不需要额外的文件。缺点是可执行文件较大,占用磁盘空间较大,且无法在运行时更新文件。 - 动态的优点是可执行文件较小,因为共享文件可以被多个可执行文件共享。缺点是依赖性高,因为需要在运行时加载文件,如果缺少或版本不匹配会导致程序无法运行。 3. 动态的加载过程是怎样的? - 当程序需要访问动态中的函数或数据时,操作系统会检查可执行文件的依赖关系,加载动态文件到内存中,并在符号表中查找相应的函数或数据。 - 如果找到了相应的函数或数据,程序就可以调用函数或访问数据。如果没有找到,操作系统会抛出未定义符号的错误。 4. 如何编译静态动态? - 编译静态可以使用命令"ar"和"ranlib",例如: ``` gcc -c file1.c file2.c ar rcs libmylib.a file1.o file2.o ``` - 编译动态可以使用命令"gcc"和"-shared"选项,例如: ``` gcc -c -fpic file1.c file2.c gcc -shared -o libmylib.so file1.o file2.o ``` 5. 如何使用静态动态? - 使用静态可以在编译时链接文件,例如: ``` gcc -o myprog main.c -L/path/to/lib -lmylib ``` - 使用动态可以在程序运行时加载文件,例如: ``` LD_LIBRARY_PATH=/path/to/lib ./myprog ``` 6. 如何避免动态版本不匹配的问题? - 在编译动态时,可以使用版本号来标识不同的版本,例如: ``` gcc -shared -Wl,-soname,libmylib.so.1 -o libmylib.so.1.0.0 file1.o file2.o ``` - 在程序中调用函数时,可以使用版本号来指定版本,例如: ``` dlopen("libmylib.so.1", RTLD_NOW); ``` 这样可以确保程序使用正确的版本,并避免版本不匹配的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值