登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  linux

Linux ldd 怎样找出缺失的共享库依赖

来源:17golang原创

时间:2026-11-12 10:23:35 157浏览 收藏

我第一次遇到“程序明明存在,却提示找不到某个 .so 文件”时,先看了应用自己的日志,后来才发现真正有用的线索在依赖解析结果里。Linux 的 ldd 会把目标程序需要的共享对象以及解析到的路径列出来;其中出现 not found,通常就是当前环境没有为该依赖找到可用文件。

官方手册地址:https://man7.org/linux/man-pages/man1/ldd.1.html

这篇只解决一个问题:怎样用 ldd 找出缺失依赖,并判断是库文件不在搜索路径、重定位失败,还是目标文件本身不适合直接交给 ldd。涉及不可信二进制时,会切换到静态的 objdump -p 读取。

先读懂 ldd 输出中的三种状态

我现在排查这类问题,会先把输出分成三类,而不是一看到某一行就开始安装软件包:

  • 已解析路径:类似 libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6,说明动态链接器为这个名字找到了文件。
  • not found:依赖名字被目标文件声明了,但按当前搜索规则没有找到对应对象。
  • 特殊对象:linux-vdso.so.1、动态链接器本身等并不等同于普通磁盘库,不能简单按普通 .so 文件处理。
ELF目标、ldd、动态链接器与直接间接依赖及not found结果的静态关系说明图
图1:ldd 依赖解析关系说明图;看“解析结果”分组即可区分已解析路径和 not found,这是一张静态说明图,不是终端截图。

官方 ldd(1) 手册说明,ldd 展示的是程序或共享对象所需的共享库;常见实现会让动态链接器在跟踪模式下检查这些依赖。因此它呈现的是“当前环境如何解析”,不只是目标文件里写着哪些名字。

用 not found 锁定真正缺失的库

先保存目标路径,再只筛选缺失行。下面的命令没有替读者决定安装哪个包,作用只是把证据从长输出中提出来:

target="/opt/example/bin/app"

# 只筛选动态链接器明确标记为未解析的依赖。
ldd "$target" | grep -F 'not found'

如果没有任何输出,并不等于程序一定能启动。它只说明 ldd 没有在这次检查中报告未找到的共享对象。还要留意:

  • 目标可能不是当前架构可运行的 ELF 文件;
  • 依赖可能存在,但符号版本或重定位阶段仍不匹配;
  • 程序在启动后通过 dlopen 动态加载的库,不一定出现在最初的依赖清单里。

如果输出是 libexample.so => not found,先记下库的名字和目标文件,不要直接把一个同名文件复制到系统目录。下一步应解释动态链接器会去哪里找。

结合搜索路径解释为什么会找不到

动态链接器的规则决定了同一个程序在不同机器上可能得到不同路径。对不含斜杠的依赖名,官方 ld.so(8) 文档列出的关键来源包括 DT_RPATH、LD_LIBRARY_PATH、DT_RUNPATH、/etc/ld.so.cache 以及默认目录;具体优先级和安全执行模式也会影响结果。

target="/opt/example/bin/app"

# 查看目标的动态段,确认是否写入了 RPATH、RUNPATH 或 NEEDED。
# 这里看的是文件声明,不把它当成完整的运行时依赖树。
readelf -d "$target" | grep -E 'NEEDED|RPATH|RUNPATH'

# 临时显示当前 shell 是否带入了库搜索路径,避免把环境差异误判成缺库。
printf 'LD_LIBRARY_PATH=%s\n' "${LD_LIBRARY_PATH-}"

我会把证据分成两层:NEEDED 说明目标声明了什么,ldd 说明当前环境把这些声明解析成了什么。若只有某个部署用户报错,优先比较该用户的环境变量、服务启动方式和安全执行条件。

特别要注意空的 LD_LIBRARY_PATH 项可能代表当前工作目录,路径顺序也会让同名库被不同目录中的文件满足。生产修复应回到构建时的 RUNPATH、系统库缓存或明确的部署目录,而不是把临时环境变量永久写进服务。

区分缺库与重定位错误

有时依赖文件能被找到,但加载其中的数据或函数仍然失败。这时可以用 ldd 的重定位检查选项把问题再缩小一层:

target="/opt/example/bin/app"

# 检查数据重定位,适合确认缺失对象或数据符号。
ldd -d "$target"

# 同时检查函数重定位;输出可能比普通 ldd 更长。
ldd -r "$target"

-d 会执行数据重定位检查,-r 会连函数重定位一起检查。我的判断习惯是:

现象优先怀疑下一步
=> not found库文件或搜索路径缺失核对 RUNPATH、缓存和部署目录
库路径已出现,但重定位报错符号、ABI 或版本不匹配比较符号版本与构建环境
普通 ldd 正常,启动后仍报错运行期动态加载或环境差异再看程序的加载逻辑与服务环境

不可信文件不要直接交给 ldd

这里是我后来最在意的一条边界:官方手册明确提醒,不要对不可信的可执行文件直接运行 ldd。在某些情况下,旧版本或特殊 ELF 解释器可能触发直接执行行为。目标文件来源不明时,先用静态方式读取它声明的直接依赖:

不可信ELF在ldd动态观察与objdump静态读取之间的安全边界说明图
图2:ldd 与 objdump -p 的安全边界说明图;它表达“完整依赖树”和“直接 NEEDED”的差别,不是截图或实际执行结果。
target="/path/to/untrusted-file"

# 对来源不明的 ELF,优先静态读取直接依赖声明。
# NEEDED 只代表目标文件直接声明的共享对象,不等于完整依赖树。
objdump -p "$target" | grep -F 'NEEDED'

这个替代方案的取舍很明确:objdump -p 更适合先做静态筛查,但它只显示直接依赖;ldd 能展开更完整的依赖树,却要求你信任目标文件和当前工具链的行为。两者不能当成完全等价的命令。

我的可复用排障清单

  1. 确认目标是预期架构的 ELF 文件,并记录绝对路径。
  2. 对可信目标运行 ldd target,先找 not found。
  3. 把 NEEDED、RPATH/RUNPATH 和当前库路径分开记录。
  4. 若路径已解析但仍失败,再用 ldd -d 或 ldd -r 看重定位。
  5. 目标不可信时停止使用 ldd,改用 objdump -p | grep NEEDED。
  6. 修复后重新检查同一个目标和同一个服务启动环境,确认不是只修好了交互式 shell。

对我来说,ldd 最有价值的地方不是“列出几个库”,而是把启动错误拆成了可观察的依赖解析证据。只要把 not found、搜索路径、重定位和文件信任边界分开,排查通常就不会退化成盲目复制 .so 文件。

相关问题

ldd 没有 not found,为什么程序仍然启动失败?

可能是符号版本、重定位、运行后 dlopen,或服务启动环境和当前 shell 不同。继续看 ldd -r、动态段和实际服务环境。

objdump -p 能完全替代 ldd 吗?

不能。它更适合不可信文件的静态筛查,只展示直接 NEEDED;ldd 侧重当前环境下的完整解析树。

看到 not found 可以直接设置 LD_LIBRARY_PATH 吗?

可以作为短时诊断手段,但生产修复应明确库的部署位置和构建时 RUNPATH,并评估路径顺序和安全执行模式,避免环境变量掩盖真正的打包问题。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>