[英]Can I change the Global Offset Table/GOT or Procedural Linkage Table/PLT programmatically?
一些特定於平台的功能的可用性,例如 SSE 或 AVX,可以在運行時確定,這非常有用,如果不想為不同的功能編譯和發布不同的對象。
例如,以下代碼允許我檢查 AVX 並使用提供cpuid.h
標頭的 gcc 進行編譯:
#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
符號指向_do_something_avx
。
更好的是,如果我可以將do_something
符號的初始版本指向一個自修改函數,該函數使用has_avx
檢查 AVX 的可用性並用_do_something_basic
或_do_something_avx
替換自身。
理論上這應該是可能的,但是如何以編程方式找到 PLT/GOT 的位置? 是否有 ABI/API 提供了 ELF 加載程序,例如 ld-linux.so.2,我可以使用它? 我是否需要鏈接描述文件來獲取 PLT/GOT 位置? 出於安全考慮,如果我獲得指向它的指針,我什至可以寫入 PLT/GOT 嗎?
也許某個項目已經完成了這個或非常相似的事情。
我完全清楚,該解決方案將是高度特定於平台的,但是由於我已經不得不處理低級特定於平台的細節,例如指令集的功能,所以這很好。
正如其他人所建議的那樣,您可以使用特定於平台的庫版本。 或者,如果您可以堅持使用 Linux,則可以使用(相對)新的IFUNC 重定位,它們完全符合您的要求。
編輯:正如塞巴斯蒂安所指出的,其他平台(FreeBSD、Android)似乎也支持 IFUNC。 但是請注意,該功能並未廣泛使用,因此可能會有一些粗糙的邊緣。
完成您所要求的一種簡單方法是使用您自己的函數指針,而不是修改 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
? 生成與位置無關的代碼時,使用全局偏移表而不是過程鏈接表發出函數調用。 所以你在 GOT 上只有一跳。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.