5

我编写了 C 程序来计算波函数的时间步长迭代,以求解时间相关的薛定谔方程。在某些步骤中,我需要进行正向和反向快速傅里叶变换 (FFT),为此我使用了库Kissfft。(https://github.com/mborgerding/kissfft

我的程序结构有点像这样:

  • TDSE(工作目录)
    • 模块
    • 包括
    • 脚本
    • 测试
      • inttest_analytical.c
    • 亲吻
      • libkissfft-double.dylib

现在,当我编译inttest_analytical.c它时,它可以工作了。但是之后尝试运行可执行文件时,出现以下错误:

(base) user TDSE % ./inttest_analytical
dyld: Library not loaded: libkissfft-double.dylib
  Referenced from: /Users/user/Documents/Uni/HU Berlin/Computational Physics 2/Project 3 - Time-dependet Schroedinger Equation/TDSE/./inttest_analytical
  Reason: image not found
zsh: abort      ./inttest_analytical

跑步后otool -L ./inttest_analytical我得到

/inttest_analytical:
        libkissfft-double.dylib (compatibility version 0.0.0, current version 0.0.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1)

据我在谷歌搜索的其他问题中阅读有关此内容的了解,libkissfft-double.dylib它是一个静态库,但我无法告诉 gcc 在哪里可以找到该库。它给出的路径(由编译器或链接器?)是工作目录 TDSE 而不是 TDSE/kissfft 对于编译我运行:

gcc -g -Wall -fPIC -I include -I kissff ./modules/wavefunction.c ./modules/integrator.c ./modules/geometry.c ./modules/linearalgebra.c ./modules/assert.c ./modules/hamiltonian.c ./modules/conjugategradient.c ./test/inttest_analytical.c -Lkissfft -lkissfft-double -o inttest_analytical

所以我想我使用的标志-L-l错误?

谢谢你的帮助。

4

1 回答 1

11

我想我会用这个问题为所有“找不到图像”问题写一个规范的答案。

1.问题

让我们从一个包含主二进制文件和库的最小设置开始,如下所示:

main.c

#include <stdio.h>

extern int f(void);

int main(void)
{
    printf("%u\n", f());
    return 0;
}

xyz.c

int f(void)
{
    return 42;
}

命令行:

% cc -Wall -O3 -shared -o libxyz.dylib xyz.c
% cc -Wall -O3 -o main main.c -L. -lxyz

这行得通。您可以运行./main,它会打印42.
但是,如果您现在创建一个文件夹lib,请移到libxyz.dylib那里并重新编译main,如下所示:

% cc -Wall -O3 -o main main.c -Llib -lxyz

然后编译仍然会成功,但是启动它不会:

% ./main
dyld: Library not loaded: libxyz.dylib
  Referenced from: /private/tmp/./main
  Reason: image not found

但是如果你直接返回并重新编译libxyz.dylib到该lib文件夹​​,然后重新构建main,如下所示:

% cc -Wall -O3 -shared -o lib/libxyz.dylib xyz.c
% cc -Wall -O3 -o main main.c -Llib -lxyz

然后它将再次工作。但只是为了说明,如果你libxyz.dylib再次移动,这是你得到的错误:

% ./main
dyld: Library not loaded: lib/libxyz.dylib
  Referenced from: /private/tmp/./main
  Reason: image not found

更糟的是,您甚至可以在不移动库的情况下产生此错误:只需cd lib调用../main.

还要注意与之前的区别,libxyz.dyliblib/libxyz.dylib. 这将我们带到了问题的核心。

2.原因

在 macOS 上,每个共享库都有一个“安装名称”,即预期在运行时可以找到的路径。这条路径可以采取三种形式:

  • 绝对的,例如/usr/lib/libxyz.dylib
  • 相对的,例如lib/libxyz.dylib
  • 魔术,例如@rpath/libxyz.dylib

该路径通过LC_ID_DYLIBload 命令嵌入到库的 Mach-O 标头中。可以这样查看otool

% otool -l /tmp/lib/libxyz.dylib | fgrep -B1 -A5 LC_ID_DYLIB
Load command 2
          cmd LC_ID_DYLIB
      cmdsize 48
         name lib/libxyz.dylib (offset 24)
   time stamp 1 Thu Jan  1 01:00:01 1970
      current version 0.0.0
compatibility version 0.0.0

此加载命令由链接器创建,其手册页 ( man ld) 告诉我们以下内容:

-install_name name
        Sets an internal "install path" (LC_ID_DYLIB) in a dynamic library.
        Any clients linked against the library will record that path as the
        way dyld should locate this library. If this option is not specified,
        then the -o path will be used. This option is also called
        -dylib_install_name for compatibility.

这告诉我们安装名称如何工作的三个步骤:

  1. 链接器在构建库时嵌入名称。
  2. 链接器在构建时将名称复制到链接到该库的二进制文件中。
  3. Dyld 使用该名称来尝试加载库。

如果库被移动,或者甚至没有使用与它们最终路径匹配的安装名称进行编译,这显然会导致问题。

3.解决方案

解决方案是更改安装名称路径。地点和方式取决于您的设置。您可以通过两种方式更改它:

  1. 使用正确的安装名称(-Wl,-install_name,...或直接-o ...)重新编译库,然后重新编译主二进制文件以链接它。
  2. 使用install_name_tool. 这涉及更多一点。

无论哪种情况,您都需要决定要使用哪种形式的安装名称:

  • 绝对。
    这建议用于全局路径中的库,由所有用户共享。您可以使用它来指向您的用户目录,但这有点难看,因为您无法移动二进制文件或将它们分发给其他人。

  • 相对的。
    相对于您的工作目录意味着这完全不可靠。永远不要使用这个。只是不要。

  • 魔法。
    除了绝对路径和相对路径之外,还有三个“特殊”标记:

    • @executable_path是进程的主要二进制文件的运行时目录。这是最简单的形式,但仅当您的库仅在单个主二进制文件中使用时才有效。
    • @loader_path是取决于库的二进制文件的运行时目录。我建议不要使用它,因为如果您在不同的文件夹中有两个二进制文件想要链接到同一个库,它会中断。
    • @rpath是从LC_RPATH加载命令组装的运行时目录列表。这有点复杂,但它是最灵活的解决方案,因为它本身可以包含@executable_pathand @loader_path

    使用这些允许您构建可以自由移动的二进制文件,只要它们都保留它们的相对位置。
    有关它们的完整描述,请参阅man dyld

有了这个,让我们看看实现可能的解决方案。我们有:

  • cc -Wl,-install_name,...在编译时指定安装名称。
  • install_name_tool -id ...更改嵌入库中的路径。
  • install_name_tool -change old new更改嵌入在针对库的二进制链接中的路径。

3.1 绝对路径

如果您可以重新编译库和主二进制文件:

% cc -Wall -O3 -shared -o /tmp/lib/libxyz.dylib xyz.c
% cc -Wall -O3 -o main main.c -L/tmp/lib -lxyz

如果您只能重新编译主二进制文件:

% install_name_tool -id '/tmp/lib/libxyz.dylib' /tmp/lib/libxyz.dylib
% cc -Wall -O3 -o main main.c -L/tmp/lib -lxyz

如果您无法重新编译:

% install_name_tool -id '/tmp/lib/libxyz.dylib' /tmp/lib/libxyz.dylib
% install_name_tool -change 'libxyz.dylib' '/tmp/lib/libxyz.dylib' main

3.2@executable_path

如果您可以重新编译库和主二进制文件:

% cc -Wall -O3 -shared -o lib/libxyz.dylib xyz.c -Wl,-install_name,'@executable_path/lib/libxyz.dylib'
% cc -Wall -O3 -o main main.c -Llib -lxyz

如果您只能重新编译主二进制文件:

% install_name_tool -id '@executable_path/lib/libxyz.dylib' lib/libxyz.dylib
% cc -Wall -O3 -o main main.c -Llib -lxyz

如果您无法重新编译:

% install_name_tool -id '@executable_path/lib/libxyz.dylib' lib/libxyz.dylib
% install_name_tool -change 'libxyz.dylib' '@executable_path/lib/libxyz.dylib' main

3.3@rpath

Rpath 需要手动添加运行时路径,这需要一些规划。假设您有以下文件层次结构:

  • a
  • bin/
    • b
  • libx.dylib
  • lib/
    • liby.dylib
    • libz.dylib

aandb是链接到 and 的二进制文件,而libxandliby又链接到libz. 对于 的安装名称libz,既不能使用@executable_path(因为ab在不同的目录中)也不能使用@loader_path(因为libxliby在不同的目录中)。但是你可以在里面使用它们中的任何一个@rpath,这是你必须做出的决定:

  • 您可以嵌入@executable_pathina@executable_path/..in的 rpath b。然后您可以使用@rpath从所有二进制文件中引用项目根目录。libz安装名称为@rpath/lib/libz.dylib.
  • 或者您可以嵌入@loader_path/libinlibx@loader_pathin的 rpath liby。然后您可以使用@rpath来引用包含每个二进制文件的目录。libz安装名称为@rpath/libz.dylib.

我通常发现前者更容易处理,但如果您有大量二进制文件分散在许多目录和几个库中,则后者可能更可取。

要实际将 rpath 添加到二进制文件,您可以使用:

  • cc -Wl,-rpath,...在编译时。
  • install_name_tool -add_rpath ...然后。

因此,如果您可以重新编译库和主二进制文件:

% cc -Wall -O3 -shared -o lib/libxyz.dylib xyz.c -Wl,-install_name,'@rpath/lib/libxyz.dylib'
% cc -Wall -O3 -o main main.c -Llib -lxyz -Wl,-rpath,'@executable_path'

如果您只能重新编译主二进制文件:

% install_name_tool -id '@rpath/lib/libxyz.dylib' lib/libxyz.dylib
% cc -Wall -O3 -o main main.c -Llib -lxyz -Wl,-rpath,'@executable_path'

如果您无法重新编译:

% install_name_tool -id '@rpath/lib/libxyz.dylib' lib/libxyz.dylib
% install_name_tool -change 'libxyz.dylib' '@rpath/lib/libxyz.dylib' main
% install_name_tool -add_rpath '@executable_path' main

请注意,如果您的任何二进制文件已签名,这当然会使该签名无效。用于codesign -f ...替换现有签名。

于 2021-02-19T21:04:48.770 回答