背景
本文主要介绍QUICTLS依赖库编译时与OpenSSL冲突问题和使用时找不到依赖库问题。
环境
操作系统: Ubuntu 22.04.4 LTS
编译工具: gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
QUICTLS库描述
OpenSSL
官方支持QUIC
协议进度太慢,于是出现了基于OpenSSL库改的QUICTLS库,原因是QUIC协议不是使用原生的TLS1.3协议。QUICTLS是基于OpenSSL修改了TLS1.3相关内容的库,OpenSSL用户态命令还是一样。
问题描述
本机上原先通过apt
安装了OpenSSL3.x
, 编译QUICTLS后, QUICTLS默认的openssl
命令位于/usr/local/bin/openssl
, 官方OpenSSL位于/usr/bin/openssl
, 环境变量PATH
中也是按照这个目录顺序。
如果键入openssl version
,使用的是QUICTLS版本的openssl
命令, 这时会报错:
openssl: error while loading shared libraries: libssl.so.81.3: cannot open shared object file: No such file or directory
问题原因
openssl
命令运行时找不到依赖的动态库, 使用ldd
命令可以查看依赖共享库情况:
ldd $(which openssl)
linux-vdso.so.1 (0x00007fff7****000)
libssl.so.81.3 => not found
libcrypto.so.81.3 => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb650****00)
/lib64/ld-linux-x86-64.so.2 (0x00007fb65****000)
可以看到libssl.so.81.3
和libcrypto.so.81.3
找不到, 官方OpenSSL的动态库是libssl.so.3
和libcrypto.so.3
, 位于/usr/lib/x86_64-linux-gnu
。QUICTLS动态库名称中 81 是 Q 的ASCII码值, 以示区分:
ls -l /usr/lib/x86_64-linux-gnu | grep -E 'libssl|libcrypto'
-rw-r--r-- 1 root root 9098630 2月 1 02:43 libcrypto.a
lrwxrwxrwx 1 root root 14 2月 1 02:43 libcrypto.so -> libcrypto.so.3
-rw-r--r-- 1 root root 4451632 2月 1 02:43 libcrypto.so.3
-rw-r--r-- 1 root root 418464 2月 17 2023 libssl3.so
-rw-r--r-- 1 root root 1231268 2月 1 02:43 libssl.a
lrwxrwxrwx 1 root root 11 2月 1 02:43 libssl.so -> libssl.so.3
-rw-r--r-- 1 root root 667864 2月 1 02:43 libssl.so.3
解决过程
解决过程也是理解Linux动态库加载流程的过程, 当程序执行时, 会通过一定的顺序寻找共享库加载。通过阅读ld.so的manpage文档 , 如果共享库没有包含slash, 按照以下的顺序寻找, 下面是原文:
If a shared object dependency does not contain a slash, then it
is searched for in the following order:
(1) Using the directories specified in the DT_RPATH dynamic
section attribute of the binary if present and DT_RUNPATH
attribute does not exist. Use of DT_RPATH is deprecated.
(2) Using the environment variable LD_LIBRARY_PATH, unless the
executable is being run in secure-execution mode (see
below), in which case this variable is ignored.
(3) Using the directories specified in the DT_RUNPATH dynamic
section attribute of the binary if present. Such
directories are searched only to find those objects required
by DT_NEEDED (direct dependencies) entries and do not apply
to those objects' children, which must themselves have their
own DT_RUNPATH entries. This is unlike DT_RPATH, which is
applied to searches for all children in the dependency tree.
(4) From the cache file /etc/ld.so.cache, which contains a
compiled list of candidate shared objects previously found
in the augmented library path. If, however, the binary was
linked with the -z nodefaultlib linker option, shared
objects in the default paths are skipped. Shared objects
installed in hardware capability directories (see below) are
preferred to other shared objects.
(5) In the default path /lib, and then /usr/lib. (On some
64-bit architectures, the default paths for 64-bit shared
objects are /lib64, and then /usr/lib64.) If the binary was
linked with the -z nodefaultlib linker option, this step is
skipped.
- 设置ELF文件的DT_RPATH, 上面文档指出这个参数过时了, 但是依然很多在使用。编译时指定GCC的相关参数, 比如
-Wl,-rpath=/usr/local/lib64
, 默认情况下文档说是设置DT_RPATH
,
man 1 ld
...
--enable-new-dtags
--disable-new-dtags
This linker can create the new dynamic tags in ELF. But the older ELF systems
may not understand them. If you specify --enable-new-dtags, the new dynamic tags
will be created as needed and older dynamic tags will be omitted. If you
specify --disable-new-dtags, no new dynamic tags will be created. By default, the
new dynamic tags are not created. Note that those options are only available for ELF systems.
...
但是在我机器 Ubuntu22.04, GCC11.04
验证是默认设置的DT_RUNPATH
, 如果要设置DT_RPATH
, 需要显式关闭开关-Wl,--disable-new-dtags
, 编译完成后可以使用如下命令检验:
readelf -d build/client | grep -E 'RUNPATH|RPATH'
- 可以设置
LD_LIBRARY_PATH
, 比如LD_LIBRARY_PATH="/usr/local/lib64" openssl version
能正确运行 - 设置DT_RUNPATH, 方法同1, 但是需要她的使用顺序以及她只应用与DT_NEEDED的依赖库, 他们的子依赖不会使用这个参数指定的地址, 这也是争议的地方, DT_RPATH说是过时了,而且存在安全争议,但是在检索第一位管用
/etc/ld.so.cache
本地缓存,这个需要在机器上自己设置,一般在目录/etc/ld.so.conf.d/
添加配置文件,然后刷新缓存:
echo "/usr/local/lib64" | sudo tee /etc/ld.so.conf.d/quictls.conf # 添加配置文件
sudo ldconfig # 刷新 ld.so.cache
openssl version # 现在能正常执行
# OpenSSL 3.1.4+quic 24 Oct 2023 (Library: OpenSSL 3.1.4+quic 24 Oct 2023)
解决办法
上述已经列出了所有的解决思路和相应解决办法,一般使用最后一种,会在本机添加多个config文件,比如,我的添加了依赖库QUITLS和ngtcp2的配置文件,配置文件内容也简单,就是依赖库所在地址, 即上述的第四种方式。
例子
下面根据QUIC-ECHO工程依赖quictls的例子解释下GCC编译参数, 具体可以参见相关的 Makefile :
gcc -g -Wall -Wextra -DDEBUG -pedantic -Wl,-rpath=/usr/local/lib64 -o build/client client.c connection.c quictls.c stream.c utils.c \
-L/usr/local/lib64 \ # 影响后面的-lssl -lcrypto, 使她们使用quictls而不是openssl的共享库
-lssl -lcrypto \ # 使用前面-L指定目录找到的依赖libssl.so.81.3 libcrypto.so.81.3
-lngtcp2 -lngtcp2_crypto_quictls
TIPS
ld 默认搜索的动态库路径可以通过如下途径查看:
ld --verbose | grep SEARCH_DIR | tr -s ' ;' '\n'
# OR
ldconfig -v 2>/dev/null | grep '^/'