【问题标题】:How do I force gcc to call a function directly in PIC code?如何强制 gcc 在 PIC 代码中直接调用函数?
【发布时间】:2016-04-01 10:41:33
【问题描述】:

考虑以下函数:

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?

【问题讨论】:

  • 你的意思是你的库/可执行文件中的调用?这应该是可能的,也许通过定义一个私人别名或其他东西。但是对于仅动态链接的库中函数的调用,我不确定运行时链接器能否解析此类引用。
  • @PeterCordes 如果没有-fpic,链接器会在适当/需要时自动将对符号的引用重写为 PLT 引用。这就是我想要的行为,即编译器生成对所有函数的正常调用,而链接器重写调用到另一个共享对象的调用。
  • -fno-semantic-interposition 做你想做的事吗?另见stackoverflow.com/questions/34102989/…。这个问题是不是其中任何一个问题的重复?
  • @PeterCordes 似乎没有什么不同,但我记住了这个选项。
  • 未来读者:另请参阅stackoverflow.com/questions/10849308/…,了解在跨共享对象边界调用时试图避免 PLT。 (例如从程序到库)。

标签: c gcc x86 position-independent-code plt


【解决方案1】:

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

【讨论】:

  • “破坏了 C 语言的保证”——哪一个?
  • @yugr 感谢您的评论。 IDK 为什么我有最后一段。刚刚尝试过, -fno-semantic-interposition 似乎并没有消除进行实际函数调用的PLT间接。我暂时把它删了。相信其余的应该没问题,尤其是。关于同一对象同时具有隐藏和可见别名的部分。
【解决方案2】:

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

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

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

【讨论】:

    猜你喜欢
    • 2016-12-11
    • 2012-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-12
    相关资源
    最近更新 更多