注:本文为 “Linux | 程序 / 进程调用库依赖查看” 方法相关合辑。
略作重排,如有内容异常,请看原文。
Linux 库依赖检查与共享库分析
在 Linux 系统中,程序通常依赖外部库才能正确运行。这些库在运行时被动态加载,使得程序可以复用通用代码并减小体积。当程序缺少所需的库或库版本不兼容时,就会出现“无法打开共享对象文件”之类的错误,导致程序无法启动。
例如,执行sudo dpkg命令时,如果系统缺少libpthread.so.0库,就会报告如下错误:
$sudodpkg sudo: errorwhileloading shared libraries: libpthread.so.0: cannotopenshared object file: No suchfileor directory为了解决此类问题,Linux 提供了多种工具来检查程序的库依赖关系。这些工具可以帮助你定位缺失的库,以便进行安装或更新。
工具分类说明
Linux 环境下获取 ELF 可执行文件的共享库依赖,分为两类方案:
- 运行时解析:
ldd,调用动态链接器,可同时读取直接依赖与传递依赖;存在执行风险,可捕获部分动态加载行为,但无法捕获dlopen在程序运行过程中才加载的库。 - 文件静态解析:
objdump、readelf,仅读取 ELF 文件内预写入的动态段,不执行目标程序,安全性更高;仅读取编译阶段写入的直接依赖,不能自动递归展开传递依赖,同样无法捕获运行期dlopen加载库。
限制:所有静态解析工具均无法获取程序运行时通过
dlopen()动态打开的共享库,该类库不会记录在 ELF 文件动态段中。
检查可执行文件的依赖
以下方法用于分析静态的二进制文件,查看其编译时记录的依赖信息。
ldd命令
ldd命令通过调用动态链接器来显示一个程序所需的所有共享库。这是最常用的方法。
$ ldd /usr/bin/bash linux-vdso.so.1(0x00007ffdd2749000)libtinfo.so.6=>/lib/x86_64-linux-gnu/libtinfo.so.6(0x00007fcecb9b6100)libdl.so.2=>/lib/x86_64-linux-gnu/libdl.so.2(0x00007fcecb9b0000)libc.so.6=>/lib/x86_64-linux-gnu/libc.so.6(0x00007fcecb7c5000)/lib64/ld-linux-x86-64.so.2(0x00007fcecbb21000)输出中的箭头=>指向了系统中库文件的实际路径。如果某个依赖缺失,则会显示not found。
安全警告:不建议对任何不可信的第三方程序运行ldd。因为某些版本的ldd可能会直接执行该程序来确定其依赖关系,这可能带来安全风险。
ldd命令详细模式ldd --verbose
通过--verbose选项,可以查看更详细的信息,包括符号版本要求。这对于排查“在A机器上能运行,在B机器上不能运行”的问题非常有帮助。
$ ldd--verbose/usr/bin/bash linux-vdso.so.1(0x00007ffce299c000)libtinfo.so.6=>/lib/x86_64-linux-gnu/libtinfo.so.6(0x00007f6fb24dd000)# ... 其他库信息 ...Version information: /usr/bin/bash: libdl.so.2(GLIBC_2.2.5)=>/lib/x86_64-linux-gnu/libdl.so.2 libc.so.6(GLIBC_2.11)=>/lib/x86_64-linux-gnu/libc.so.6# ... 其他版本信息 ...objdump或readelf命令
对于来自不可信来源的二进制文件,使用objdump或readelf是更安全的选择。它们直接读取 ELF 文件的元数据,而不会执行任何代码。
objdump: 通过-p选项查看程序头,并筛选出NEEDED的库。$ objdump-p/usr/bin/git|grepNEEDED NEEDED libpcre.so.3 NEEDED libz.so.1 NEEDED libresolv.so.2 NEEDED libpthread.so.0 NEEDED libc.so.6readelf: 通过--dynamic选项查看动态段信息,同样筛选NEEDED条目。$ readelf--dynamic/usr/bin/bash|grepNEEDED 0x0000000000000001(NEEDED)Shared library:[libtinfo.so.6]0x0000000000000001(NEEDED)Shared library:[libdl.so.2]0x0000000000000001(NEEDED)Shared library:[libc.so.6]
ld.so(动态链接器直接调用)
动态链接器本身可以模拟加载,效果等价于ldd底层逻辑。
/lib64/ld-linux-x86-64.so.2 ./target_bin# x86_64架构,直接调用动态链接器打印依赖检查运行中进程的依赖
以下方法用于分析一个已经启动的进程,查看其实际加载到内存中的库。
pldd命令
pldd命令可以显示一个正在运行的进程所加载的所有共享对象。执行此命令需要root权限。
$sudopldd11491149: /usr/sbin/sshd linux-vdso.so.1 /lib/x86_64-linux-gnu/libwrap.so.0 /lib/x86_64-linux-gnu/libpam.so.0 /lib/x86_64-linux-gnu/libselinux.so.1# ... 其他库路径 ...pmap命令
pmap命令用于报告一个进程的内存映射。通过筛选输出,可以列出该进程使用的所有共享库。
$sudopmap14601460: /usr/sbin/sshd-D00007f1b69e63000 44K r-x-- libnss_files-2.19.so 00007f1b69e6e000 2044K ----- libnss_files-2.19.so# ... 其他内存映射 ...00007f1b6a485000 92K r-x-- libresolv-2.19.solsof命令
列出进程打开的文件,通过筛选内存映射(mem)类型的文件来查看共享库。
$lsof-p$(pgrepbash|head-n1)|grepmembash4470user mem REG8,151672404577/usr/lib/x86_64-linux-gnu/libnss_files-2.29.sobash4470user mem REG8,12000480403822/usr/lib/x86_64-linux-gnu/libc-2.29.so# ... 其他库信息 ...适用场景:程序已经启动,需要查看当前实际加载的库,可捕获运行期dlopen加载的库;无法用于未启动的可执行文件。
/proc文件系统
可以直接读取进程的内存映射文件来获取库信息。
$awk'/\.so/{print $6}'/proc/$(pgrepbash|head-n1)/maps|sort-u/usr/lib/x86_64-linux-gnu/ld-2.29.so /usr/lib/x86_64-linux-gnu/libc-2.29.so /usr/lib/x86_64-linux-gnu/libdl-2.29.so /usr/lib/x86_64-linux-gnu/libnss_files-2.29.so /usr/lib/x86_64-linux-gnu/libtinfo.so.6.1扫描目录下全部可执行文件并统计共享库引用频次
基础脚本:扫描指定目录,统计共享库引用频次
以扫描/bin目录下全部可执行文件为例:
find/bin-typef-perm/a+x-execldd{}\;2>/dev/null\|grep'so'\|sed-e's/^\t//'\|sed-e's/.*=> //'\|sed-e's/ (0x.*//'\|sort\|uniq-c\|sort-nrfind /bin -type f -perm /a+x:在/bin目录检索普通文件(-type f),且对所有用户开放可执行权限(-perm /a+x)。如需扫描整个系统,可将/bin替换为/,该操作需要root权限,执行耗时更长。-exec ldd {} \;:对检索到的每一个可执行文件执行ldd。ldd打印该程序依赖的共享库列表。2>/dev/null:将错误信息(静态链接文件等产生的报错)重定向至空设备,清理输出内容。grep 'so':过滤输出行,仅保留包含so(共享对象 Shared Object)的行,剔除静态链接与无关信息。sed数据清洗:s/^\t//:移除行首制表符s/.*=> //:删除库路径前的文本,仅保留库文件路径,例如/lib64/libc.so.6s/ (0x.*//:剔除内存地址片段,例如(0x00007f...)
sort | uniq -c | sort -nr:sort:对库路径排序,为去重统计做准备uniq -c:统计重复行数量,对应单个库被程序引用的次数sort -nr:按数字(-n)逆序(-r)排列,高引用次数的库置于输出上方
输出样例(仅/bin目录)
1 /lib64/libexpat.so.0 1 /lib64/libgcc_s.so.1 1 /lib64/libnsl.so.1 1 /lib64/libpcre.so.0 1 /lib64/libproc-3.2.7.so 1 /usr/lib64/libbeecrypt.so.6 1 /usr/lib64/libbz2.so.1 1 /usr/lib64/libelf.so.1 1 /usr/lib64/libpopt.so.0 1 /usr/lib64/librpm-4.4.so 1 /usr/lib64/librpmdb-4.4.so 1 /usr/lib64/librpmio-4.4.so 1 /usr/lib64/libsqlite3.so.0 1 /usr/lib64/libstdc++.so.6 1 /usr/lib64/libz.so.1 2 /lib64/libasound.so.2 2 /lib64/libblkid.so.1 2 /lib64/libdevmapper.so.1.02 2 /lib64/libpam_misc.so.0 2 /lib64/libpam.so.0 2 /lib64/libuuid.so.1 3 /lib64/libaudit.so.0 3 /lib64/libcrypt.so.1 3 /lib64/libdbus-1.so.3 4 /lib64/libresolv.so.2 4 /lib64/libtermcap.so.2 5 /lib64/libacl.so.1 5 /lib64/libattr.so.1 5 /lib64/libcap.so.1 6 /lib64/librt.so.1 7 /lib64/libm.so.6 9 /lib64/libpthread.so.0 13 /lib64/libselinux.so.1 13 /lib64/libsepol.so.1 22 /lib64/libdl.so.2 83 /lib64/ld-linux-x86-64.so.2 83 /lib64/libc.so.6反向检索脚本:查找指定目录下依赖某库的全部可执行文件
以检索依赖libc.so.6的程序为例:
\# 遍历 /bin 目录下所有用户可执行的普通文件forfilein$(find/bin-typef-perm/a+x);do\# 检查文件是否依赖 libc.so.6(屏蔽静态链接文件的 stderr 报错)ifldd"$file"2>/dev/null|grep-q"libc.so.6";thenecho"$file"fidone安全注意事项
ldd内部依靠 Linux 动态链接器,在处理特制恶意 ELF 文件时存在执行代码风险。不可信二进制文件禁止使用ldd,优先使用readelf或objdump静态解析。
ldd通过设置特殊环境变量运行目标可执行文件,Linux 动态链接器识别该标记后仅打印库信息,而不运行程序本体。ldd在多数系统上是 bash 脚本。若可执行文件为静态链接、使用自定义系统调用并指定其他加载器,则该文件可执行任意操作。不要对不可信二进制文件使用ldd。
针对不受信文件,使用以下命令读取依赖更为安全:
objdump-p<file>|grepNEEDED# 或readelf-d<file>|grepNEEDED各工具对比简表
| 工具 | 是否执行程序 | 能否获取传递依赖 | 能否捕获 dlopen |
|---|---|---|---|
| ldd | 是 | 是 | 仅程序启动阶段 |
| readelf | 否 | 否 | 否 |
| objdump | 否 | 否 | 否 |
| lsof | 否(附着到已运行进程) | 是(进程当前已加载) | 是(进程运行后dlopen加载) |
补充:dlopen动态加载共享库的统计与排查方案
dlopen动态加载的共享库无法被ldd捕获,这类库不会计入统计结果。
dlopen的功能定位为运行时动态加载库并返回句柄,并非统计工具。若仅为了获取已加载库的列表而反复调用dlopen,会引入性能开销与资源泄漏风险。针对不同的排查需求,应采用系统提供的标准机制。
一、 不建议使用dlopen进行统计的原因
- 性能开销:
dlopen涉及文件读取、内存映射、符号解析与重定位。若动态库数量较多(如 300~400 个),会显著拖慢程序启动或运行速度。 - 引用计数机制:每次
dlopen会增加库的引用计数,需配合dlclose释放。若未正确释放会导致资源泄漏;若在释放后仍有指针在使用,则会导致程序崩溃(SIGSEGV)。 - 重复加载问题:动态链接器会对已加载的库做缓存。多次
dlopen同一库通常只增加引用计数,并不会重新加载,这使得“通过加载次数统计”的逻辑变得复杂且不准确。
二、 正确的统计与排查方案
根据需求场景,推荐以下标准方案:
运行时统计(查看当前进程已加载的库)
若需查看程序运行中实际加载了哪些库(含dlopen加载的库),无需调用dlopen,可直接使用系统工具:pldd <PID>:专门列出进程加载的动态共享对象,包含dlopen加载的库。cat /proc/<PID>/maps | grep '\.so':查看进程内存映射,过滤出共享库路径。lsof -p <PID> | grep '\.so':列出进程打开的.so文件。pmap <PID> | grep '\.so':查看进程内存映射及共享库依赖。
启动时跟踪(查看加载过程与耗时)
若需跟踪程序启动过程中哪些库被加载、耗时多久:LD_DEBUG=libs,statistics ./your_app:启用动态链接器调试,输出库加载路径、符号解析及重定位统计信息。strace -e open,mmap ./your_app:跟踪系统调用,观察.so文件的打开与内存映射过程。
代码内统计(程序内部获取已加载库列表)
若需在代码中获取当前进程已加载的库列表,不应循环调用dlopen,而应使用:dl_iterate_phdr():glibc提供的 API,可遍历当前进程已加载的所有共享对象,并获取其路径、地址、大小等信息。- 读取
/proc/self/maps:在 Linux 下直接读取该文件并解析,即可获取当前进程的内存映射信息。
性能测试场景(统计加载耗时)
若确实需要测量dlopen加载特定库的耗时(如性能优化场景),可编写专门的测试代码,但需注意:- 使用
clock()或gettimeofday()计时。 - 每次加载后调用
dlclose释放,确保下次是全新加载。 - 仅用于测试,不可用于生产逻辑。
- 使用
示例代码:
#include<stdio.h>#include<dlfcn.h>#include<time.h>intmain(){constchar*lib_path="/path/to/libtest.so";clock_tstart=clock();void*handle=dlopen(lib_path,RTLD_LAZY);clock_tend=clock();if(handle){printf("加载耗时: %.6f 秒\n",(double)(end-start)/CLOCKS_PER_SEC);dlclose(handle);// 务必释放}else{fprintf(stderr,"加载失败: %s\n",dlerror());}return0;}编译时需链接-ldl。
三、 方案总结
| 需求场景 | 推荐方案 | 是否调用dlopen |
|---|---|---|
| 查看运行中进程加载了哪些库 | pldd、/proc/PID/maps、lsof | 否 |
| 跟踪库加载过程与耗时 | LD_DEBUG、strace | 否 |
| 代码内获取已加载库列表 | dl_iterate_phdr()、读取/proc/self/maps | 否 |
| 性能测试:测量加载耗时 | 专用测试代码(加载后立即关闭) | 仅测试用 |
| 生产环境业务逻辑 | 正常dlopen/dlclose管理资源 | 是 |
dlopen是加载工具,不是查询工具。统计和查询应使用系统提供的自省机制(如pldd、dl_iterate_phdr、/proc文件系统),避免在生产代码中滥用dlopen做统计。
Reference
- How to check what libraries are used by a program or process on Linux
https://www.xmodulo.com/check-library-dependency-program-process-linux.html - How to show shared library dependencies in Linux
https://www.simplified.guide/linux/show-shared-library-dependency - How to show all shared libraries used by executables in Linux? - Stack Overflow
https://stackoverflow.com/questions/50159/how-to-show-all-shared-libraries-used-by-executables-in-linux