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

考虑以下function:

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

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

 test2: jmp test1 

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

 test2: jmp test1@PLT 

然而,对于与位置无关的代码并不严格需要,如果我不想支持,可以省略。 如有必要,链接器无论如何都会将跳转目标重写为PLT符号。

如何在不更改源代码且不使编译代码不适合共享库的情况下,使函数调用直接转到目标而不是通过PLT显式转换?

如果声明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链接器标志:

创建共享库时,将对全局符号的引用绑定到共享库中的定义(如果有)。 通常,链接到共享库的程序可以覆盖共享库中的定义。 此选项仅在支持共享库的ELF平台上有意义。

但请注意,如果库的某些部分依赖于符号插入,它将会中断。 我建议使用隐藏不需要导出的函数(通过设置隐藏的可见性)或通过隐藏的别名调用它们(专门设计为以受控方式执行无PLT的库内调用)。