Статическая компоновка libstdc ++: есть ли ошибки?

93

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

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

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

Ник Хатчинсон
источник
Нет, статическое связывание с libstdc ++ этого не подразумевает. Если бы это действительно означало, что в этом -static-libstdc++варианте не было бы смысла , вы бы просто использовали-static
Джонатан Уэйкли
@JonathanWakely -static получит kernel too oldошибку в некоторой системе ubuntu 1404. Glibc.so похож kernel32.dllна окно, это часть интерфейса операционной системы, мы не должны встраивать его в наш двоичный файл. Вы можете использовать его, objdump -T [binary path]чтобы увидеть динамическую загрузку libstdc++.soили нет. Для программиста golang вы можете добавить #cgo linux LDFLAGS: -static-libstdc++ -static-libgccперед импортом "C"
бронзовый человек
@bronzeman, но мы говорим -static-libstdc++не о том, -staticчтобы libc.soне было статической связи.
Джонатан Уэйкли,
1
@NickHutchinson, ссылка в блоге исчезла. Этот вопрос SO является популярным поиском по релевантным терминам здесь. Можете ли вы воспроизвести важную информацию из этого сообщения в блоге в своем вопросе или предложить новую ссылку, если вы знаете, куда она перемещена?
Брайан Кейн
1
@BrianCain В интернет-архиве есть это: web.archive.org/web/20160313071116/http://www.trilithium.com/…
Роб Кенигер

Ответы:

136

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

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

Не правда. Единственные изменения C ++ ABI, внесенные после GCC 3.4, были обратно совместимы, то есть C ++ 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которая предоставляет эти символы, она будет работать, не имеет значения, если это немного другая версия с исправлениями из другой версии вашего дистрибутива.

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

Это тоже неправда.

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

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

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

Джонатан Уэйкли
источник
8
Это объяснение, особенно в отношении RPATH, великолепно.
nilweed 01
3
Поставлять libstdc ++ с вашим приложением в Linux - плохой совет. Google по запросу "steam libstdc ++", чтобы увидеть всю драму, которую это принесет. Короче говоря, если ваш exe загружает внешние библиотеки (например, opengl), которые хотят снова открыть libstdc ++ (например, драйверы radeon), эти библиотеки будут использовать ваш libstdc ++, потому что он уже загружен, а не их собственный, что им нужно и ожидать. Итак, вы вернулись на круги своя.
7
@cap, OP конкретно спрашивает о развертывании в дистрибутиве, где система libstdc ++ старше. Проблема Steam в том, что они связали libstdc ++., Так что он был старше системного (предположительно, он был новее в то время, когда они собирали его, но дистрибутивы перешли на еще более новые). Это можно решить, указав RPATH на каталог, содержащий libstdc++.so.6символическую ссылку, которая устанавливается во время установки, чтобы указать на прилагаемую библиотеку или на системную, если она новее. Существуют более сложные модели смешанной связи, используемые Red Hat DTS, но их сложно реализовать самостоятельно.
Джонатан Уэйкли
5
эй, парень, извини, если я не хочу, чтобы моя модель доставки двоичных файлов с обратной совместимостью включала в себя «доверие другим людям поддерживать совместимость libstdc ++ ABI» или «условное связывание libstdc ++ во время выполнения» ... если это сбивает с толку а там, что тут поделаешь, я не имею в виду неуважения. И если вы помните драму memcpy@GLIBC_2.14, вы не можете обвинить меня в том, что у меня проблемы с доверием :)
6
Мне пришлось использовать '-Wl, -rpath, $ ORIGIN' (обратите внимание на '-' перед rpath). Я не могу отредактировать ответ, потому что правка должна состоять как минимум из 6 символов ....
user368507
11

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

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

Эмиль Стырке
источник
1
__gnu_cxx::__freeres()Похоже, что эта функция хотя бы немного помогает в решении этой проблемы, поскольку освобождает внутренний буфер объекта пула. Но для меня довольно неясно, какое значение имеет вызов этой функции в отношении исключений, случайно сгенерированных впоследствии.
phlipsy
3

Дополнение к ответу Джонатана Уэйкли относительно 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 - очень полезный инструмент, но при его реализации все же есть некоторые недостатки.

Джонатан МакДевитт
источник
так что RPATH для общих библиотек бессмысленен! И я надеялся, что они немного улучшили Linux в этом отношении за последние два десятилетия ...
Фрэнк Пак
2

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

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

Александр Львович Беликов
источник
1
В чем проблема в зависимости от динамического glibc?
Ник Хатчинсон
Я считаю, что, по крайней мере, некоторое время назад libstdc ++ подразумевала зависимость от glibc. Не уверен, как обстоят дела сегодня.
Александр Л. Беликов
9
libstdc ++ действительно зависит от glibc (например, iostreams реализованы в терминах printf), но пока glibc в Ubuntu 10.04 предоставляет все функции, необходимые для нового libstdc ++, нет проблем с зависимостью от динамического glibc, на самом деле настоятельно рекомендуется никогда не связывать статически в glibc
Джонатан Уэйкли
1

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

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

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

Федоров7890
источник
1
Это, вероятно, похоже на смешивание реализаций libc (скажем, динамическое связывание с плагином, который, в свою очередь, динамически связывает glibc, тогда как само приложение статически связано с musl-libc). Рич Фелкер, автор musl-libc, утверждает, что проблема в таком сценарии состоит в том, что управление памятью glibc (использование sbrk) делает определенные предположения и в значительной степени ожидает, что будет работать в одиночку в рамках одного процесса ... не уверен, ограничивается ли это ограничением конкретная версия glibc или что-то еще.
0xC0000022L
и люди до сих пор не видят преимуществ интерфейса кучи Windows, который может работать с несколькими независимыми копиями libc ++ / libc внутри одного процесса. Таким людям не следует разрабатывать программное обеспечение.
Фрэнк Пак,
@FrankPuck имеет приличный опыт работы как с Windows, так и с Linux, и я могу сказать вам, что способ, которым «Windows» это делает, не поможет вам, когда MSVC является стороной, которая решает, какой распределитель использовать и как. Основное преимущество кучи в Windows, которое я вижу, заключается в том, что вы можете раздавать кусочки и фрагменты, а затем освобождать их одним махом. Но с MSVC вы все равно столкнетесь в значительной степени с проблемой, описанной выше, например, при передаче указателей, выделенных другой средой выполнения VC (выпуск вместо отладки или статически или динамически связанный). Так что «Windows» не застрахована. Следует проявлять осторожность в отношении обеих систем.
0xC0000022L