Linux下动态链接库的查找问题

Linux下动态链接库的查找问题

上一篇文章我们从 Linux C 编程的角度分析了一下 Linux 中的静态链接库和动态链接库的区别,这篇文章着重从 Linux 编译和运行的角度分析一下 Linux 动态链接库的查找问题。

一、编译期间动态链接库报错

Linux下编译应用程序的configure阶段常常会出现如下错误:/usr/bin/ld: cannot find -lxxx 意思是编译过程找不到对应库文件。其中,-lxxx表示链接库文件 libxxx.so。一般出现这种错误有以下几种应对方法:

1.1、通过 root 权限将需要的 lib 库安装到系统中

对于这种情况,可以通过下载安装lib来解决,如使用对应版本的软件包管理器下载安装缺失的动态链接库或者通过源码方式安装依赖的库文件。

一般来说,yum安装的lib库默认目录位于/usr/lib/usr/lib64下,源码包安装的库文件默认目录位于/usr/local/lib下,如果拥有root权限,能够通过包管理器或者源码包将lib库安装至系统默认目录中,那么./configure往往能够正确找到依赖的库与头文件,编译便能顺利进行下去,但是如果权限受限则是另一回事了,本文的后面会着重讲到这一类情况。

此情况种还有另外一种不多见的情况,就是库文件的链接错误,这时候我们可以通过find指令定位到链接文件,查看链接文件是否正确的指向了我们希望的lib,如果不是,用 ln -sf */libxxx.so.x */libxxx.so 指令修改它。

1.2、通过配置 pkg-config 使编译器找到需要的lib库

在安装的configure阶段,为了检测安装安装环境是否满足,通常情况下都是通过一个叫做 pkg-config 的工具来检测它需要依赖的动态库是否存在,pkg-config通常情况都是位于/usr/bin目录下,是个可执行程序。

一般来说,如果库的头文件不在/usr/include目录中,那么在编译的时候需要用-I参数指定其路径。由于同一个库在不同系统上可能位于不同的目录下,用户安装库的时候也可以将库安装在不同的目录下,所以即使使用同一个库,由于库的路径的不同,造成了用-I参数指定的头文件的路径和在连接时使用-L参数指定lib库的路径都可能不同,其结果就是造成了编译命令界面的不统一。可能由于编译,连接的不一致,造成同一份程序从一台机器copy到另一台机器时就可能会出现问题。

pkg-config 就是用来解决编译连接界面不统一问题的一个工具。它提供的主要功能有:

  1. 检查库的版本号。如果所需库的版本不满足要求,打印出错误信息,避免连接错误版本的库文件。
  2. 获得编译预处理参数,如宏定义,头文件的路径。
  3. 获得编译参数,如库及其依赖的其他库的位置,文件名及其他一些连接参数。
  4. 自动加入所依赖的其他库的设置。

它的基本思想:pkg-config是通过库提供的一个.pc文件获得库的各种必要信息的,包括版本信息、编译和连接需要的参数等。需要的时候可以通过pkg-config提供的参数(–cflags, –libs),将所需信息提取出来供编译和连接使用。这样,不管库文件安装在哪,通过库对应的.pc文件就可以准确定位,可以使用相同的编译和连接命令,使得编译和连接界面统一。

一般当我们安装完某个程序后,如果它提供了动态库的功能,在源码中都会有一个或多个以pc结尾的文件,当执行完make install后这些pc文件拷贝到${prefix}/lib/pkgconfig这个目录里,这里的prefix就是我们在configure阶段时通过配置参数–prefix指定的,缺省情况这个值就是/usr/local,所以这些pc文件最终会被拷贝到/usr/local/lib/pkgconfig目录下。那么这些pc文件有啥用呢?我们随便打开一个来瞅瞅:

[root@localhost ~]# cat /usr/local/lib/pkgconfig/librtmp.pc
prefix=/usr/local
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
incdir=${prefix}/include

Name: librtmp
Description: RTMP implementation
Version: v2.3
Requires: libssl,libcrypto
URL:
Libs: -L${libdir} -lrtmp -lz
Cflags: -I${incdir}

