Как заставить 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
(Определение HERE=1 дополнительно включает вызов test1, так как он маленький и локальный, а -O3 включен).
Живой пример на https://godbolt.org/g/eZvmp7.
-fno-semantic-interposition
тоже сделает эту работу, но это также нарушает гарантию языка C, и это своего рода большой молот, у которого нет зернистости псевдонимов.
Если вы не можете изменить исходный код, вы можете использовать большой молоток: -Bsymbolic linker flag:
При создании общей библиотеки свяжите ссылки на глобальные символы с определением в общей библиотеке, если оно есть. Обычно программа, связанная с общей библиотекой, может переопределить определение в общей библиотеке. Эта опция имеет смысл только на платформах ELF, которые поддерживают общие библиотеки.
Но имейте в виду, что он сломается, если некоторые части библиотеки полагаются на взаимное расположение символов. Я бы рекомендовал использовать скрытые функции, которые не нужно экспортировать (устанавливая скрытую видимость для них) или вызывая их через скрытые псевдонимы (специально предназначенные для выполнения внутрибиблиотечных вызовов без PLT контролируемым образом).