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