跟我们configure阶段相关的主要集中在Libs和Cflags两项上面,如果你此时再执行下面这两条命令,就全明白了:

[root@localhost ~]# pkg-config --cflags librtmp
-I/usr/local/include
[root@localhost ~]# pkg-config --libs librtmp
-L/usr/local/lib -lrtmp -lz -lssl -lcrypto

也就是说,pkg-config把我们以前需要在Makefile里指定编译和链接时所需要用到的参数从手工硬编码的模式变成了自动完成。但有的时候你已经确确实实已经安装了它需要的库,configure阶段依然会提示找不到依赖库,那回事什么原因呢?主要原因应该有以下两点:

  1. pkg-config搜索了所有它认为合适的目录都没找着这个库对应的pc文件的下落;

  2. 这个库在发布时根本就没有提供它的pc文件。

那么,现在问题来了:pkg-config的查找路径是哪里?

  1. 如果你是通过yum和rpm包安装的:
prefix=/usr
libdir=${prefix}/lib=/usr/lib
datadir=${prefix}/share=/usr/share
  1. 如果你是通过源码包安装的,且没有指定prefix的值
prefix=/usr/local
libdir=${prefix}/lib=/usr/local/lib
datadir=${prefix}/share=/usr/local/share 

pkg-config在查找对应软件包的信息时的缺省搜索路径已经很清楚了,有一点写错了,不是${libdir}/pkg-config,而应该是${libdir}/pkgconfig${datadir}/pkgconfig。如果你软件包对应的pc文件都不在这两个目录下时,pkg-config肯定找不到。既然原因都已经找到了,那解决办法也就多种多样了。

方案一:我们可以在安装我们那个被依赖的软件包时,在configure阶段用–prefix参数把安装目录指定到/usr目录下;

方案二:通过一个名叫PKG_CONFIG_PATH的环境变量来向pkg-config指明我们自己的pc文件所在的路径,不过要注意的是PKG_CONFIG_PATH所指定的路径优先级比较高,pkg-config会先进行搜索,完了之后才是去搜索缺省路径。

前者的优点是以后再通过源码安装软件时少了不少麻烦,缺点是用户自己的软件包和系统软件混到一起不方便管理,所以实际使用中,后者用的要多一些。

针对没有root权限的情况下,我们可以通过在执行./configure前设定PKG_CONFIG_PATH环境变量来实现 pkg-config 对依赖库的查找:

PKG_CONFIG_PATH=/mylib/libxxx/lib/pkgconfig:$PKG_CONFIG_PATH ./configure --prefix=/my/local

1.3、通过配置 --with-PACKAGE 使编译器找到需要的lib库

运行./configure --help通过configure可以配置很多信息,下面挑几个重要的选项进行描述。

  • --prefix=PREFIX配置安装路径,要用绝对路径。make install时会把可执行文件,配置文件或库安装到目录下的对应目录中,如 bin/、lib/ 等;

  • --build=BUILD编译所在的环境;

  • --host=HOST程序运行的环境对应的交叉编译器;

  • --with-PACKAGE在自由软件社区里,有使用已有软件包和库的优秀传统.当用’configure’来配置一个源码树时,可以提供其他已经安装的软件包的信息.例如,倚赖于Tcl和Tk的BLT器件工具包.要配置BLT,可能需要给’configure’提供一些关于我们把Tcl和Tk装的何处的信息:

$ ./configure --with-tcl=/usr/local --with-tk=/usr/local

其它关键是环境变量,包括:

CC              C compiler command
CFLAGS          C compiler flags
LDFLAGS         linker flags, e.g. -L<lib dir> if you have libraries in a 
                nonstandard directory <lib dir>
LIBS            libraries to pass to the linker, e.g. -l<library>
CPPFLAGS        C/C++/Objective C preprocessor flags, e.g. -I<include dir> if
                you have headers in a nonstandard directory <include dir>
