我有一个编译的ELF文件libfoo.so,它导出一些类方法,例如:
struct Bar {
void f();
void g();
};
我知道这些类的确切声明,但是定义被编译为.so。我使用与.so相同的编译器(gcc> = 7),因此名称改写和ABI匹配。这意味着,如果将上述声明添加到代码中,则可以直接调用在libfoo.so中实现的方法。
但是,我不想用顶级libfoo东西污染我的 namespace (并且我不控制libfoo源)。所以我想在
struct Bar
中声明namespace foo {}
。现在,一旦执行此操作,名称更改将不再匹配,并且加载程序将无法找到有问题的功能。我不想为所有这些对象编写代码,因为a)有成千上万个b)导致虚函数和析构函数出现问题。
我正在考虑使用
objcopy
并在libfoo.so中重命名所有导出的符号,但是希望这里有更好的解决方案。libfoo和我的代码都使用C++ 14(可以移至C++ 17)和gcc 7(可以移至更高版本)。在Linux上针对64位ELF进行了编译。
最佳答案
最终结果比预期的要复杂得多,但我确实做到了。如果libfoo.so
具有函数Bar::f()
(被混合为_ZN3Bar1fEv
),并且我想定义foo::Bar::f()
(_ZN3foo3Bar1fEv
),则首先需要在libfoo.so中找到函数的相对偏移量。使用nm
,您将获得类似以下内容的信息:
0000000000abcd00 T _ZN3Bar1fEv
我有一个脚本可以解析此输出,并生成如下汇编代码:
; Export start of the translation space
.global START
START:
nop
.global _ZN3foo3Bar1fEv ; Desired target name
_ZN3foo3Bar1fEv:
movq $0x0000abcd12345678, %rax ; arbitrary constant
addq $0xabcd00, %rax ; offset from libfoo
pushq %rax
ret
; same for every other needed symbol
; Export end of the translation space
.global END
END:
nop
这定义了一个具有所需名称的“裸”函数,该函数将一个任意常量添加到偏移量并跳转到该偏移量。额外的跳跃是我可以忍受的最佳表现。
处理ASLR需要任意常量-
0xabcd00
是基本偏移量,但是将libfoo.so加载到某个基本地址(例如0x04000000
)时,函数的实际地址(即是否需要函数指针)是0x04abde00
。生成asm代码时,我使用了一个临时常量,该常量将在加载后进行修复:const uintptr_t offset = 0xabcd00; // Expected offset in libfoo.so
uintptr_t real = (uintptr_t) dlsym(RTLD_DEFAULT, "_ZN3Bar1fEv"); // real address with ASLR
uintptr_t aslr_base = real - offset; // This is the actual base to be applied.
// Search everything between START and END and update the arbitrary constant
extern "C" void START(); extern "C" void END();
uint8_t *p = (uint8_t*)&START;
uint8_t *end = (uint8_t*)&END;
mprotect(ALIGN_TO_PAGE(p), end-p+PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC); // mark the memory as writable
// update all instances of the arbitrary constant
while (p != end)
{
if (*(uintptr_t*)p == 0x0000abcd12345678)
*(uintptr_t*)p = aslr_base;
p++;
}
然后,您可以恢复内存保护。通过仅使用
rax
和push
进行计算,并使用mov
进行间接寻址,可以将asm优化为不使用rsp
,但这适用于我的情况。什么不起作用
objcopy
允许重新定义或注入(inject)静态符号,但不支持编辑动态符号表。我发现的任何工具都没有。 PROVIDE(alias = real)
),但前提是在链接时定义了目标。您不能为动态符号创建别名。 -fPIC
编译所有代码。这样就无需修改代码。 关于c++ - 将类从共享库导入特定的 namespace ,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/57738060/