Думаю, я воспользуюсь этим вопросом, чтобы написать канонический ответ для всех проблем с изображением, которые не найдены.
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.
Это говорит нам о трех шагах того, как работают имена установки:
- Компоновщик встраивает имя при построении библиотеки.
- Компоновщик копирует имя в двоичные файлы, ссылаясь на эту библиотеку, когда они создаются.
- Dyld использует это имя, чтобы попытаться загрузить библиотеку.
Это, очевидно, вызовет проблемы, если библиотеки будут перемещены или даже не скомпилированы с именем установки, соответствующим пути, по которому они закончатся.
3. Решение
Решение состоит в том, чтобы изменить путь имени установки. Где и как зависит от вашей настройки. Вы можете изменить его двумя способами:
- Перекомпилируйте библиотеку с правильным именем установки (либо
-Wl,-install_name,...
, либо полностью -o ...
), затем перекомпилируйте основной двоичный файл, чтобы связать его с этим.
- Используйте
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
и 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
LD_LIBRARY_PATH
не определяет, где программы GCC находят свои динамические библиотеки? Статические библиотеки компилируются в исполняемый файл. Так что в вашем случаеexport LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
следует добавить текущий каталог в путь к динамической библиотеке. Но, возможно, прочтите и это: hpc.dtu.dk/?page_id=1180 - person Jerry Jeremiah   schedule 19.02.2021LD_LIBRARY_PATH
. Я понял, что могу также создать свою необходимую библиотеку как статическую.a
вместо динамической.dylib
, и тогда способ, которым я связал указанный выше путь, сработал. Я бы попробовал вашу рекомендацию позже, чтобы посмотреть, работает ли она так, как вы предложили. - person Asti   schedule 19.02.2021