Как заставить gcc вызывать функцию непосредственно в PIC-коде?
рассмотрим следующую функцию:
extern void test1(void);
extern void test2(void) {
test1();
}
это код, который GCC генерирует без -fpic
на amd64 Linux:
test2:
jmp test1
когда я компилирую с -fpic
, gcc явно вызывает через PLT, чтобы включить интерпозицию символов:
test2:
jmp test1@PLT
это, однако, не требуется строго для независимого от позиции кода и может быть оставлено, если я не хочу поддерживать. При необходимости компоновщик переписывает цель перехода на символ PLT в любом случае.
как я могу, не изменяя исходный код и не делая скомпилированный код непригодным для общей библиотеки, сделать вызовы функций прямыми к их целям, а не явно через PLT?
2 ответов
если вы объявляете test1()
скрытые (__attribute__((__visibility__("hidden")))
, прыжок будет прямым.
теперь test1()
не может быть определен в его исходной единице перевода как скрытый, но я считаю, что от этого несоответствия не должно быть никакого вреда, кроме гарантии языка C, что &test1 == &test1
может быть нарушен для вас во время выполнения, если один из указателей был получен через скрытую ссылку и один через общедоступный (общедоступная ссылка могла быть вставлена через предварительную загрузку или DSO, который пришел до текущего в область поиска, в то время как скрытая ссылка (которая приводит к прямым скачкам) эффективно предотвращает любое вмешательство)
более правильным способом справиться с этим было бы определить два имени для test1()
- публичное имя и частное / скрытое имя.
в gcc и clang это можно сделать с помощью некоторой магии псевдонимов, которая может быть выполнена только в блоке перевода, который определяет символ.
макросы могут сделать его красивше:
#define PRIVATE __attribute__((__visibility__("hidden")))
#define PUBLIC __attribute__((__visibility__("default")))
#define PRIVATE_ALIAS(Alias,OfWhat) \
extern __typeof(OfWhat) Alias __attribute((__alias__(#OfWhat), \
__visibility__("hidden")))
#if HERE
PUBLIC void test1(void) { }
PRIVATE_ALIAS(test1__,test1);
#else
PUBLIC void test1(void);
PRIVATE void test1__(void);
#endif
void call_test1(void) { test1(); }
void call_test1__(void) { test1__(); }
void call_ext0(void) { void ext0(void); ext0(); }
void call_ext1(void) { PRIVATE void ext1(void); ext1(); }
выше компилируется (- O3, x86-64) в:
call_test1:
jmp test1@PLT
call_test1__:
jmp test1__
call_ext0:
jmp ext0@PLT
call_ext1:
jmp ext1
(определение здесь=1 дополнительно inlines вызов test1, так как он маленький и локальный и-O3 включен).
живой пример в https://godbolt.org/g/eZvmp7.
-fno-semantic-interposition
сделает работу тоже, но она также ломает гарантию языка C, и это своего рода большой молоток, который не имеет гранулярности сглаживания.
Если вы не можете изменить исходный код, вы можете использовать флаг Big-hammer: - bsymbolic linker:
при создании общей библиотеки привязывайте ссылки на глобальные символы к определение в общей библиотеке, если таковое имеется. Нормально, возможно для программы, связанной с общей библиотекой, чтобы переопределить определение в общей библиотеке. Этот параметр имеет значение только на платформах ELF какая поддержка разделяется библиотеки.
но помните, что он сломается, если некоторые части библиотеки полагаются на символ разъединения. Я бы рекомендовал пойти с функциями скрытия, которые не нужно экспортировать (установив скрытую видимость на них) или вызывая их через скрытые псевдонимы (специально разработанные для выполнения внутрибиблиотечных вызовов без PLT контролируемым образом).