考虑以下功能:
extern void test1(void);
extern void test2(void) {
test1();
}
这是gcc在amd64 Linux上没有-fpic
生成的代码:
test2:
jmp test1
当我使用-fpic
进行编译时,gcc显式调用PLT来启用符号插入:
test2:
jmp test1@PLT
然而,对于与位置无关的代码并非严格要求,如果我不想支持,可能会被遗漏。如有必要,链接器无论如何都会将跳转目标重写为PLT符号。
如果不更改源代码并且不使编译代码不适合共享库,我怎样才能直接将函数调用转移到目标而不是通过PLT显式转换?
答案 0 :(得分:2)
如果您无法更改源代码,可以使用大锤:-Bsymbolic链接器标记:
创建共享库时,将对全局符号的引用绑定到 共享库中的定义(如果有)。通常,这是可能的 对于链接到共享库的程序来覆盖定义 在共享库中。此选项仅对ELF平台有意义 它支持共享库。
但请注意,如果库的某些部分依赖于符号插入,它将会中断。我建议使用隐藏不需要导出的函数(通过设置隐藏的可见性)或通过隐藏的别名调用它们(专门设计为以受控方式执行无PLT的库内调用)。 / p>
答案 1 :(得分:2)
如果您声明test1()
隐藏(__attribute__((__visibility__("hidden")))
,则跳转将是直接的。
现在test1()
可能没有在其源代码翻译单元中被定义为隐藏,但我认为除了C语言保证&test1 == &test1
在运行时可能会被破坏时,这种差异不会产生任何伤害其中一个指针是通过隐藏引用获得的,一个是通过公共引用获取的(公共引用可能是通过预加载或在查找范围中当前的DSO之前插入的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语言的保证,而且它不具备别名的粒度。