【问题标题】:Optimize: build shared libraries without PLT/GOT优化:构建不带PLT/GOT的共享库
【发布时间】:2012-12-03 13:51:38
【问题描述】:

我有一些库,除了说“CreateObject”之外不公开任何功能。尽管如此,它们的所有函数都是间接调用的,所以我在性能报告中看到高达 1.65% 的时间花费在 __i686.get_pc_thunk.bx 中。函数(类方法)被调用了 1.6 亿次,它们在共享库内部,即未公开。

我想知道是否可以在不重定位的情况下编译内部方法 - 即使用相对偏移或类似的东西。

gcc 是 4.5.2

更新:实际上我认为这是因为 -O0 留在了 makefile 中。所以现在这没什么大不了的,但我仍然想对 -O0 做同样的事情,因为它可以减少探查器的“垃圾”。我想知道执行此操作的 -O2 “真实”选项是什么。

UPDATE2:嗯,不是 -O2,可能是 --dynamic-list 降低了 pc_thunk 性能,但它仍然存在......所以甚至不确定 --dynamic-list 是否真的有帮助.隐藏符号是否仍应包含间接 thunk,是否正确?

UPDATE3:我创建了一个测试项目,对于内部库函数,我将属性可见性设置为隐藏,我使用 gcc 4.7 和 -O2 进行编译,并启用了 LTO,我将 --dynamic-list 传递给链接器,但其中没有内部函数,并且尽管如此,对 get_pc_thunk 的调用仍然存在。

这是测试共享库中的代码:

#include <stdio.h>

__attribute__((visibility("hidden"), noinline)) void lib1f2()
{
    puts("I should have PLT disabled");
}

void lib1f()
{
    puts("I'm lib1");
    lib1f2();
}

在 gdb 中,我仍然在 lib1f2 中看到 thunk。

有趣的是,使用 -fwhole-program lib1f2 内联到主可执行文件中仍然包含对 thunk 的调用。

UPDATE4:好的,我快接近了(意识到我很笨),程序(和上面的代码)使用数据,即使它只是一个 const 字符串,所以它需要 GOT 调用。所以现在的问题是:

  1. 不过,我可以避免 GOT 的 thunk 吗?
  2. (相关)通过,也许,在没有 -fPIC 的情况下编译 - 会有什么缺点?

【问题讨论】:

    标签: optimization gcc shared-libraries


    【解决方案1】:

    1) 不过,我可以避免 GOT 的重击吗?

    我认为不会。至少不在 i686 上。问题是代码可以自动进行相对跳转......或者更确切地说,x86 上的所有跳转都是相对的,除了间接跳转 IIRC。另一方面,没有办法索引相对于当前程序计数器的数据。这个问题实际上在 x86_64 中得到了解决,因为有一个新的指令指针相对寻址可以完全用于这种情况。

    您的测试,使用 gcc -fPIC -shared -O2 -flto 编译

    在 32 位上:

    00000530 <lib1f2.2321>:
    push   %ebx
    call   52b <__x86.get_pc_thunk.bx>
    add    $0x1aca,%ebx
    sub    $0x18,%esp
    lea    -0x1a67(%ebx),%eax
    mov    %eax,(%esp)
    call   3f0 <puts@plt>
    add    $0x18,%esp
    pop    %ebx
    ret
    00000560 <lib1f>:
    push   %ebx
    call   52b <__x86.get_pc_thunk.bx>
    add    $0x1a9a,%ebx
    sub    $0x18,%esp
    lea    -0x1a4c(%ebx),%eax
    mov    %eax,(%esp)
    call   3f0 <puts@plt>
    add    $0x18,%esp
    pop    %ebx
    jmp    530 <lib1f2.2321>
    nop
    

    在 64 位上

    00000000000006b0 <lib1f2.2352>:
    lea    0x2a(%rip),%rdi
    jmpq   590 <puts@plt>
    
    00000000000006c0 <lib1f>:
    lea    0x35(%rip),%rdi
    sub    $0x8,%rsp
    callq  590 <puts@plt>
    xor    %eax,%eax
    add    $0x8,%rsp
    jmp    6b0 <lib1f2.2352>
    

    2) (相关)通过,也许,在没有 -fPIC 的情况下编译 - 会有什么缺点?

    好吧,虽然很尴尬,但我不得不承认我在这里有点困惑。乍一看,我会说共享库是用 -fPIC 编译的。相反,以下两个命令都有效

    gcc -fPIC -m32 -shared -O2 -flto test.c -o test.so
    gcc -m32 -shared -O2 -flto test.c -o test.so
    

    在非-fPIC 情况下,代码也不需要调用get_pc_thunk。诀窍是动态加载器在运行时使用正确的数据地址修复库代码。

    这是一个问题,因为您在避免 thunk 时获得了一些速度,但是您失去了实际共享共享库的能力,因为操作系统必须创建一个包含重定位的库的每个代码页的新副本。另一方面,当使用 GOT 时,只需复制 GOT 页,当许多应用程序链接到同一个库时,大大减少了内存占用。

    有趣的是,在64位模式下无法在非pic模式下编译库,以下命令失败

    gcc -m64 -shared -O2 -flto test.c -o test.so
    

    不过,由于处理器提供了对代码相对寻址的支持,这不是问题。

    【讨论】:

    • 非常详细,谢谢。并不是说它有很多新信息,但它证实了我到目前为止所发现的。
    • 好吧,我实际上认为它确实回答了你的问题。你觉得少了点什么?
    • 找到解决方案的希望并没有消失……但是 ;-)
    【解决方案2】:

    您可能对 GCC 可见性支持感兴趣

    http://gcc.gnu.org/wiki/Visibility

    要将所有符号设为私有,您可以使用 -fvisibility=hidden 选项。还记得使用 attribute ((visibility ("default")))

    将 CreateObject 方法标记为 public

    【讨论】:

    • 我确实使用可见性=隐藏进行编译。不过,我现在会仔细检查...是的。
    【解决方案3】:

    在您的函数中使用visibility attribute,尤其是hidden。你可以定义

     #ifdef __GNUC__
      #define MODULE_VISIBILITY  __attribute__ ((visibility ("hidden")))
     #else
      #define MODULE_VISIBILITY
     #endif
    

    然后声明你的函数,例如与

     extern void MODULE_VISIBILITY my_module_fun(int);
    

    使用最近的(4.6 或 4.7)GCC 编译器,您还可以使用链接时间 optimizations 进行编译和链接,例如在您的Makefile 中使用CXX= g++ -flto

    【讨论】:

    • 你确定 lto 会解决这个问题吗?我尝试使用 gcc 4.6.1,但它在 lto 实现中有错误。所以我必须升级到 4.6.3 才能测试。
    • 我很难解释我们需要 4.7 才能使用它(因为它在所有发行版上都不容易使用)所以我首先更喜欢另一种解决方案,如果没有其他问题,我会回退到 gcc 4.7有帮助。
    • 实际上 lto 并没有帮助,在 4.7 上使用隐藏、动态列表和 lto,即使在小型测试项目中,隐藏 lib-only 函数的间接性仍然存在。
    猜你喜欢
    • 2020-06-20
    • 2020-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-03
    • 2017-12-27
    相关资源
    最近更新 更多