понедельник, 2 мая 2011 г.

gcc Тёмная магия линкера

Последние пару дней упорно бился над проблемой потерянного символа. Обстоятельства его потери были не столь уж и мистическими, причину я понял сразу. А вот какое заклятье использовать дабы её искоренить, я понял только после детального изучения манов и кучи экспериментов.

Итак имеется статическая библиотека А и несколько плюсовых исходников. Юный маг хочет собрать из этого всего динамическую библиотеку. Разумеется это ему удаётся сделать одной левой, но... В полученной библиотеке отсутствует один из символов имеющихся в скатической библиотеке, который нигде более в рамках имеющегося кода не вызывается.

Ситуация выглядит логично. Просто линкер обнаружил unreferenced symbol и для оптимизации размера сгенерённого файла и ускорения зугрузки библиотеки не стал включать мёртвый код. Ну откуда ему знать, что это точка входа в приложение, которая будет позвана из Java кода через JNI. И собственно потерять можно всё что угодно, но не этот символ.

На помощь пришёл невнятно описанный в манах ключ -u. Он явно говрорит линкеру считать символ имя которого переданно аргументом этому ключу как undefined символ и провести поиск этого символа в библиотеках использумемых при линковке. Это заклинание помогло спасти бедный символ от жестогкого скальпеля линкера.

В процессе исследования было найдено ещё одно интересное чародейство, позволяюще ускорять время загрузки бинамической библиотеки путём исключения из таблицы символов внутренних функций и переменных библиотеки. Если нужно чтобы таблица символов содержала только публичный интерфейс библиотеки, то публичные функции, классы и переменные нужно промаркировать с помощью __attribute__((visibility("default"))):
 __attribute__((visibility("default")))  
 void foo();  
 class __attribute__((visibility("default"))) MyClass {  
 };  
 __attribute__((visibility("default")))  
 int globalNum = 5;  

После чего при компиляции нужно использовать флаг -fvisibility=hidden.

Суть сей магии достаточно проста. По умолчанию gcc считает все символы публичными и при линковке динамической библиотеки перечисляет абсолютно всё в таблице символов. Если что-то или кто-то используется только библиотечными потрохами и наружу смотреть не должно, то его можно явно промаркировать с помощью __attribute__((visibility("hidden"))) и оно не попадёт в таблицу символов. Но, как правило, публичный интерфейс намного меньше приватного и реже меняется, поэтому куда логичней перечислять символы которые должны быть видны. Тут на помощь приходит флаг -fvisibility=hidden который указывает, что все символы, для которых не указанно обратное, нужно считать спрятанными.

Зесь есть абсолютно полная аналогия с подходом в Windows и их __declspec(dllexport). К счастью тут не наблюдается ничего подобного маразматичному __declspec(dllimport). Символ промаркерованный с помощью __attribute__((visibility("default"))) в публичном хедере не нужно маркировать как-то иначе при компиляции приложения использующего данную динамическую библиотеку.