CPP             C preprocessor
PKG_CONFIG      path to pkg-config utility
LIBXML2_CFLAGS  C compiler flags for LIBXML2, overriding pkg-config
LIBXML2_LIBS    linker flags for LIBXML2, overriding pkg-config

1.4、直接指定编译选项使编译器找到需要的lib库

前面我们已经提到过使用 pkg-config 帮助编译器找到需要的lib库或者通过配置 --with-PACKAGE 使编译器找到需要的lib库,但是如果我们所依赖的某个包,既没有提供pc文件,./configure 中又没有提供 --with-PACKAGE 选项时要怎么办呢,这是侯就需要手动指定编译参数来帮编译器找到所需要的库文件了:

CPPFLAGS="-I/include/path"  LDFLAGS="-L/lib/path"  ./configure --prefix=/...

补充:

LIBRARY_PATHLD_LIBRARY_PATH 这两个环境变量很容易搞混,LIBRARY_PATH 用于指定编译期间搜索lib库的路径,编译器会先搜索该变量指定的路径,找不到才去系统默认搜索路径搜索;而LD_LIBRARY_PATH 则用于指定程序运行期间查找 so 动态链接库的搜索路径。

1.5、补充:configure使用相对路径编译软件

我们在编译软件时,有时希望编译出来的可执行文件,在调用依赖库的时候能使用相对路径,这样就可以免去设置一些环境变量的问题,办法就是使用 rpath 参数,用法如下所示:

./configure --prefix=/usr/local LDFLAGS=-Wl,-rpath=../lib,--disable-new-dtags

../lib就是相对于可执行文件的lib库的路径

二、运行期间动态链接库报错

2.1、rpath和patchelf

rpath全称是run-time search path。Linux下所有elf格式的文件都包含它,特别是可执行文件。它规定了可执行文件在寻找.so文件时的第一优先位置。

另外,elf文件中还规定了runpath。它与rpath相同,只是优先级低一些。

2.1.1、搜索 .so 的优先级顺序
  • 编译目标代码时指定的动态库搜索路径; 如果在编译程序时增加参数-Wl,-rpath='.', 这时生成程序的Dynamic section会新加一个RPATH
  • 环境变量LD_LIBRARY_PATH指定的动态库搜索路径; ( 可用export LD_LIBRARY_PATH="NEWDIRS" 命令添加临时环境变量 )
  • RUNPATH: 写在elf文件中
  • ldconfig的缓存:配置文件/etc/ld.so.conf中指定的动态库搜索路径;(系统默认情况下未设置)
  • 默认的动态库搜索路径/lib
  • 默认的动态库搜索路径/usr/lib

RPATHRUNPATH中间隔着LD_LIBRARY_PATH,可以通过修改LD_LIBRARY_PATH来指定.so文件,大多数编译器都将输出的RPATH留空,并用RUNPATH代替RPATH

2.1.2、查看RPATH

对于任意的elf文件,可以使用 $ readelf -d filename | grep PATH 来查看。

0x000000000000001d (RUNPATH) Library runpath: [$ORIGIN/]

结果有两类,一个是RPATH,另一个是RUNPATH。一般情况下,RPATH为空,而RUNPATH不为空。

RPATH中有个特殊的标识符$ORIGIN。这个标识符代表elf文件自身所在的目录。

当希望使用相对位置寻找.so文件,就需要利用$ORIGIN设置RPATH。多个路径之间使用冒号:隔开。

2.1.3、设置RPATH

在gcc中,设置RPATH的办法很简单,就是设置linker的rpath选项:

gcc -L. -larith main.c -Wl,-rpath='.' -o main

(-Wl参数意思是把rpath选项传递到链接阶段)

如果需要设置$ORIGIN

$ gcc -Wl,-rpath,'$ORIGIN/lib' test.cpp

注意,虽然选项里写着RPATH,但它设置的还是RUNPATH。原因在前文有交代。

在CMake中,使用变量来控制RPATH:INSTALL_RPATHBUILD_RPATH。设置的办法是:

SET_TARGET_PROPERTIES(target PROPERTIES INSTALL_RPATH "$ORIGIN;/another/run/path")

