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

Can I change the Global Offset Table/GOT or Procedural Linkage Table/PLT programmatically?

某些 platform-specific 功能的可用性,例如 SSE 或 AVX,可以在运行时确定,这非常有用,如果不想编译和发布不同的 objects功能。

例如,下面的代码允许我检查 AVX 并使用 gcc 进行编译,它提供 cpuid.h header:

 #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;
 }

而不是像上面那样用运行时检查乱扔代码,重复执行检查,速度很慢并引入分支(可以缓存检查以减少开销,但仍然会有分支),我我想我可以使用动态 linker/loader.

提供的基础设施

在带有 ELF 的平台上调用具有外部链接的函数已经是间接的,并且通过程序链接 Table/PLT 和全局偏移 Table/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")));

在我的库或程序的 load-time 期间,我想使用 has_avx 检查一次 AVX 的可用性,并根据检查点的结果将 do_something 符号添加到_do_something_avx.

如果我可以将 do_something 符号的初始版本指向一个 self-modifying 函数,该函数使用 has_avx 检查 AVX 的可用性并用 _do_something_basic_do_something_avx.

理论上这应该是可行的,但我如何以编程方式找到 PLT/GOT 的位置?是否有 ABI/API 提供 ELF 加载程序,例如ld-linux.so.2,我可以用它吗?我是否需要链接描述文件来获取 PLT/GOT 位置?出于安全考虑,如果我获得指向它的指针,我什至可以写入 PLT/GOT 吗?

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

我完全知道,解决方案将是高度 platform-specific,但由于我已经不得不处理 low-level platform-specific 细节,例如说明的功能设置,这样就可以了

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

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

实现您所要求的一种简单方法是使用您自己的函数指针,而不是修改 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++ 模板可以帮助减少输入。

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