Linux运行时动态库搜索路径优先级

Windows运行时动态库搜索路径优先级:

在Windows运行时,动态库(通常指DLL文件)的搜索路径遵循一定的优先级顺序,以确保程序能够正确地加载所需的动态库。以下是对Windows运行时动态库搜索路径优先级的总结:

  1. 应用程序所在的目录
  • 当一个应用程序(如exe文件)尝试加载一个DLL时,它首先会在自己所在的目录中查找该DLL文件。这是搜索路径中的第一优先级。
  1. 系统目录(System32)
  • 如果在应用程序所在目录中没有找到DLL,系统接下来会在系统目录中查找。在Windows操作系统中,这通常是C:\Windows\System32目录。此目录包含了大量系统级的DLL文件,这些文件对于操作系统的正常运行至关重要。
  1. Windows目录
  • 如果系统目录中也没有找到所需的DLL,搜索会继续到Windows目录下,即C:\Windows
  1. 环境变量PATH中指定的目录
  • 如果以上三个位置都没有找到DLL,系统会检查环境变量PATH中列出的所有目录。PATH环境变量是一个系统级变量,它包含了多个目录的路径,用于指导系统在执行文件或脚本时搜索相关文件的路径。因此,用户可以通过修改PATH环境变量来添加额外的搜索路径。

一、Linux运行时动态库搜索路径优先级基础:

在Linux系统中,运行时动态库(shared libraries)的搜索路径优先级是由多个因素决定的。以下是这些因素的详细解释和优先级顺序:

1. RPATH

  • 定义:RPATH(也称为DT_RPATH)是在编译时设置在可执行文件中的路径,它指定了程序运行时应该搜索库文件的位置。
  • 优先级:如果可执行文件包含了RPATH,那么链接器会首先按照RPATH指定的路径来搜索所需的库。
  • 查看和修改方法:可以使用readelf -d xxx | grep rpath来查看库文件是否指定了rpath,可以在编译时设置RPATH。
// 在编译时设置rpath
if(OS_LINUX)
    set_target_properties(xxx PROPERTIES LINK_FLAGS "-Wl,-rpath='$ORIGIN' ")
endif()

备注:$ORIGIN是一个动态链接器的特殊变量,表示可执行文件或共享库文件所在的目录,可以用于指定相对路径。

2. LD_LIBRARY_PATH环境变量

  • 定义:LD_LIBRARY_PATH是一个环境变量,用户可以在运行时设置,以添加额外的库搜索路径。
  • 优先级:LD_LIBRARY_PATH中指定的路径会在系统默认路径之前进行查找,但其优先级低于RPATH。
  • 设置方法
    • 临时设置:可以通过export LD_LIBRARY_PATH=/my/lib/path/来设置环境变量;
    • 永久设置(不安全不推荐):可以添加到shell的配置文件中,如:~/.bashrc或~/.bash_profile,设置完后执行source命令即可立即生效。

3. RUNPATH

  • 定义:RUNPATH是另一个与RPATH相似的特性,同样是在可执行文件或共享库被链接时设置,用于指定运行时查找共享库的路径。值得注意的是,当RUNPATH存在时,它会覆盖RPATH的设置。
  • 优先级:RUNPATH中指定的路径会在系统默认路径之前进行查找,但其优先级低于LD_LIBRARY_PATH。
  • 查看和设置方法:可以使用readelf -d xxx | grep runpath来查看库文件是否指定了runpath,并使用工具如patchelf来设置和修改RUNPATH
// 使用工具修改runpath
patchelf --set-rpath '$ORIGIN' xxx

4. ld.so.cache

  • 定义:/etc/ld.so.cache是动态链接器ld.so的缓存文件,存储了系统中所有可用的共享库路径和名称信息。/etc/ld.so.cache由ldconfig命令生成,ldconfig会扫描系统中指定路径下的共享库文件,并更新/etc/ld.so.cache文件
  • 优先级:ld.so.cache的优先级低于LD_LIBRARY_PATH和RUNPATH,但高于其他,这是为了快速找到和加载需要的共享库。
  • 更新方法:运行sudo ldconfig命令来更新缓存。
