【问题标题】:Import classes from shared object into a specific namespace将共享对象中的类导入特定命名空间
【发布时间】:2020-01-04 09:01:54
【问题描述】:

我有一个已编译的 ELF 文件 libfoo.so,它导出了一些类方法,例如:

struct Bar {
   void f();
   void g();
};

我知道这些类的确切声明,但定义被编译到 .so 中。我使用与 .so 相同的编译器 (gcc >= 7),因此名称修饰和 ABI 匹配。这意味着如果我将上述声明添加到我的代码中,我将能够直接调用 libfoo.so 中实现的方法。

但是,我不想用顶级 libfoo 的东西污染我的命名空间(而且我不控制 libfoo 源)。所以我想在namespace foo {} 中声明struct Bar。现在,一旦我这样做了,名称修饰就不再匹配,加载程序将无法找到有问题的函数。

我不想为所有这些都写 thunk,因为 a) 有数千个 b) 它会导致虚函数和析构函数出现问题。

我正在考虑使用 objcopy 并重命名 libfoo.so 中的所有导出符号,但希望这里有更好的解决方案。

libfoo 和我的代码都使用 C++14(可以移动到 C++17)和 gcc 7(可以移动到以后)。在 Linux 上为 64 位 ELF 编译。

【问题讨论】:

  • 编译后的代码将完全损坏、不可信和//或不稳定。并非不可能,但我认为您必须采取不同的方法
  • 你能详细说明@memosdp 吗?编译后的 libfoo.so 不应该关心可执行文件的其他部分做什么,或者它的导出符号的名称是什么。它是完全独立的,即使 99% 的符号被完全剥离也可以运行。
  • 它仍然是一个库,不仅如此,它还是一个so,意思是shared object。你将如何重构重定位?我认为您必须深入了解elflib 才能做到这一点,而不仅仅是objcopy
  • 我根本不会更改重定位。 objcopy 只会重命名符号,因为它们在外部中看到。在内部,lib 使用保持不变的相对偏移量。 lib 的所有代码都保持不变,只是符号名称(即nm 的输出)得到了更多的修饰 - 但这从未被 lib 本身使用,它仅适用于 lib 的消费者(和调试器,打印堆栈跟踪等)
  • 问题和解决方案都非常符合C++的精神。干得好。

标签: c++ gcc elf


【解决方案1】:

它最终比预期的要复杂得多,但我确实做到了。如果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++;
}

然后您可以恢复内存保护。可以通过仅使用 pushmov 以及使用 rsp 的间接寻址进行计算来优化 asm 以不使用 rax,但这适用于我的情况。


什么不起作用

  • objcopy 允许重新定义或注入 static 符号,但不支持编辑动态符号表。我找到的工具都没有。
  • 链接器脚本允许定义别名 (PROVIDE(alias = real)),但前提是在链接时定义了目标;您不能为动态符号创建别名。
  • 从全局变量中获取 ASLR 偏移量不起作用 - 链接器一直要求使用 -fPIC 编译所有代码,确实如此。这样就无需修改代码了。

【讨论】:

    猜你喜欢
    • 2012-01-11
    • 1970-01-01
    • 1970-01-01
    • 2021-11-21
    • 1970-01-01
    • 1970-01-01
    • 2018-08-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多