7

考虑以下函数:

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?

4

2 回答 2

3

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

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

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

于 2016-11-10T16:15:43.550 回答
3

如果你声明test1()hidden ( __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上的实时示例。

于 2017-06-21T06:45:41.763 回答