@rpath 报错

Posted by CoderLeonidas on March 20, 2020

@rpath 报错

简而言之:发生了动态链接。

动态链接:是指当代码的一部分跨不同的文件(称为库)传播,并且库的二进制内容在运行时加载时,就会发生这种操作。

动态链接器(dynamic linker,一个系统工具)在动态库中找到一个符号(例如,函数)。接下来,将代码加载到内存中,并使用符号分配内存地址。通过这种方式,运行程序可以找到存储在外部库(共享库)中的符号的实现。动作发生在程序执行过程的开始,在应用程序代码开始执行之前(如果延迟加载符号,则可能发生在后面)

这意味着当我们开发一个应用程序时,我们可能会错过它,直到程序运行。

是什么?

如果没有为可执行文件正确地设置链接,这是我们将在开发过程的最后时刻注意到的警告:

1
2
3
dyld: Library not loaded: @rpath/Allthethings.framework/Allthethings
  Referenced from: /private/var/mobile/Containers/Bundle/Application/0F2C2461-A68B-4ABA-A604-B88E6E9D1BB1/App.app/App
  Reason: image not found

这个报错的意思是说:

  • 发生在从App.app的 bundle中执行一个App二进制文件的过程中
  • dyld是macOS系统中的动态链接器工具
  • 无法从Allthethings.framework的bundle中加载Allthethings二进制文件
  • 在搜索@rpath/Allthethings.framework/ Allthethings二进制文件时找不到镜像(image not found)

为什么?

简而言之:因为文件不在那里,所以找不到。

那里到底在哪里? 路径为@rpath/Allthethings.framework/ Allthethings。 该值是在构建应用程序时设置的。它被传递给链接器(而不是编译器)。

@rpath代表运行路径搜索路径 (Runpath Search Path).

  • 在xcode中,它是用LD_RUNPATH_SEARCH_PATH设置的
  • 在命令行工具ld中,它是在链接时期带着-rpath参数设置的

因此,这是链接器的搜索路径。运行时搜索路径指示动态链接器按顺序搜索路径列表,以定位动态库。

参数的值可以是目录的绝对路径(或多个路径),例如:/usr/private/lib@executable_path/Frameworks

对于相对路径,我们可以使用两个替换符号中的一个:

  • @loader_path - 解析到目录的路径,该目录包含了包含load命令的Mach-O二进制文件。因此,在每个二进制文件中,它都被解析为不同的路径,也就是说,它是执行加载给定库的库路径。不一定是相同的库。 想象一下,库libc.dylib加载了另一个库libqq.dylib。这种情况下,loader_path将指向libc.dylib所在的位置(因为这是调用者被定位)。

  • @executable_path - 解析为可执行文件的绝对路径,例如:/private/var/mobile/Containers/Bundle/Application/0F2C2461-A68B-4ABA-A604-B88E6E9D1BB1/App.app/

在运行时,dyld在搜索动态库时会使用那个以@rpath开头的加载路径(runpath)。

那错误信息中的@rpath/Allthethings.framework/Allthething该怎么解决?我们现在还不知道。为了解决这个,我们需要了解二进制文件的可能存在的rpath。

在本例中,要研究的二进制文件在/private/var/mobile/Containers/Bundle/Application/0F2C2461-A68B-4ABA-A604-B88E6E9D1BB1/App.app/App

Runpath搜索路径会存储为二进制文件的一部分,作为LC_RPATH命令。

要读取该部分的值,可以使用命令行工具otool -l 0F2C2461-A68B-4ABA-A604-B88E6E9D1BB1/App.app/App 然后在输出中搜索 LC_RPATH

1
2
3
4
Load command 48
          cmd LC_RPATH
      cmdsize 48
         path @executable_path/../Frameworks (offset 12)

有一个或多个这样的条目。

现在,我们可以用找到的路径替换@rpath,并验证文件丢失的原因。

怎么做?

要解决最初的问题,我们需要以下二选一:

  • 将二进制文件复制到指定目录
  • 通过修改LD_RUNPATH_SEARCH_PATH(或-rpath)的值来添加一个其他的运行路径搜索路径

我已经说过@rpath是二进制文件的一部分,它是在编译过程(链接阶段)中使用给定的参数值设置的。然而,如果我们需要手动修改@rpath,例如,作为安装阶段的一部分,有一个应用程序: install_name_tool

install_name_tool

install_name_tool可以更改动态共享库、安装名称和操作运行路径。

  • 添加新路径:

install_name_tool -add_rpath @executable_path/../private/libs File

  • 删除已添加的路径(我们只能删除用-add_rpath添加的路径):

install_name_tool -delete_rpath @ executable_path/../private/libs File

  • 修改已存在的路径:

install_name_tool -rpath @executable_path/../Frameworks @executable_path/../private/libs File

内嵌的二进制文件

我们刚刚掌握了动态链接解决问题的方法。Xcode中嵌入的二进制代码部分的含义很清楚

Xcode将文件从“内嵌的二进制文件”部分复制到LD_RUNPATH搜索路径所指向的位置。

使用环境变量来控制链接器

dyld的行为可能由环境变量控制,但是如果启用了系统完整性保护(SIP, System Integrity Protection),那么在执行受系统完整性保护保护的二进制文件时,环境变量将被忽略——这是大多数情况下的情况。

PS: 在Linux上不一样,因为Linux使用了不同的loader。

参考文章

@rpath what?

Error: dlopen() Library not loaded Reason: image not found**