Подробнее, какие залипухи возникат при бинарной переносимости в Линухе, расписаны здесь
http://autopackage.org/docs/devguide/ch07.html.
Могу даже свой недоделаный перевод выложить, чтоб прочуствовал.
(Хм, не нашел paste-сервиса в котором бы текст с переносом длинных строк размещался. Везде строки не переносятся, надо горизонтально скроллить. Кто знает paste-сервис с форматированием конечного текста с автопереносом строк, дайте ссылу плиз).
Бинарная переносимостьСодержание- Версии функций (Symbol Versions)
- Конфликты версий функций (Symbolic collisions)
- Фальшивые зависимости (Bogus dependencies)
- Заголовочные файлы (Headers)
- Обработка исключений (Exception handling internals)
- C++
Версии функцийОказывается, довольно трудно делать бинарники, которые будут надежно работать на разных дистрибутивов, и даже в версиях одного и того же дистрибутива. Однако, для того чтобы ваш пакет был готов для корректной работы на всех дистрибутивах Linux, это проблема должна быть решена в первую очередь. Игнорировать эту проблему нельзя.
Первой проблемой является номер версии glibc. Glibc - это специальная библиотека, включенная в GNU C. Все программы используют ее либо прямо, либо косвенно, поскольку программный интерфейс ядра предоставляет функции для различных служб - для файловой системы, DNS поиска, арифметических функций и т. д. Эта библиотека, кроме того, занимается реализацией механизма shared library. Обычно, когда какая-либо библиотека теряет бинарную совместимость, майнтейнер (человек, ответственный за сборку), увеличивает главный (major) номер библиотеки, просто переименовав so-файл библиотеки (изменяется нужная цифра в имени so-файла). Это стандартное решение. И при таком решении, в системе может присутствовать одновременно несколько вариантов одной и той же библиотеки. Однако, для glibc такое решение применить невозможно, по многим причинам, не будем вдаваться в подробности почему. Вместо этого, используется переименование каждой отдельной функции, чтобы была возможность хранить разные версии одной и той же функции внутри glibc библиотеки. ELF-интерпретатор, при обработке библиотеки glibc, делает прозрачным доступ к нужной функции.
Чтобы увидеть такие функции, наберите команду
# nm --dynamic /lib/libc.so.6 | grep chown
000b0ec0 T chown
000f7030 T chown
000b0f20 W fchown
000b0fe0 T fchownat
000b0f80 W lchown
Видно, что есть две реализации функции chown.
Когда программа компилируется, она связывается с последними версиями функций. Если при запуске программы, функция нужной версий не присутствует в системе, процесс будет прерван и программа не запустится. Эта проблема, и производные от нее, присутствуют в UNIX-подобных системах в течение очень долгого времени. Так как пользователи не имеют возможности просто обновлять glibc, приложение должно быть скомпилировано с достаточно старым набором версий функций – версий, присутствующих в большем количестве дистрибутивов, в которых планируется запускать бинарник. К счастью, есть по крайней мере частичное решение этой проблемы в виде apbuild. Это решение подменяет для gcc контроль версий функций, используемых в программе. Если любая версия функции в системе окажется слишком новой для собираемой программы, сборка будет прекращена. Автоматически будет выбрана старая версия функции, и бинарник будет переносимым (по крайней мере, для glibc) так, что сможет работать с glibc 2.2 – т.е. даже на дистрибутивах, старее Debian Stable или RH 7.2!
(Бредовый абзац. Совершенно непонятно что такое apbuild и что делает эта программа).
Конфликты версий функцийЕще одна проблема – это конфликт функций. Семантика ELF, к сожалению, основывается на устаревшей концепции статической компоновки. Когда программа выполняется, строится дерево зависимостей в бинарнике путем обхода функций через библиотеку /lib/ld-linux.so (которая является динамический компоновщик ELF). Если, согласно зависимостям, программа foo, зависит от libbar.so, которая, в свою очередь использует libpng.so, то foo, libbar.so и libpng.so будут отмаппены в память. Семантически, все символы из этих объектов сбрасываются в одну большую кучу, и это является сутью проблемы. Когда происходит увеличение версии функции, ELF интерпретатор glibc будет всегда выбирать первую функцию которую найдет, независимо от того, что объект уже подвергался процессу компоновки ранее.
(Теперь я еще больше запутался, как происходит интерпретация ELF файлов)
Для примера, давайте возьмем наш бинарник foo, и свяжем его с двумя библиотеками libA и libB. LibA в свою очередь будет связана с libA1, а libB связана с libB1. LibA1 и libB1 – это разные бинарники, и предположим, что в каждом есть своя реализация функции с именем someFunction(). То есть, имеем две абсолютно разные функции, которые имеют одно и то же имя. Казалось бы, что libA будет использовать функцию someFunction() из libA1, а libB будет вызывать функцию из libB1, что достаточно интуитивно (и именно так и происходит в Windows). Но в линукс такое поведение компоновщика не реализовано. Функция someFunction() будет вызываться из libA1 только потому что будет найдена первой. И это, как правило, приводит к почти мгновенному сегфолту при старте программы.
Так почему эти конфликты версий приводят к проблемам портирования бинарников? Ну хотя бы из-за того, что когда две библиотеки объявляют функции с одинаковыми именами, это происходит достаточно редко, а вот когда в системе присутствуют две версии одной и той же библиотеки, то это довольно распространенное явление. К примеру, libpng имеет две основных широко используемых версии - libpng.so.2 и libpng.so.3, и они совместимы. При поставке программы в виде исходников компиляция произойдет с последней версией, но будет несовместимость в случае поставки бинарника. Если скомпилировать бинарник в дистрибутиве с libpng.so.3, то программа будет слинкована с libpng.so.3. Если пользователь захочет запустить эту программу в старом дистрибутиве, в котором есть только libpng.so.2, то ему вначале нужно будет установить новую версию данной библиотеки, чтобы программа заработала. Мы можем сказать – что ж тут такого страшного? А неприятность в том, что программа (к примеру, игра) слинкована не только с libpng.so.3, но и с libSDL.
А теперь предположим, что libSDL связана с библиотекой libSDL_image, которая в данном дистрибутиве была собрана с зависимостью от libpng.so.2. И теперь, когда программа будет загружена, две разных версии библиотеки libpng (с одной стороны libpng.so.2, с другой стороны libpng.so.3) будут одновременно динамически подлинкованы, и это приведет к остановке программы. Это нехорошо.
Напомним, что мы имеем две версии исходников, которые не имеют ABI совместимости. В принципе, пользователь может решить проблему путем перекомпиляции программы, в момент которой произойдет линковка с libpng.so.2. На один раз этого будет достаточно. Но пересобирать программы из исходников не каждому по силам.
Как результат, при запуске, бинарники получаются связанными с библиотеками таким механизмом, в котором совершенно неизвестно, какие версии библиотек были использованы при компиляции бинарника. В принципе, запуск бинарника на другом дистрибутиве может произойти нормально, но никаких гарантий нет.
К счастью, существует решение этой проблемы в виде подключения к ELF-бинарнику надстройки в виде исправлящих (fixup) правил, как это было реализовано в операционной системе Sun Solaris. Прямые и групповые применения правил исправления позволяют ограничить видимость функций, что предотвращает проблему конфликта версий. К сожалению, такого механизма нет в glibc. Есть добровольцы? Проблема настолько большая и глубокая, что разработчики системы autopackage планируют временно прекратить разработку autoackage-инструментария, и потратить несколько месяцев на работу по изменению glibc.
Фальшивые зависимостиОпять же, рассмотрим зависимость функций при линковке. Типично, неприятность происходит при использовании конфигурирующих foo-скриптов, или конфигурирующих pkg-скриптов, которые находятся в установочных пакетах, которые используются для распространения программ. Это удобные способы, используемые для настройки системы сборки. К сожалению, есть побочный эффект – такие программы генерируют команды сборки, подобные следующей
-L/opt/foolib/lib -lfoo -lxml2 -lglib-2.0
Первые две опции понять достаточно просто (-L/opt/foolib/lib показывает путь, где могут находиться библиотеки программы foo, а -lfoo говорит линковщику что нужно собирать бинарник, используя библиотеку libfoo.so, которая скорее всего и лежит в каталоге /opt/foolib/lib). А что можно сказать о последних двух опциях? Они указаны из-за того, что библиотека libfoo содержит внутри себя вызовы функций из libxml2 и glib2. К сожалению, оказывается, что некоторые старые версии динамического линковщика (LD) пытаются полностью «покрыть» весь набор функций, используемых в зависимых библиотеках (т. е. чтобы был доступ к каждой функции библиотеки, даже если библиотека только находится в зависимостях, но ни одна функция этой библиотеки не используется). И так происходит для каждой библиотеки, и подобная обработка идет до конца цепочки зависимостей библиотек.
Это большая проблема. Даже если ваша программа не использует функции из библиотек glib или libxml, ваша программа все равно будет требовать данные для подключения каждой библиотеки, расположенной в дереве (цепочке) зависимостей. Если совместимая версия libfoo присутствует в системе пользователя, но она слинкована с какой-нибудь другой библиотекой «не той» версии, ваш бинарник работать не будет, так как получается фальшивая зависимость (зависимость от неиспользуемой библиотеки).
Этот вопрос решен в последней версии программы apbuild, которая может просканировать бинарный файл программы, найти фальшивые зависимости, и исключить их. Это делается с помощью опции -l.
Кроме того, данная проблема решена в binutils в версиях старше 2.15.90.0.2, и решается с помощью указания линковщику ld опции –as-needed. Начиная с версии 1.9 программа apbuild будет иметь подобную функцию (пока не реализованную), которая обеспечит высокую производительность и более аккуратное преобразование бинарника. А так же мы рекомендуем пользоваться системными утилитами toolchain.
Заголовочные файлыНекоторые библиотеки, такие как GTK+ и glibc, часто требуют использования заголовков конкретных версий. Другими словами, даже если требуются функции «начиная с версии 2.4», в случае если они уже были скомпилированы с другими GTK+ 2.4 заголовками, существует риск, что бинарник будет требовать зависимость от GTK+ 2.4.
Единственное правильное решение этой проблемы заключается в том, чтобы взять копии старых заголовков нужной версии, и положить их в директорию программы apbuild, таким образом при сборке будут первыми найдены и использованы именно эти заголовки, а не заголовки, присутствующие в данный момент в системе.
В частности, это решение влияет на макрос g_return_if_fail, используемый в glibc, на обработку ctype в функциях glibc, на макрос GDK_THREADS_ENTER/EXIT, используемый в GDK, и на pthread_cleanup_push/pop.
...
Дальше не перевел еще.