Linux rpath or rpath
Recently I’ve been working on the Comedi ebuild in my Gentoo overlay, wrestling with the following error:
While tracking this down the source of this error, I learned a lot about dynamic linking on Linux, so here’s the condensed version.
RPATH , RUNPATH , and LD_LIBRARY_PATH . The current state of affairs is well summarized on the Debian wiki, which lists the library search path:
- the RPATH binary header (set at build-time) of the library causing the lookup (if any)
- the RPATH binary header (set at build-time) of the executable
- the LD_LIBRARY_PATH environment variable (set at run-time)
- the RUNPATH binary header (set at build-time) of the executable
- /etc/ld.so.cache
- base library directories ( /lib and /usr/lib )
There was a big dust-up between Debian and libtool back in 1999 when libtool-generated RPATH s caused problems during the libc5 to libc6 transition. The mailing list discussion makes for amusing and informative reading, if you’ve got a spare hour or two ;). If not, you should at least read the opening post, the description of competing contracts, and Buddha Buck’s description of how linking works, although I imagine things might have changed since then. The Debian / libtool compromise (don’t set RPATH by default for directories in the dynamic linker search path) was implemented in libtool 1.5.2 (released in 2004, see the Debian wiki), so this is not as big an issue as it once was.
By the way, it looks like RUNPATH was added since 1999 as a version of RPATH that did not override LD_LIBRARY_PATH , which is good, since LD_LIBARY_PATH gives you a way to link against libraries in, say, /var/tmp/portage/sci-libs/comedilib-9999/work/comedilib-9999/lib/.libs to test your executable before installation.
Anyhow, issues with rpaths persist. Since it both hard to predict all installation configurations at compile time, and tools to change rpaths later on (i.e. chrpath and patchelf) aren’t able to increase the size of the rpath string on Linux (they can on Solaris, because Solaris leaves a bit of padding at the end of the dynamic string table in the compiled ELF file). This means you will have trouble moving a library out of the standard library path into some out-of-the-way location. However, in the more common case of installing a library into the standard library path, chrpath is the tool you need, and it solved my comedilib QA issue.
Along the way, I ran across two other interesting posts by Diego PettenГІ about not bundling libraries.
Setting RPATH and RUNPATH
Most of the time, you’ll want to avoid setting RPATH and RUNPATH and just use libraries in your usual linking path. However, sometimes they are useful. For example, SimulAVR depends on the AVR-specific libbfd , which is hopefully not in your usual path. On Gentoo, crossdev installs under /usr/lib/binutils/ :
When you link against this library, you’ll want to set RUNPATH so you don’t have to remember to use LD_LIBRARY_PATH every time you run simulavr . Of course, if you switch to a different binutils version (e.g. not git ), you’ll need to fix the RUNPATH to point to the new target (or just rebuild simulavr against the new version).
Since you’re probably not calling the linker directly when you build simulavr , you’ll want to set some linker flags at configure time:
The relevant linker flags are -rpath and —enable-new-dtags . Without —enable-new-dtags , you’ll just set the RPATH flag, which is probably not what you want. With —enable-new-dtags , you’ll set both RPAH and RUNPATH to the same value. From ld(1) :
The DT_RPATH entries are ignored if DT_RUNPATH entries exist.
so setting both is the same as just setting RUNPATH (except for tools like chrpath which are only designed to handle a single tag). You can use readelf to see if the tags were set:
On Gentoo, —enable-new-dtags has been the default since 2005, but explicitly including the flag can’t hurt ;).
Источник
rpath vs runpath
Before we understand the difference between rpath and runpath, we need to understand where they are used. Both rpath and runpath are used to specify directories to search for shared libraries(dynamic libraries). If you are not sure what shared libraries is, I have a story written on Static vs Dynamic libraries.
Shared libraries are libraries which are not bundles along with the executable. They are loaded at the run time. How does the executable know where the libraries are present? Every executable follows a sequence of directories search to search for libraries.
Let’s understand this with a small experiment. We will build a shared library and try linking it with the executable.
We can now use the library in sample executable.
The executable cannot find the library. Let’s debug to to see the paths the library is searched for.
Now let’s try to use one of the methodology LD_LIBRARY_PATH to specify the path of the library. LD_LIBRARY_PATH specifies the search path for the executable. Let’s see the the debug output
Now you can see that “.” is added to the search path and we were able to find the library and the program executed successfully. Let’s delete the library and try the same.
As we can see after it uses the LD_LIBRARY_PATH for search it uses the system path as it cannot find the library in the LD_LIBRARY_PATH paths.
Hope by now you should have a understanding of runtime paths. We used LD_LIBRARY_PATH to specify runtime path. Similarly rpath and runpath are used the specify the runtime paths to find libraries. The difference is the order in which they are searched. We will understand the order by experiments that follow.
rpath
We are using the same test programs as before.
Looking at the compile step above we include the rpath options to provide the current path. We use the “readelf” to check the path set. The output prints as expected.
Now let’s create a another similar library which displays another message, buts let’s create it another folder named overrided_library inside the current folder.
Now we will be conducting the rest of the experiments in the parent folder.
Lets execute the already created the use_shared executable but also providing the LD_LIBRARY_PATH to the library in the overrided_library path
Let’s see the debug output of the executable load steps
The library we search for was available in the folder provided in the rpath. So it does not bother to search in the path specified from the LD_LIBRARY_PATH.
Let’s continue the example by deleting the shared library in the current path(not the one in overrided_library folder)
There we go, we have the message printed from the library in the overrided_library. Let’s see the debug message for the same
Here we see that first rpath is searched and since the library was not available so next it searched in the LD_LIBRARY_PATH.
So rpath is first searched and next LD_LIBRARY_PATH.
runpath
For this experiment we are going to use the same programs and folder structure we had for the previous experiment.
Let’s compile the executable with runpath set rather than rpath. For this we need to use “enable-new-dtags” flag.
Here we see that the message from the library in the overrided_library folder is printed. The reason is runpath comes lower in precedence to the LD_LIBRARY_PATH. Let’s see this with the debug info
We can see that the LD_LIBRARY_PATH is searched and the library is found and the runpath is not searched. Now let’s delete the library in the overrided_library folder.
We can see from the above output that the library from the runpath was executed. The reason is it did not find the library in the LD_LIBRARY_PATH. Let’s prove this using the debug info.
From this we can udestand that runpath has lower precedence than LD_LIBRARY_PATH. The order of precedence for search paths are
But why do we need two flags here rpath and runpath. Cant we live with one.
Conclusion
Earlier rpath was the only flag that existed. The problem arised that once rpath is set(which is done at the build time) we cannot override it during execution since LD_LIBRARY_PATH has lower precedence. We need to rebuild the executable everytime we need to test it with a different library which was quite annoying. That is when runpath was introduced where we could override the libraries with LD_LIBRARY_PATH which makes it easy to test accross different libraries without rebuilding it everytime.
Источник
What’s the difference between -rpath and -L?
gcc and ld provide many ways to specify a search path for libraries—among them the -rpath and -L flags. The manpages reveal no differences between these two flags, effectively saying each flag adds a library to the library search path. Yet it seems strange that both flags do exactly the same thing. What are the differences, if any, between these two options?
1 Answer 1
You must be reading some outdated copies of the manpages (emphasis added):
-rpath=dir
Add a directory to the runtime library search path. This is used
when linking an ELF executable with shared objects. All -rpath
arguments are concatenated and passed to the runtime linker, which
uses them to locate shared objects at runtime.
-L searchdir
—library-path=searchdir
Add path searchdir to the list of paths that ld will search for
archive libraries and ld control scripts.
So, -L tells ld where to look for libraries to link against when linking. You use this (for example) when you’re building against libraries in your build tree, which will be put in the normal system library paths by make install . —rpath , on the other hand, stores that path inside the executable, so that the runtime dynamic linker can find the libraries. You use this when your libraries are outside the system library search path.
Источник
Про компоновку, dependency hell и обратную совместимость
В данной статье речь пойдёт о высокоуровневом взгляде на компоновку. Где ищутся разделяемые библиотеки на Linux, BSD*, Mac OS X, Windows, от которых зависят приложения? Что делать с обратной совместимостью? Как бороться с адом зависимостей?
Предполагается, что читатель знаком с такими наборами символов как «компилятор», «объектный файл», «компоновщик», «статическая библиотека», «динамическая библиотека», «динамический загрузчик» и некоторыми другими, поэтому разжёвывать мы ничего не будем.
Проблемы статической загрузки динамических библиотек:
main.exe зависит от version-0.3.dll и bar.dll. bar в свою очередь, зависит от version-0.2.dll, которая бинарно не совместима с версией 0.3 (не просто символы отсутствуют, а совпадают имена, но различное число аргументов, или создают объекты разной природы и т. п.). Затрут ли символы из version-0.2.dll оные из version-0.3.dll? Тот же вопрос стоит тогда, когда используется одна статическая версия библиотеки (скажем, version-0.2.lib) и динамическая (version-0.3.dll);
- создание перемещаемых приложений: где динамический загрузчик будет искать version-0. dll и bar.dll для приложения из предыдущего пункта? Найдёт ли он зависимости main.exe, если тот будет перемещён в другую папку? Как нужно собрать main.exe, чтобы зависимости искались относительно исполняемого файла?
- dependency hell: две версии одной библиотеки /opt/kde3/lib/libkdecore.so и /opt/kde4/lib/libkdecore.so (с которой плазма уже не падает), половина программ требуют первую, другая половина программ — вторую. Обе библиотеки нельзя поместить в одну область видимости (один каталог). Эта же проблема есть и в п. 1, т. к. надо поместить две версии библиотеки version в один каталог.
После прочтения первого пункта читатель может воскликнуть: «Да это извращение! Так не делают! Надо использовать одну версию библиотеки!» Да, это так, но в жизни всякое бывает. Простейший пример: ваше приложение использует библиотеку а, и стороннюю закрытую библиотеку (я подчёркиваю это, чужой платный продукт) б, которая ну вот никак не может использовать ту же версию а, что и вы или наоборот.
Другим примером в рамках огромного проекта служит тот факт, что выпуск различных частей имеет разный период (с чем наша команда столкнулась и что вообще послужило причиной написания данного текста). Таким образом, главный продукт может время от времени выходить в конфигурации, описанной в пункте 1.
Dependency hell больше актуален для разработчиков системных библиотек и операционных систем, но и в прикладной области может возникнуть. Опять же предположим, что имеется огромный проект, в котором несколько исполняемых программ. Все они зависят от одной библиотеки, но разных версий. (Это не та же ситуация, что и в п. 1: там в один процесс загружается две версии одной библиотеки, а здесь в каждый процесс загружается только одна, но каждый использует свою версию).
Побег из ада
Ответ прост: надо добавить версию в имя файла библиотеки. Это позволит размещать файлы библиотек в одном каталоге. При этом рекомендуется добавлять версию ABI, а не API, порождая тем самым две параллельных ветки версий и соответствующие трудности.
Контроль версии – очень рутиная работа. Рассмотрим схему x.y.z:
- x – мажорный выпуск. Ни о какой совместимости речи не идёт;
- y – минорный выпуск. Либа совместима на уровне исходных текстов, но двоичная совместимость может быть сломана;
- z – багофикс. Либа совместима в обе стороны.
Тогда в имя файла разумно включить x.y. Если при увеличении минорной версии совместимость сохранили, то достаточно сделать соответствующий симлинк:
version-1.1.dll
version-1.0.dll → version-1.1.dll
Будут работать и приложения, использующие version-1.1.0, и те, кто использует version-1.0.x.
Если совместимость сломали, то в системе будет два файла и снова всё будет работать.
Если по каким-то причинам совместимость сломали при багофиксе, то должна быть увеличена минорная версия (и нечего фейлится, как это сделала команда любимейшего Qt [1] ).
Кстати говоря, никто не запрещает вообще включить версию API – тогда символических ссылок будет больше, т.к. совместимость чаще сохраняется. Зато в этом случае упомянутый фейл Qt разрулился бы легко и не заставил увеличивать минорную версию.
Это справедливо для всех платформ.
Решение оставшихся двух вопросов отличается в зависимости от ОС.
ELF & GNU ld (Linux, *BSD, etc)
В разделяемой библиотеке формата ELF присутствует так называемое SONAME [2] [3] . Это – строка символов, которая прописывается в двоичный файл в секцию DT_SONAME. Просмотреть SONAME для библиотеки можно, например, так:
Если программа/библиотека faz связывается с библиотекой baz, которая имеет SONAME = baz-0.dll, то строка baz-0.dll будет жёстко прописана в двоичном файле faz в секции DT_NEEDED, и при его запуске динамический загрузчик будет искать файл с именем baz-0.dll. При этом никто не запрещает назвать файл по-другому!
Просмотреть SONAME’ы, от которых зависит исполняемый файл можно так:
Динамический загрузчик ищет библиотеки из секции DT_NEEDED в следующих местах в данном порядке [4][5] :
- список каталогов в секции DT_RPATH, которая жёстко прописана в исполняемом файле. Поддерживается большинством *nix-систем. Игнорируется, если присутствует секция DT_RUNPATH;
- LD_LIBRARY_PATH – переменная окружения, также содержит список каталогов;
- DT_RUNPATH – тоже самое, что и DT_RPATH, только просматривается после LD_LIBRARY_PATH. Поддерживается только на самых свежих Unix-подобных системах;
- /etc/ld.so.conf – файл настроек динамического загрузчика ld.so, который содержит список папок с библиотеками;
- жёстко зашитые пути – обычно /lib и /usr/lib.
Формат данных для RPATH, LD_LIBRARY_PATH и RUNPATH такой же, как и для PATH: список путей, разделённых двоеточием. Просмотреть RUNPATH’ы можно, например, так:
R[UN]PATH может содержать специальную метку $ORIGIN, которую динамический загрузчик развернёт в полный абсолютный путь до загружаемой сущности. Здесь стоит отметить, что некоторые разработчики добавляют в RUNPATH “.” (точку). Это не тоже самое, что $ORIGIN! Точка развернётся в текущий рабочий каталог, который естественно не обязан совпадать с путём до сущности!
Для демонстрации написанного, разработаем приложение по схеме из п. 1 (ссылка на хранилище в гитхабе: github.com/gshep/linking-sample). Чтобы собрать всю систему достаточно перейти в корень папки и вызвать ./linux_make_good.sh , результат будет в папке result . Ниже будут разобраны некоторые этапы сборки.
На этапе компоновки библиотек version-0.x задаются SONAME:
Они зависят только от системных библиотек и поэтому не требуют наличия секций R[UN]PATH.
Библиотека bar уже зависит от version-0.2, поэтому нужно указать RPATH:
Параметр —enable-new-dtags указывает компоновщику заполнить секцию DT_RUNPATH.
Параметр -Wl,-rpath. позволяет заполнить секцию R[UN]PATH. Для задания списка путей можно указать параметр несколько раз, либо перечислить все пути через двоеточие:
Теперь всё содержимое папки result целиком или саму папку можно перемещать по файловой системе как угодно, но при запуске динамический загрузчик найдёт все зависимости и программа исполнится:
Вот мы и подошли к проблеме затирания символов! Bar использует version-0.2.dll, в которой get_number() возвращает 2, а само приложение version-0.3.dll, где та же функция возращает уже 3. По выводу приложения видно, что одна версия функции get_number затирается другой.
Дело в том [6; Dynamic Linking and Loading, Comparison of dynamic linking approaches] , что GNU ld & ELF не использует SONAME или имя файла в качестве пространства имён для импортируемых символов:
если разные библиотеки экспортируют сущности с одними и теми же именами, то одни из них будут перетирать другие и в лучшем случае программа упадёт.
Случай, когда одна из библиотек суть статическая, решается просто: все символы статической библиотеки должны быть скрыты [7, 2.2.2 Define Global Visibility] .
К сожалению, в случае динамических библиотек не всё так просто. У компоновщика/загрузчика GNU отсутствует такая функциональность, как прямое связывание [8] . Кто-то пилил эту возможность в Генту [9] , но кажется, всё заглохло. В Солярке она есть [10] [11] , но сама Солярка сдохла…
Одним из возможных вариантов является версионирование самих символов [7, 2.2.5 Use Export Maps] . На самом деле это больше похоже на декорирование символов. (Можно только представлять, что сейчас кричит читатель, программирующий на Си++. )
Данный способ заключается в том, чтобы создать так называемый версионный сценарий, в котором перечислить все экспортируемые и скрытые сущности [12] [13] . Пример сценария из version-0.3:
На этапе компоновки указать данный файл с помощью параметра —version-script=/path/to/version.script . После этого приложение, которое будет связано с такой либой получит в NEEDED version-0.3.dll, а в таблице импорта неопределённый символ get_number@@VERSION_0.3 , хотя в заголовочных файлах по-прежнему будет просто int get_number().
Натравите nm на любую программу, которая использует glibc, и вы прозреете!
Чтобы собрать пример с использованием версионирования символов в библиотеках version-0.x запустите корневой сценарий linux_make_good.sh с параметром use_version_script :
- Эй, Дарт! Наша libX будет поддерживать Линукс!
- Noooooooooooooooooooooooooo!
Да, после фейла, с которым наша команда столкнулась, капитан принял волевое решение и теперь используется только одна версия либы (именно из-за Линукса).
Как обстоят дела на Маке?
Мак Ось использует формат Mach-o для исполняемых файлов, а для поиска символов двух-уровневое пространство имён [14, Two-level namespace] [16] . Это по-умолчанию сейчас, но можно собрать с плоским пространством имён или вообще отключить его при запуске программы [15, FORCE_FLAT_NAMESPACE] . Узнать, собран ли бинарник с поддержкой пространства имён поможет команда:
То есть не надо париться с каким-то дополнительным декорированием имён – просто включить версию в имя файла!
А что же с поиском зависимостей?
В макоси почти всё аналогично, только называется по-другому.
Вместо SONAME есть id библиотеки или install name. Просмотреть можно, например, так: Изменить можно с помощью install_name_tool.
При связывании с библиотекой её id прописывается в бинарнике.
Просмотреть зависимости бинарника можно так:или
При запуске dyld пытается открыть файл с именем «id» [15, DYNAMIC LIBRARY LOADING] , т. е. рассматривает install name как абсолютный путь к зависимости. Если потерпел неудачу – то ищет файл с именем/суффиксом «id» в каталогах, перечисленных в переменной окружения DYLD_LIBRARY_PATH (полный аналог LD_LIBRARY_PATH).
Если поиск в DYLD_LIBRARY_PATH не дал результатов, то dyld аналогично просматривает ещё парочку переменных окружения [15] , после чего поищет либу в стандартных каталогах.
Такая схема не позволяет собирать перемещаемые приложения, поэтому была введена специальная метка, которую можно прописывать в id: @executable_path/. Эта метка во время загрузки будет развёрнута в абсолютный путь до исполняемого файла.
Далее, можно поменять зависимости у готового бинарника:
Теперь загрузчик сначала будет искать эту либу в той же папке, где и main.exe. Чтобы не менять в готовом бинарнике, надо во время компоновки подсунуть либу libstdc++.dylib, у которой >
Далее, возникает одна проблема, а точнее две. Пусть есть такая иерархия:
- main.bin
- tools/
- auxiliary.bin
- library.dll
main.bin зависит от library.dll, но и tools/auxiliary.bin зависит от неё же.
При этом id либы = @executable_path/library.dll, и оба бинарника были просто с ней скомпонованы. Тогда при запуске auxiliary.bin загрузчик будет искать /path/to/tools/library.dll и естественно не найдёт! Конечно можно ручками после компоновки подправить tools/auxiliary.bin или кинуть мягкую ссылку, но опять неудобства!
Ещё лучше проблема проявляет себя, когда речь заходит о подключаемых модулях (plugins):
- main.bin
- plugin/
- 1.plugin
- helper.dylib
1.plugin имеет запись @executable_path/helper.dylib, но во время запуска она развернётся в абсолютный путь до main.bin, а не 1.plugin!
Для решения этой проблемы яблочники с версии Оси 10.4 ввели новый маркер: @loader_path/. Во время загрузки зависимости, этот маркер развернётся в абсолютный путь к бинарнику, который дёргает зависимость.
Последняя сложность заключается в том, что надо две версии связываемых библиотек: одни будут устанавлены в систему, и иметь а другие использованы для сборки проектов, и их Это легко решить с помощью install_name_tool, но утомительно; поэтому с версии 10.5 ввели метку @rpath/. Библиотека собирается с и копируется куда угодно. Бинарник собирается со списком путей для поиска зависимостей, в котором разрешено использовать @
Это аналогично RPATH/RUNPATH для ELF. При запуске бинарника строка @rpath/libfoo.dylib будет развёрнута в @executable_path/libs/libfoo.dylib, которая уже развернётся в абсолютный путь. Либо развернётся в /usr/lib/libfoo.dylib.
Просмотреть зашитые в бинарник rpath’ы можно так:
Удалить, изменить или добавить rpath’ы можно с помощью install_name_tool.
Проверяем на примере:
На айОС всё так же.
Как видно из примера, Mac OS X в плане динамических библиотек лучше Linux & Co.
И наконец, Windows!
Тут тоже всё хорошо [6; Dynamic Linking and Loading, Comparison of dynamic linking approaches] . Надо только добавить версию в имя файла и… симлинков нет! То есть они есть, но на них многие жалуются и работают они только на NTFS (Windows XP точно можно установить на FAT раздел). Следовательно, обратная совместимость может стоить приличного места на диске… Ну и ладно. )
Чтобы собрать пример на Windows потребуется запустить консоль Visual Studio, в которой уже будет настроено окружение. Далее сборка и запуск:
Либы ищутся только так [17] . Одним из возможных способов смены алгоритма поиска зависимостей является использование файла настроек приложения (application configuration file) и свойства privatePath у probing [18] . Однако данный способ применим только начиная с Windows 7/Server 2008 R2.
А ещё есть WinSxS и так называемые сборки (assemblies) [19] . Это – тема отдельной статьи. Однако пока писалась эта статья, снизошло озарение и понимание, что эти самые сборки нужны лишь для того (по крайней мере, Сишникам и Си++никам) чтобы все приложения компоновались, скажем, с comdlg32.dll, но все использовали разные версии.
Заключение
Все основные платформы позволяют относительно просто создавать приложения, которые могут быть установлены обычным копированием. Однако проблемы dependency hell, обратной совместимости и затирания символов разработчики должны решать самостоятельно.
Основным решением является выбор правильного версионирования и контроля за ним.
В то время, как Curiosity бороздит марсианские просторыавтор пытался здесь поведать о том, как избежать затирания символов, на хабре уже давно есть статьи, где рассказано, как специально добиться обратного: habrahabr.ru/post/106107, habrahabr.ru/post/115558.
П.С. Пока велась работа над данной статьёй, автор посетил конференцию “На стачку!”, где послушал доклад К. Назарова из Parallels о версионировании [20] . Ничего неожиданного или необычного там не прозвучало, но было приятно услышать, что в такой известной компании осознали проблему и сделали правильные выводы. Из нового для себя автор вынес оттуда ссылку: semver.org.
Пользуясь возможностью, хочу поблагодарить своих коллег Александра Сидорова и Александра Прокофьева за конструктивную критику и ценные замечания!
Источник