// /etc/ld.so.cache是二进制文件可以通过strings命令查看其内容,例如查看libstdc++.so的搜索路径
strings /etc/ld.so.cache | grep libstdc++.so

5. 配置文件/etc/ld.so.conf及/etc/ld.so.conf.d/目录

  • 定义:/etc/ld.so.conf是系统的库配置文件,包含了系统级别的库搜索路径。/etc/ld.so.conf.d/目录用于存放额外的配置文件。
  • 优先级:链接器会读取这些文件,并按照其中列出的路径来搜索库,其优先级低于LD_LIBRARY_PATH和RUNPATH。
  • 修改方法:修改这些文件后,可以运行sudo ldconfig命令来更新缓存。

6. 默认的系统路径

  • 定义:在大多数Linux系统中,/lib和/usr/lib目录(以及它们的64位对应目录/lib64和/usr/lib64)都是默认的库搜索路径。
  • 优先级:这些路径的优先级最低,如果在前面的路径中都没有找到所需的库,链接器会回退到这些默认路径。

总结

Linux运行时动态库的搜索路径优先级大致如下:

  1. RPATH(DT_RPATH)
  2. LD_LIBRARY_PATH环境变量
  3. RUNPATH(如果DT_RPATH为空)
  4. ld.so.cache
  5. 配置文件/etc/ld.so.conf及/etc/ld.so.conf.d/目录
  6. 默认的系统路径(/lib, /usr/lib, /lib64, /usr/lib64)
 1 开始
 2    ├──> 检查RPATH(DT_RPATH)
 3    │       ├──> 如果找到库,则加载并使用
 4    │       └──> 否则,继续
 5    ├──> 检查LD_LIBRARY_PATH环境变量
 6    │       ├──> 如果找到库,则加载并使用
 7    │       └──> 否则,继续
 8    ├──> 检查RUNPATH(如果DT_RPATH为空)
 9    │       ├──> 如果找到库,则加载并使用
10    │       └──> 否则,继续
11    ├──> 搜索ld.so.cache缓存
12    │       ├──> 如果找到库,则加载并使用
13    │       └──> 否则,继续
14    ├──> 读取配置文件/etc/ld.so.conf及/etc/ld.so.conf.d/目录
15    │       ├──> 对于每个配置文件中的路径
16    │       │       ├──> 搜索该路径
17    │       │       │       ├──> 如果找到库,则加载并使用
18    │       │       │       └──> 否则,继续搜索
19    │       │       └──> 结束搜索该路径
20    │       └──> 如果所有配置文件中均未找到,则继续
21    └──> 搜索默认的系统路径(/lib, /usr/lib, /lib64, /usr/lib64)
22         ├──> 如果找到库,则加载并使用
23         └──> 否则,报告错误,无法找到库

请注意,这个顺序可能会根据具体的Linux发行版和链接器实现而有所不同。在实际应用中,可以通过ldd命令来查看一个可执行文件或库文件的依赖关系,以及它们是如何被解析的。

补充说明:

$ORIGIN和./的区别

在Linux和其他类Unix系统中,当动态链接库(如.so文件)被加载时,系统需要知道从哪里查找这些库。rpath(运行时库搜索路径)是存储在可执行文件或库本身中的一种机制,用于指示动态链接器在哪些位置查找依赖的库。

Library rpath:[$ORIGIN/]和 Library rpath:[./]指定了两种不同的相对路径:

1. [$ORIGIN/]:

  • $ORIGIN 是一个特殊的占位符,代表可执行文件或库文件自身的目录;
  • 当设置为[$ORIGIN/]时,它告诉动态链接器在可执行文件或库所在的同一目录下查找依赖的库;
  • 这意味着,如果你的可执行文件位于/path/to/app/myapp,那么动态链接器会在 /path/to/app目录下查找依赖的库。

