2016-11-10 50 views
5

可以在运行时确定某些特定于平台的功能(如SSE或AVX)的可用性,如果不想为不同的功能编译和发送不同的对象,这非常有用。我可以以编程方式更改全局偏移表/ GOT或程序链接表/ PLT吗?

例如下面的代码允许我检查AVX和用gcc编译,它提供了cpuid.h头:

#include "stdbool.h" 
#include "cpuid.h" 

bool has_avx(void) 
{ 
    uint32_t eax, ebx, ecx, edx; 
    __get_cpuid(1, &eax, &ebx, &ecx, &edx); 
    return ecx & bit_AVX; 
} 

相反乱丢与运行时检查,如上面的代码的,其重复执行这些检查很慢并且引入了分支(检查可以被缓存以减少开销,但是会有分支),我想我可以使用动态链接器/加载器提供的基础结构。

在ELF平台上调用具有外部链接的函数已经是间接的,并且要通过程序链接表/ PLT和全局偏移表/ GOT。

假设有两个内部功能,基本_do_something_basic,总是和某种方式优化版本_do_something_avx,它使用AVX。我可以将它导出一个通用do_something符号,别名基本的添加:

static void _do_something_basic(…) { 
    // Basic implementation 
} 


static void _do_something_avx(…) { 
    // Optimized implementation using AVX 
} 

void do_something(…) __attribute__((alias("_do_something_basic"))); 

在我的图书馆或程序的加载时间,我想检查AVX的可用性使用has_avx并根据结果一次检查点do_something符号到_do_something_avx

更妙的是,如果我能点do_something符号的最初版本,使用has_avx检查AVX的可用性,并与_do_something_basic_do_something_avx替代本身就是一种自我调节功能。

理论上这应该是可能的,但我怎样才能以编程方式找到PLT/GOT的位置?是否有ELF加载程序提供的ABI/API ld-linux.so.2,我可以用这个吗?我需要链接脚本来获取PLT/GOT位置吗?如果我得到一个指向它的指针,那么我怎么样才能写入PLT/GOT呢?

也许有些项目已经做过或者非常类似的事情了。

我完全意识到,该解决方案具有很高的平台特定性,但由于我已经不得不处理底层特定于平台的细节,例如指令集的特性,所以这很好。

+0

据我所知,Solaris通过在启动时运行脚本来解决此问题,该脚本交换受影响的库的硬链接以匹配硬件可以执行的操作。 – fuz

+0

Linux的动态链接器/加载器的[ld.so(8)手册页](http://man7.org/linux/man-pages/man8/ld.so.8.html#NOTES)也提到了特殊路径对于硬件功能而言,但我并不是说任何Linux发行版都实际使用这个功能,而且这在x86-64上不可用,并且只支持一些功能,尤其是不支持AVX。但一个更基本的问题是,你将不得不生成多个版本的库,而不是只有一个版本。 –

+0

创建单独版本的库,然后使用'dlopen'加载相应的版本。不必自己惹PLT。看到这个[回答一个例子](http://stackoverflow.com/a/26037586/547981)。 – Jester

回答

5

正如其他人所建议的,您可以使用平台特定版本的库。或者,如果您坚持使用Linux,您可以使用(相对)新的IFUNC relocations,它们完全符合您的要求。

编辑:正如塞巴斯蒂安所指出的,IFUNCs似乎也受到其他平台(FreeBSD,Android)的支持。但请注意,该功能并没有被广泛使用,因此可能会有一些粗糙的边缘。

+0

'ifunc'正是我正在寻找的,但这真的是Linux特有的?它在我看来是GNU和ELF specfic,所以它至少应该在其他一些平台上工作。 –

+0

谢谢,我有不同的印象。我已经更新了答案。 – yugr

0

一个简单的方法来做你所要求的是使用你自己的函数指针,而不是在PLT中修改它们。

例如:

extern void (*do_something)(...); 

void 
_do_something(...) { 
    if (has_avx()) { 
     do_something = _do_something_avx; 
    } else { 
     do_something = _do_something_basic; 
    } 
    do_something(...); 
} 

void (*do_something)(...) = _do_something; 

虽然这是很麻烦,如果你有很多的这些功能,做这种方式简化版,需要任何特殊的编译器或链接功能。 (尽管如果你需要在读写指针不是原子的平台上使用函数来保证线程安全,你需要以某种方式使它们成为原子,然而,在x86平台上这不是问题。)如果你有很多这些函数,宏或C++模板可以帮助保持输入。

0

你为什么不尝试gcc选项“-mprefergot”? 当生成位置无关的代码时,使用全局偏移表而不是程序链接表发出函数调用。 所以你只有一个跳转到GOT。

+0

只使用GOT而不是PLT + GOT不能解决我的问题,它只是移动它:如何以编程方式获得GOT地址? –