如何强制 gcc 直接在 PIC 代码中调用函数?

How do I force gcc to call a function directly in PIC code?

考虑以下函数:

extern void test1(void);
extern void test2(void) {
    test1();
}

这是 gcc 在 amd64 Linux 上没有 -fpic 时生成的代码 Linux:

test2:
    jmp test1

当我用 -fpic 编译时,gcc 通过 PLT 显式调用以启用符号插入:

test2:
    jmp test1@PLT

然而,这对于位置无关代码来说并不是严格需要的,如果我不想支持,可以将其排除在外。如有必要,链接器无论如何都会将跳转目标重写为 PLT 符号。

我怎样才能在不更改源代码且不使编译后的代码不适合共享库的情况下,使函数调用直接转到其目标而不是显式地通过 PLT?

如果无法更改源代码,可以使用大锤:-Bsymbolic 链接器标志:

When creating a shared library, bind references to global symbols to the definition within the shared library, if any. Normally, it is possible for a program linked against a shared library to override the definition within the shared library. This option is only meaningful on ELF platforms which support shared libraries.

但请注意,如果库的某些部分依赖于符号插入,它将崩溃。我建议使用不需要导出的隐藏函数(通过用 __attribute__((visibility("hidden"))) 注释它们)或通过 hidden aliases 调用它们(专门设计用于在受控制的时尚)。

如果声明test1()隐藏(__attribute__((__visibility__("hidden"))),则直接跳转

现在 test1() 可能不会在其源翻译单元中定义为隐藏,但我相信这种差异不会造成任何伤害,除了 C 语言保证 &test1 == &test1 可能会在运行时,如果其中一个指针是通过隐藏引用获得的,另一个是通过 public 指针获得的(public 引用可能是通过预加载插入的,或者是查找范围中当前指针之前的 DSO,而隐藏的引用(导致直接跳转)有效地防止了任何类型的插入)

处理这个问题的更合适的方法是为 test1() 定义两个名称——一个 public 名称和一个 private/hidden 名称。

在 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