优化:在没有PLT / GOT的情况下构建共享库

时间:2012-12-03 13:51:38

标签: optimization gcc shared-libraries

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

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

gcc是4.5.2

更新:实际上我认为那是因为makefile中还剩下了-O0。所以现在这不是什么大问题,但我仍然希望对-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避免吵闹吗?
  2. (相关)通过,也许,没有-fPIC编译 - 会有什么缺点?

3 个答案:

答案 0 :(得分:1)

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

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

要将所有符号设为私有,您可以使用-fvisibility = hidden选项。还记得使用属性((visibility(“default”))将CreateObject方法标记为公共方式。)

答案 1 :(得分:0)

在您的功能中使用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进行编译和链接,例如在CXX= g++ -flto中使用Makefile

答案 2 :(得分:0)

  

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

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