(cmake中多个RPATH使用分号隔开)

2.1.4、patchelf

patchelf 是一个用来修改elf格式的动态库和可执行程序的小工具,可以修改动态链接库的库名字,以及链接库的RPATH。

  • 打印出动态库的soname

    patchelf --print-soname xxx.so
    
  • 修改动态库的soname

    patchelf --set-soname oldxxx.so newxxx.so
    
  • 查看并修改第三方依赖库

    patchelf --print-needed xxx.so
    patchelf --replace-needed oldxxx.so newxxx.so this.so
    
  • 修改rpath

    patchelf --set-rpath '$ORIGIN/' main
    

2.2 ldconfig 命令

ldconfig 命令的作用主要是在默认搜寻目录/lib/usr/lib以及动态库配置文件/etc/ld.so.conf/etc/ld.so.conf.d/*.conf内所列的目录下,搜索出可共享的动态链接库(格式如lib*.so*),进而创建出动态装入程序(ld.so)所需的连接和缓存文件,缓存文件默认为/etc/ld.so.cache,此文件保存已排好序的动态链接库名字列表。linux下的共享库机制采用了类似高速缓存机制,将库信息保存在/etc/ld.so.cache,程序连接的时候首先从这个文件里查找,然后再到ld.so.conf的路径中查找。为了让动态链接库为系统所共享,需运行动态链接库的管理命令ldconfig,此执行程序存放在/sbin目录下。

ldconfig命令参数:

  • -v或–verbose:ldconfig将显示正在扫描的目录及搜索到的动态链接库,还有它所创建的连接的名字;

  • -f CONF:此选项指定动态链接库的配置文件为CONF,系统默认为/etc/ld.so.conf;

  • -p或–print-cache:此选项指示ldconfig打印出当前缓存文件所保存的所有共享库的名字;

  • -V:此选项打印出ldconfig的版本信息,而后退出。

从以上可知:

/lib/usr/lib里面添加库文件,是无需将路径添加到/etc/ld.so.conf中去的,但是需要使用命令sudo ldconfig,否则无法找到库文件。在上述两个目录之外的路径添加库文件,需要先将将库文件的路径追加入/etc/ld.so.conf/etc/ld.so.conf.d/*.conf中。比如你在部署软件时,有些动态库安装在/usr/local/mysql/lib目录下,可以通过如下方法实现:

echo "/usr/local/mysql/lib" >> /etc/ld.so.conf #或者
echo "/usr/local/mysql/lib" > /etc/ld.so.conf.d/mysql.conf
sudo ldconfig -v | grep mysql  # 查看mysql库文件是否被找到

2.3 LD_LIBRARY_PATH

前面使用 ldconfig 命令来指定装载器搜索动态链接库,但这是针对有root权限的用户的解决办法。没有root权限运行软件时,Linux也为我们提供了一个名为LD_LIBRARY_PATH的环境变量来解决运行时动态库查找路径的解决方案。由这个环境变量所指定的路径会被装载器/lib/ld-2.12.so优先查找,然后才是动态库库缓存文件/etc/ld.so.cache。这种方式一般只是临时使用,不建议将export LD_LIBRARY_PATH=...添加到profile或者rc中。

关于PKG_CONFIG_PATHLD_LIBRARY_PATH的区别:

  • PKG_CONFIG_PATH从字面意思上翻译,就是“软件包的配置路径”,软件编译过程中需要通过PKG_CONFIG_PATH寻找所依赖的动态库;
  • LD_LIBRARY_PATH从字面意思上翻译,就是“装载器的库路径”,LD是Loader的简写,Linux系统启动一个程序的过程就叫做装载,一个程序要执行时它或多或少的会依赖一些动态库(静态编译的除外)。当你用ldd 可执行程序名查看一个软件启动时所依赖的动态库,如果输出项有一项libxxx.so.y=> not found,那这个软件便运行不起来。

参考

  1. 谈谈Linux下动态库查找路径的问题
  2. c++ configure帮助
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值