Как заставить 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 контролируемым образом).

Другие вопросы по тегам