Подтвердить что ты не робот

Связывание libstdС++ статически: любые ошибки?

Мне нужно развернуть приложение С++, основанное на Ubuntu 12.10 с GCC 4.7 libstdС++, на системы под управлением Ubuntu 10.04, которая поставляется со значительно более старой версией libstdС++.

В настоящее время я компилирую с -static-libstdc++ -static-libgcc, как предложено в этом сообщении в блоге: Связывание libstdС++ статически. Автор предупреждает об использовании любого динамически загружаемого кода на С++ при компиляции libstdС++ статически, чего я еще не проверил. Тем не менее, все, кажется, идет гладко до сих пор: я могу использовать возможности С++ 11 на Ubuntu 10.04, что и было после.

Я отмечаю, что эта статья с 2005 года, и, возможно, с тех пор многое изменилось. Является ли его совет еще актуальным? Есть ли какие-то скрытые проблемы, о которых я должен знать?

4b9b3361

Ответ 1

Это сообщение в блоге довольно неточно.

Насколько я знаю, изменения С++ ABI были введены с каждым основным выпуском GCC (т.е. с разными компонентами первого или второго номера версии).

Не верно. Единственные изменения С++ ABI, введенные с тех пор, как GCC 3.4 были обратно совместимы, что означает, что С++ ABI стабилен в течение почти девяти лет.

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

Различия между исправленными версиями GCC для дистрибутивов являются незначительными, а не смена ABI, например. Fedora 4.6.3 20120306 (Red Hat 4.6.3-2) - это ABI, совместимый с версиями FSF 4.6.x и выше, и почти наверняка с любым 4.6.x из любого другого дистрибутива.

В библиотеках времени исполнения GNU/Linux GCC используется управление версиями символов ELF, поэтому легко проверить версии символов, необходимые объектам и библиотекам, и если у вас есть libstdc++.so, который предоставляет эти символы, он будет работать, неважно, это немного другая исправленная версия из другой версии вашего дистрибутива.

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

Это также неверно.

Тем не менее, статическая привязка к libstdc++.a является для вас одним из вариантов.

Причина, по которой это может не сработать, если вы динамически загружаете библиотеку (используя dlopen), состоит в том, что символы libstdС++, от которых она зависит, могут быть не нужны вашему приложению, когда вы (статически) связаны с ней, поэтому эти символы не будут присутствовать в вашем исполняемом файле. Это можно решить путем динамической привязки разделяемой библиотеки к libstdc++.so (что в любом случае необходимо делать, если это зависит от нее). Подстановка символа ELF означает, что символы, присутствующие в вашем исполняемом файле, будут использоваться совместно используемой библиотекой, но другие, не присутствующие в вашем исполняемом файле, будут найдены в зависимости от того, к какому типу libstdc++.so он ссылается. Если ваше приложение не использует dlopen, вам не нужно об этом заботиться.

Другой вариант (и тот, который я предпочитаю) - это развернуть новый libstdc++.so рядом с вашим приложением и убедиться, что он найден до системы по умолчанию libstdc++.so, что может быть сделано путем принудительного просмотра динамического компоновщика справа, либо используя переменную среды $LD_LIBRARY_PATH во время выполнения, либо установив RPATH в исполняемом файле в момент ссылки. Я предпочитаю использовать RPATH, поскольку он не полагается на правильную настройку среды для работы приложения. Если вы связали свое приложение с '-Wl,-rpath,$ORIGIN' (обратите внимание на одинарные кавычки, чтобы предотвратить развертывание оболочки с расширением $ORIGIN), тогда исполняемый файл будет иметь RPATH из $ORIGIN, который сообщает динамическому компоновщику искать общие библиотеки в тот же каталог, что и сам исполняемый файл. Если вы поместите новый libstdc++.so в тот же каталог, что и исполняемый файл, он будет найден во время выполнения, проблема будет решена. (Другой вариант - поместить исполняемый файл в /some/path/bin/ и новый libstdС++, поэтому в /some/path/lib/ и ссылку с '-Wl,-rpath,$ORIGIN/../lib' или любое другое фиксированное местоположение относительно исполняемого файла и установить RPATH относительно $ORIGIN)

Ответ 2

Еще одно дополнение к Джонатану Уэйкли, отличный ответ, почему dlopen() проблематичен:

Из-за нового пула обработки исключений в GCC 5 (см. PR 64535 и PR 65434), если вы удаляете и закрываете библиотеку, которая статически связана с libstdc++, вы будете каждый раз получать утечку памяти (объекта пула). Так что если есть шанс, что вы когда-либо будете использовать dlopen, кажется, что статическая связь - это действительно плохая идея libstdc++. Обратите внимание, что это реальная утечка, в отличие от доброкачественной, упомянутой в PR 65434.

Ответ 3

Вам также может потребоваться убедиться, что вы не зависите от динамического glibc. Запустите ldd в свой исполняемый файл и обратите внимание на любые динамические зависимости (libc/libm/libpthread являются подозреваемыми в использовании).

Дополнительным упражнением было бы создание кучи задействованных примеров С++ 11, использующих эту методологию, и на самом деле попытаться получить двоичные файлы в реальной системе 10.04. В большинстве случаев, если вы не делаете что-то странное с динамической загрузкой, вы сразу узнаете, работает ли программа или она сработает.

Ответ 4

Я хотел бы добавить к Джонатану Уэйкли ответ на следующий вопрос.

Играя -static-libstdc++ в Linux, я столкнулся с проблемой dlclose(). Предположим, у нас есть приложение "A", статически связанное с libstdc++ и оно загружается динамически, связанное с libstdc++ "P" во время выполнения. Это здорово. Но когда "A" выгружает "P", возникает ошибка сегментации. Я предполагаю, что после выгрузки libstdc++.so "A" больше не может использовать символы, связанные с libstdc++. Обратите внимание, что если "A" и "P" статически связаны с libstdc++, или "A" связан динамически, а "P" статически, проблема не возникает.

Резюме: если ваше приложение загружает/выгружает плагины, которые могут динамически связываться с libstdc++, приложение также должно быть динамически связано с ним. Это всего лишь мое наблюдение, и я хотел бы получить ваши комментарии.

Ответ 5

Дополнение к ответу Джонатана Уэйкли относительно RPATH:

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

Например, допустим, у вас есть приложение App.exe, которое динамически связано с libstdc++.so.x для GCC 4.9. App.exe разрешает эту зависимость через RPATH, т.е.

App.exe (RPATH=.:./gcc4_9/libstdc++.so.x)

Теперь допустим, что есть другая библиотека Dependency.so, которая имеет динамически связанную зависимость от libstdc++.so.y для GCC 5.5. Зависимость здесь разрешается через RPATH библиотеки, т.е.

Dependency.so (RPATH=.:./gcc5_5/libstdc++.so.y)

Когда App.exe загружает Dependency.so, он не добавляет и не добавляет RPATH библиотеки. Это вообще не справляется. Единственный RPATH, который рассматривается, будет запущенным приложением или App.exe в этом примере. Это означает, что если библиотека использует символы, которые находятся в gcc5_5/libstdc++.so.y, но не в gcc4_9/libstdc++.so.x, библиотека не сможет загрузиться.

Это как предупреждение, так как я сам сталкивался с этими проблемами в прошлом. RPATH - очень полезный инструмент, но в его реализации все еще есть некоторые ошибки.