dyld: библиотека не загружена: как правильно указать компилятору GCC, где найти другую статическую библиотеку?

Я написал программу на C, которая вычисляет итерации волновых функций с временным шагом для решения уравнения Шредингера, зависящего от времени. На каком-то этапе мне нужно сделать прямое и обратное быстрое преобразование Фурье (БПФ), для чего я использовал библиотеку kissfft. (https://github.com/mborgerding/kissfft)

Структура моих программ примерно такая:

  • TDSE (working directory)
    • modules
    • включают
    • сценарии
    • test
      • inttest_analytical.c
    • kissfft
      • 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)

Насколько я понял, прочитав об этом в других вопросах о поиске в Google, 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??

Спасибо за любую помощь.


person Asti    schedule 18.02.2021    source источник
comment
Разве переменная окружения LD_LIBRARY_PATH не определяет, где программы GCC находят свои динамические библиотеки? Статические библиотеки компилируются в исполняемый файл. Так что в вашем случае export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. следует добавить текущий каталог в путь к динамической библиотеке. Но, возможно, прочтите и это: hpc.dtu.dk/?page_id=1180   -  person Jerry Jeremiah    schedule 19.02.2021
comment
Джерри, спасибо за комментарий. Прочитав предоставленную вами статью, я попытался обойти это с помощью LD_LIBRARY_PATH . Я понял, что могу также создать свою необходимую библиотеку как статическую .a вместо динамической .dylib, и тогда способ, которым я связал указанный выше путь, сработал. Я бы попробовал вашу рекомендацию позже, чтобы посмотреть, работает ли она так, как вы предложили.   -  person Asti    schedule 19.02.2021


Ответы (1)


Думаю, я воспользуюсь этим вопросом, чтобы написать канонический ответ для всех проблем с изображением, которые не найдены.

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.dylib против lib/libxyz.dylib. Это подводит нас к сути вопроса.

2. Причина

В macOS у каждой общей библиотеки есть имя установки, т. е. путь, по которому ее можно найти во время выполнения. Этот путь может принимать три формы:

  • Абсолютный, т.е. /usr/lib/libxyz.dylib.
  • Родственник, напр. lib/libxyz.dylib.
  • Магия, например. @rpath/libxyz.dylib.

Этот путь встроен в заголовок библиотеки Mach-O с помощью команды загрузки LC_ID_DYLIB. Его можно просмотреть с помощью 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 is the runtime directory of the main binary of the process. This is the simplest form, but only works if your libraries are only used in a single main binary.
    • @loader_path — это каталог времени выполнения двоичного файла в зависимости от библиотеки. Я рекомендую не использовать это, так как это сломается, если у вас есть два двоичных файла в разных папках, которые хотят связать с одной и той же библиотекой.
    • @rpath — это список каталогов времени выполнения, собранный из LC_RPATH команд загрузки. Это немного сложнее, но это наиболее гибкое решение, поскольку само может содержать @executable_path и @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

a и b — это двоичные файлы, которые связаны с libx и liby, которые, в свою очередь, связаны с libz. Для имени установки libz вы не можете использовать ни @executable_path (поскольку a и b находятся в разных каталогах), ни @loader_path (поскольку libx и liby находятся в разных каталогах). Но вы можете использовать любой из них внутри @rpath, и вот решение, которое вы должны принять:

  • Вы можете вставить rpath @executable_path в a и @executable_path/.. в b. Затем вы можете использовать @rpath для ссылки на корень проекта из всех двоичных файлов. libz будет иметь имя установки @rpath/lib/libz.dylib.
  • Или вы можете вставить rpath @loader_path/lib в libx и @loader_path в 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 ... для замены существующих подписей.

person Siguza    schedule 19.02.2021