Як боротися зі зіткненнями символів між статично пов'язаними бібліотеками?


84

Одним з найважливіших правил та найкращих практик при написанні бібліотеки є розміщення всіх символів бібліотеки у просторі імен, що відповідає конкретній бібліотеці. C ++ полегшує це завдяки namespaceключовому слову. У C звичайний підхід полягає в префіксі ідентифікаторів певним префіксом для певної бібліотеки.

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

Але що, якщо ви зіткнулися з деякими бібліотеками, в яких ви не можете змінити імена символів / ідентифікатори? Можливо, у вас є лише статичний двійковий файл і заголовки, або ви цього не хочете, або вам заборонено самостійно коригувати та перекомпілювати.


Дивіться також: stackoverflow.com/questions/6538501 / ...
ninjalj

Відповіді:


143

Принаймні у випадку зі статичними бібліотеками ви можете обійти це досить зручно.

Розглянемо ті заголовки бібліотек foo and bar . Для цього підручника я також дам вам вихідні файли

приклади / ex01 / foo.h

examples / ex01 / foo.c (це може бути непрозоро / недоступно)

example / ex01 / bar.h

examples / ex01 / bar.c (це може бути непрозоро / недоступно)

Ми хочемо використовувати їх у програмі foobar

example / ex01 / foobar.c

Одразу стає очевидною одна проблема: C не знає перевантаження. Отже, ми маємо двічі дві функції з однаковим іменем, але з різним підписом. Тож нам потрібен якийсь спосіб їх розрізнити. У будь-якому випадку, давайте подивимося, що скаже про це компілятор:

Гаразд, це не було несподіванкою, воно просто розповіло нам про те, що ми вже знали або принаймні підозрювали.

Тож чи можемо ми якось вирішити це зіткнення ідентифікатора, не змінюючи вихідний код або заголовки оригінальних бібліотек? Насправді ми можемо.

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

example / ex02 / foobar.c

Тепер, якщо ми скомпілюємо це ...

... спочатку здається, що справи погіршились. Але придивіться: насправді етап складання пройшов чудово. Це просто лінкер, який зараз скаржиться, що символи стикаються, і він повідомляє нам місце (вихідний файл і рядок), де це відбувається. І як ми бачимо, ці символи не мають префіксу.

Давайте поглянемо на таблиці символів з утилітою nm :

Отже, зараз нам поставлено завдання з вправою префіксувати ці символи в якомусь непрозорому двійковому файлі. Так, я знаю, що в ході цього прикладу у нас є джерела, і я міг би це змінити там. Але наразі просто припустимо, що у вас є лише ті файли .o або файл .a (що насправді є лише купою .o ).

обйкопія на допомогу

Існує один інструмент, особливо цікавий для нас: 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

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

Це не просто розширення сучасних компіляторів; чинний стандарт C також вимагає, щоб компілятор підтримував досить довгі зовнішні імена. Я забуваю точну довжину, але це приблизно 31 символ, якщо я правильно пам’ятаю.

Але що, якщо ви зіткнулися з деякими бібліотеками, в яких ви не можете змінити імена символів / ідентифікатори? Можливо, у вас є лише статичний двійковий файл і заголовки, або ви цього не хочете, або вам заборонено самостійно коригувати та перекомпілювати.

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

Я справді вважаю, що це найкращий підхід, оскільки він вирішує проблему для всіх . Будь-який локальний хакер, який ви зробите, залишить проблему в бібліотеці для наступного нещасного користувача, з яким він знову зіткнеться і побореться.

Якщо вам дійсно потрібне швидке виправлення, і у вас є джерело, ви можете додати купу і -Dfoo=crappylib_foo -Dbar=crappylib_barт. Д. До make-файлу, щоб це виправити. Якщо ні, скористайтеся objcopyзнайденим рішенням.


Ви, звичайно, маєте рацію, однак іноді вам потрібен брудний хак, як той, який я показав вище. Наприклад, якщо ви застрягли в якійсь застарілій бібліотеці, де продавець припинив свою діяльність чи тому подібне. Я спеціально написав це для статичних бібліотек.
datenwolf

3

Якщо ви використовуєте GCC, перемикач --allow-multiple-definition linker є зручним інструментом налагодження. Це заважає лінкеру скористатися першим визначенням (і не скиглити). Детальніше про це тут .

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.