我可以通过编程方式更改全局偏移表/ GOT或程序链接表/ PLT吗?

时间:2016-11-10 11:40:51

标签: c gcc assembly ld elf

某些特定于平台的功能(如SSE或AVX)的可用性可以在运行时确定,如果不想为不同的功能编译和发送不同的对象,这非常有用。

以下代码允许我检查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")));

在我的图书馆或程序的加载时间内,我想使用has_avx检查AVX的可用性,并根据检查点的结果将do_something符号设置为{{1} }。

如果我可以将_do_something_avx符号的初始版本指向自我修改函数,使用do_something检查AVX的可用性并将其替换为has_avx,那就更好了。或_do_something_basic

理论上这应该是可能的,但是如何以编程方式找到PLT / GOT的位置?是否有ELI加载器提供的ABI / API,例如: ld-linux.so.2,我可以用它吗?我是否需要链接描述文件来获取PLT / GOT位置?如果我获得指向它的指针,我甚至可以写入PLT / GOT安全注意事项呢?

也许某个项目已经完成了这个或者已经非常相似的事情了。

我完全清楚,解决方案将是高度特定于平台的,但由于我已经不得不处理低级平台特定的细节,例如指令集的功能,这是细

3 个答案:

答案 0 :(得分:5)

正如其他人所建议的那样,您可以使用特定于平台的libs版本。或者,如果您坚持使用Linux,则可以使用(相对)新的IFUNC relocations,它可以完全按照您的要求进行操作。

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

答案 1 :(得分: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 ++模板可以帮助保持打字状态。

答案 2 :(得分:0)

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