修复 macOS 报错 dyld: Library not loaded: @rpath
本页目录
Library not loaded: @rpath/libfoo.dylib 的意思是:dyld 把加载方的每条 LC_RPATH 代入 @rpath,得到的路径里都没有这个文件。修法只有两类:把 dylib 放到 tried: 列出的某个路径上,或者用 install_name_tool -add_rpath 加一条指向 dylib 实际位置的 rpath。
下面的命令都在 macOS 27.0.1(Apple silicon)、Apple clang 21.0.0 上实际跑过,样本是专门为本文编译的两文件小程序。
复现报错
动态库的 install name 是 @rpath/libanswer.dylib,可执行文件的搜索路径是 @executable_path/lib:
mkdir -p sample/lib missing
printf 'int answer(void) { return 42; }\n' > sample/lib/answer.c
printf 'extern int answer(void); int main(void) { return answer() == 42 ? 0 : 1; }\n' > sample/main.c
clang -dynamiclib sample/lib/answer.c \
-Wl,-install_name,@rpath/libanswer.dylib -o sample/lib/libanswer.dylib
clang sample/main.c -Lsample/lib -lanswer \
-Wl,-rpath,@executable_path/lib -o sample/demo
cp sample/demo missing/demo # 字节相同,但旁边没有 lib/
./sample/demo 正常退出(0)。没带动态库的副本在 main 之前就被终止:
$ ./missing/demo
dyld[7720]: Library not loaded: @rpath/libanswer.dylib
Referenced from: <E4183D4B-01D7-36A3-A542-CA09BC4F425A> /…/work/missing/demo
Reason: tried: '/…/work/missing/lib/libanswer.dylib' (no such file), '/…/work/missing/lib/libanswer.dylib' (no such file)
Abort trap: 6
退出码 134(SIGABRT)。最有用的是 tried: 这一行,它就是展开后的全部 rpath。这里同一路径出现了两次,而我们只有一条 LC_RPATH,所以重复并不代表有两条搜索路径。如果 tried: 里有你预期的路径,就去核对那里文件的名字和架构;如果根本没有你想要的目录,说明缺 rpath。
用 otool 看依赖名和 rpath
$ otool -L sample/demo
sample/demo:
@rpath/libanswer.dylib (compatibility version 0.0.0, current version 0.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1359.0.0)
$ otool -l sample/demo | grep -A2 LC_RPATH
cmd LC_RPATH
cmdsize 40
path @executable_path/lib (offset 12)
$ otool -D sample/lib/libanswer.dylib
sample/lib/libanswer.dylib:
@rpath/libanswer.dylib
otool -L 是可执行文件要找的名字,otool -l 是它的搜索路径,otool -D 是 dylib 自己声明的 install name(链接时会被抄进可执行文件)。三个前缀的含义:
@executable_path:进程主程序所在目录,即使请求来自插件或 framework 也是这个目录。@loader_path:包含这条加载命令的 image 所在目录,framework 和插件内部应该用它。@rpath:依次替换为加载方及其上游 image 的每条LC_RPATH。
新版 macOS 上 /usr/lib/libSystem.B.dylib 在磁盘上并不存在,它来自 dyld 共享缓存,ls 找不到是正常的。
三种修法,都实测过
把 dylib 放到 rpath 指向的位置。 mkdir -p missing/lib && cp sample/lib/libanswer.dylib missing/lib/ 之后 ./missing/demo 退出码为 0。对 app 包来说,通常就是在构建阶段把它拷进 Contents/Frameworks。
为 dylib 的实际位置加一条 rpath。 把库改放在 missing/Frameworks/:
$ install_name_tool -add_rpath @executable_path/Frameworks missing/demo
$ otool -l missing/demo | grep -A2 LC_RPATH
cmd LC_RPATH
cmdsize 40
path @executable_path/lib (offset 12)
--
cmd LC_RPATH
cmdsize 40
path @executable_path/Frameworks (offset 12)
$ ./missing/demo; echo $?
0
在 Xcode 或 CMake 项目里,最好在链接阶段设置(LD_RUNPATH_SEARCH_PATHS、-Wl,-rpath,… 或 CMAKE_INSTALL_RPATH),否则下次构建会把手工修改覆盖掉。
直接改依赖路径。 install_name_tool -change @rpath/libanswer.dylib @executable_path/sample/lib/libanswer.dylib fixed3 之后,otool -L 显示新路径,程序退出码为 0。想改 dylib 对外声明的名字(影响以后的链接),对 dylib 用 install_name_tool -id。
修改后我们的样本 codesign -dv 仍显示 Signature=adhoc,可以运行。但 Developer ID 签名或已公证的 app 不同:要在签名前修改,或者改完重新签名(codesign --force --sign …);开启 library validation 时,加载的每个 dylib 都必须是同一团队或 Apple 签名的。
静态看不出来时
DYLD_PRINT_RPATHS=1、DYLD_PRINT_LIBRARIES=1 能显示 dyld 运行时的实际行为,但 SIP 会对受保护的程序清掉 DYLD_* 变量,开启 hardened runtime 的应用除非有对应 entitlement 也会忽略它们。
如果 app 包里嵌套了很多 framework,逐个读 otool -l 很慢。第三方开源工具 REA 有一个 trace-dylib-resolution 命令,不启动程序就能静态构建整张依赖图。在上面的样本上用 REA 6.8.0(要求 Node 22.23.4+、24.22.1+ 或 26.12.1+)执行 rea trace-dylib-resolution sample/demo --format json,@rpath/libanswer.dylib 这条边经候选 lib/libanswer.dylib 显示为 resolved;换成 missing/demo 时候选为 absent,并给出 required-load-unresolved。/usr/lib/libSystem.B.dylib 显示为 outside-target / undetermined,意思是“未评估”,不是“缺失”。它不检查代码签名和兼容版本,所以 resolved 也不等于能启动。
要先在 .app 里找到可执行文件,参见 macOS 应用结构;如果报错说的是架构不对而不是找不到文件,用通用二进制指南检查切片。