Как бороться с конфликтами символов между статически связанными библиотеками?

84

Одно из самых важных правил и передовых практик при написании библиотеки - поместить все символы библиотеки в пространство имен, специфичное для библиотеки. C ++ упрощает это благодаря namespaceключевому слову. В C обычный подход заключается в добавлении к идентификаторам префикса, специфичного для библиотеки.

Правила стандарта C поставить некоторые ограничения на тех (для безопасной компиляции): AC компилятор может смотреть на только первые 8 символах идентификатора, так foobar2k_eggsи foobar2k_spamможет быть интерпретирован как те же идентификаторы действительных - однако каждый современный компилятор допускает произвольные длинные идентификаторы , поэтому в наше время (21 век) мы не должны об этом беспокоиться.

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

свидание
источник
См. Также: stackoverflow.com/questions/6538501/…
ninjalj

Ответы:

143

По крайней мере, в случае со статическими библиотеками вы можете довольно удобно обойти это.

Рассмотрим заголовки библиотек foo и bar . Для этого урока я также дам вам исходные файлы

примеры / ex01 / foo.h

examples / ex01 / foo.c (может быть непрозрачным / недоступным)

пример / ex01 / bar.h

examples / ex01 / bar.c (может быть непрозрачным / недоступным)

Мы хотим использовать их в программе foobar

пример / ex01 / foobar.c

Одна проблема становится очевидной сразу: C не знает перегрузки. Итак, у нас есть дважды две функции с одинаковым именем, но разной подписью. Итак, нам нужен способ их различать. В любом случае, давайте посмотрим, что компилятор скажет по этому поводу:

Ладно, это не было сюрпризом, он просто сказал нам то, что мы уже знали или, по крайней мере, подозревали.

Итак, можем ли мы каким-то образом разрешить конфликт идентификаторов без изменения исходного кода или заголовков исходных библиотек? На самом деле мы можем.

Во-первых, давайте решим проблемы времени компиляции. Для этого мы окружаем заголовок, содержащий кучу #defineдиректив препроцессора, которые префикс всех символов, экспортируемых библиотекой. Позже мы сделаем это с помощью приятного уютного заголовка-оболочки, но просто для демонстрации происходящего мы дословно делали это в исходном файле foobar.c :

пример / ex02 / foobar.c

Теперь, если мы скомпилируем это ...

... сначала кажется, что все стало еще хуже. Но посмотрите внимательно: на самом деле этап компиляции прошел отлично. Это просто компоновщик, который теперь жалуется на столкновение символов и сообщает нам место (исходный файл и строку), где это происходит. И, как мы видим, эти символы не имеют префикса.

Посмотрим на таблицы символов с помощью утилиты nm :

Итак, теперь перед нами стоит задача добавить префикс этих символов в какой-нибудь непрозрачный двоичный файл. Да, я знаю, что в этом примере у нас есть источники и мы можем изменить это там. Но пока предположим, что у вас есть только эти файлы .o или .a (на самом деле это просто набор .o ).

objcopy спешит на помощь

Нам особенно интересен один инструмент: objcopy.

objcopy работает с временными файлами, поэтому мы можем использовать его, как если бы он работал на месте. Есть одна опция / операция, называемая --prefix-symbols, и у вас есть 3 предположения, что она делает.

Так что давайте бросим этого парня в наши упрямые библиотеки:

nm показывает нам, что это работало:

Давайте попробуем связать все это:

И действительно, это сработало:

Теперь я оставляю читателю в качестве упражнения для реализации инструмента / скрипта, который автоматически извлекает символы библиотеки с помощью nm , записывает файл заголовка оболочки структуры.

и применяет префикс символа к объектным файлам статической библиотеки с помощью objcopy .

А как насчет общих библиотек?

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

Обертку для батута не обойдется. Хуже того, вы не можете связываться с разделяемой библиотекой на уровне объектного файла, а вынуждены выполнять динамическую загрузку. Но это заслуживает отдельной статьи.

Оставайтесь с нами, и удачного кодирования.

свидание
источник
4
Впечатляет! Не ожидал, что с этим будет так легко objcopy.
Кос,
12
Вы только что ... ответили на свой вопрос в течение 1 минуты после того, как задали его?
Alex B
18
@Alex B: Это учебная статья, и я пошел по пути, предложенному мне на meta.stackoverflow.com, как можно разместить учебные пособия по (интересным?) Вопросам и их решениям. Возник вопрос о конфликтующих библиотеках, и я подумал: «Хм, я знаю, как справиться с подобным решением», написал статью и разместил ее здесь в форме вопросов и ответов. meta.stackexchange.com/questions/97240/…
datenwolf
4
@datenwolf есть идеи по решению этой проблемы для библиотек iOS. Как я выяснил, objcopy не поддерживает библиотеки iOS: /
Эге Акпинар
6
Бла-бла-бла objcopy --prefix-symbols ... +1!
Бен Джексон
7

Правила стандарта C накладывают на них некоторые ограничения (для безопасной компиляции): компилятор AC может смотреть только на первые 8 символов идентификатора, поэтому foobar2k_eggs и foobar2k_spam могут корректно интерпретироваться как одни и те же идентификаторы - однако каждый современный компилятор допускает произвольные длинные идентификаторы, поэтому в наше время (21 век) нам не стоит об этом беспокоиться.

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

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

Тогда вы застряли. Пожаловаться автору библиотеки. Однажды я столкнулся с такой ошибкой, когда пользователи моего приложения не могли создать его на Debian из-за libSDLсвязывания Debian libsoundfile, которое (по крайней мере, в то время) ужасно загрязняло глобальное пространство имен такими переменными, как dsp(я не шучу!). Я пожаловался Debian, и они исправили свои пакеты и отправили исправление в апстрим, где, как я полагаю, оно было применено, поскольку я никогда больше не слышал об этой проблеме.

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

Если вам действительно нужно быстрое исправление, и у вас есть исходный код, вы можете добавить кучу -Dfoo=crappylib_foo -Dbar=crappylib_barи т. Д. В make-файл, чтобы исправить это. Если нет, используйте objcopyнайденное вами решение.

R .. GitHub НЕ ПОМОГАЕТ ICE
источник
Вы, конечно, правы, но иногда вам нужен грязный хак, вроде того, что я показал выше. Например, если вы застряли с какой-то устаревшей библиотекой, поставщик которой прекратил работу, или аналогичной. Я специально написал это для статических библиотек.
datenwolf 04
3

Если вы используете GCC, переключатель компоновщика --allow-multiple-definition - удобный инструмент для отладки. Это заставляет компоновщика использовать первое определение (а не ныть по этому поводу). Подробнее об этом здесь .

Это помогло мне во время разработки, когда у меня есть исходный код для библиотеки, поставляемой поставщиком, и по той или иной причине мне нужно отследить библиотечную функцию. Переключатель позволяет скомпилировать и связать локальную копию исходного файла и по-прежнему ссылаться на неизмененную статическую библиотеку поставщика. Не забудьте выдернуть переключатель из символов создания, когда путешествие на поиски завершится. Код выпуска отгрузки с намеренными конфликтами пространства имен подвержен ошибкам, включая непреднамеренные конфликты пространств имен.

JJJSchmidt
источник