2. [. /]:

  • ./表示当前工作目录,即启动可执行文件时所在的目录;
  • 当设置为[./]时,它指示动态链接器在启动可执行文件的当前目录下查找依赖的库;
  • 这意味着,如果你在 /home/user/目录下运行/path/to/app/myapp,那么动态链接器会在/home/user/ 目录下查找依赖的库,而不是在可执行文件所在的 /path/to/app/目录下。

两者的主要区别在于它们所引用的基准点不同:[$ORIGIN/]是相对于可执行文件或库的位置,而[./]是相对于当前工作目录。在实际应用中,选择哪种方式取决于你的具体需求,比如你的应用程序和库是如何被部署和使用的。通常,[$ORIGIN/]更为灵活和可靠,因为它不依赖于用户从哪个日录启动应用程序。

2)确认动态库优先级常用到的几个命令

ldd:ldd 是一个工具,用于显示可执行文件或共享库所依赖的共享库。其使用方法为:ldd xxx 或者 ldd libA.so

readelf:readelf 是一个命令行工具,专门用于查看ELF(Executable and Linkable Format)文件的信息。其用法包括:readelf -d xxx 或者 readelf -d libA.so

ps和lsof:在结合使用ps和lsof时,首先需要利用 ps -ef | grep xxx 命令来查找符合要求的进程ID,然后利用 lsof -p [PID] 命令来查看该进程打开的所有文件,从而确认模块加载的路径。

strace:strace 是一个强大的工具,能够通过打印出可执行程序的加载路径来确定优先级。其使用示例为:strace -e open,openat -o xxx.log ./xxx。该命令将执行名为xxx的可执行程序,并捕获所有open和openat系统调用,将相关信息输出到xxx.log文件中,通过查看日志也能确认模块加载的路径。

二、Linux运行时动态库搜索路径优先级规则探索:

在充分掌握动态库搜索路径优先级的基石概念之后,我们能够初步预估程序执行过程中如何精确定位并加载特定的动态库。然而,在错综复杂的大型项目背景下,时常遭遇同一动态库存在多个版本的场景。为确保程序准确无误地加载所需版本的库,深化对搜索路径优先级原则的理解显得尤为重要。

核心议题探讨

利用ldd命令检视一个可执行文件或动态库所依赖的共享库清单时,此清单是否必然反映程序运行时将加载的动态库?

针对此议题,可进一步细化为两个关键问题进行剖析:

1. 双重依赖下的优先级:若可执行文件与动态库均对某一动态库存在依赖,其搜索与加载的优先级机制如何?
2. 间接依赖的优先级:在多个动态库均依赖于同一动态库,而该依赖链不直接关联至可执行文件时,其搜索与加载的优先级又将如何确定?

规则总结

  • 基础步骤优先:总体上,遵循既定的优先级基础步骤进行搜索。
  • 架构特定路径优先:在每个搜索路径下,结合系统架构、线程局部存储等特性(如glibc-hwcaps/x86-64-v4、tls/x86_64/x86_64等),优先在经此拼接后的路径中查找。
  • 时序原则:依据动态库加载的先后顺序进行。
  • 同级动态库优先原则:是指当动态库依赖某一动态库,而在其加载之前未加载其所依赖的动态库,那么同级别优先级下,会优先按照动态库自身指定的路径查找,然后再去可执行程序指定的路径查找。
  • 路径去重:值得注意的是,对于已确认不存在的路径,系统将避免重复搜索。

实例分析

为增进理解,我们将通过具体实例进行预测与验证,以直观展示上述原则的应用与实践。

此处省略实例分析图片......

通过上图可以看到动态库的加载路径与我们分析的结果一致,详细的搜索步骤可以通过strace命令验证。

总结

本文详尽地梳理了Linux系统中运行时动态库(shared libraries)搜索路径优先级的基本规则,并在此基础上,借助具体实例深入剖析并总结了这些优先级规则的运作机理。此外,本文还针对实际项目中可能遇到的动态库加载问题,提出了具有实用价值的排查参考方案。期望本文能为各位在未来工作中遭遇的动态库依赖问题(诸如版本兼容性问题等)提供有力支持与帮助。

posted @ 2024-10-23 17:03  水云间月掌柜  阅读(228)  评论(0编辑  收藏  举报