【问题标题】:dlopen from memory?从内存中打开?
【发布时间】:2011-06-30 12:48:14
【问题描述】:

我正在寻找一种直接从内存加载生成的目标代码的方法。

我知道如果我将它写入文件,我可以调用 dlopen 来动态加载其符号并链接它们。但是,考虑到它从内存中开始,写入磁盘,然后由 dlopen 重新加载到内存中,这似乎有点绕道。我想知道是否有某种方法可以动态链接内存中存在的目标代码。据我所知,可能有几种不同的方法可以做到这一点:

  1. 诱使 dlopen 认为您的内存位置是一个文件,即使它永远不会离开内存。

  2. 找到其他一些系统调用来做我正在寻找的事情(我认为这不存在)。

  3. 找到一些可以直接链接内存中代码的动态链接库。显然,这有点难用谷歌搜索,因为“动态链接库”提供了有关如何动态链接库的信息,而不是执行动态链接任务的库。

  4. 从链接器中提取一些 API,并在其代码库中创建一个新库。 (显然这对我来说是最不理想的选择)。

那么其中哪些是可能的?可行的?你能指出我假设存在的任何事情吗?还有其他我没想到的方法吗?

【问题讨论】:

    标签: c dynamic-linking ld dlopen


    【解决方案1】:

    我需要一个解决方案,因为我有一个没有文件系统的可编写脚本的系统(使用数据库中的 blob)并且需要加载二进制插件来支持一些脚本。这是我想出的适用于 FreeBSD 但可能不可移植的解决方案。

    void *dlblob(const void *blob, size_t len) {
        /* Create shared-memory file descriptor */
        int fd = shm_open(SHM_ANON, O_RDWR, 0);
        ftruncate(fd, len);
        /* MemMap file descriptor, and load data */
        void *mem = mmap(NULL, len, PROT_WRITE, MAP_SHARED, fd, 0);
        memcpy(mem, blob, len);
        munmap(mem, len);
        /* Open Dynamic Library from SHM file descriptor */
        void *so = fdlopen(fd,RTLD_LAZY);
        close(fd);
        return so;
    }
    

    显然代码缺少任何类型的错误检查等,但这是核心功能。

    ETA:我最初假设 fdlopen 是 POSIX 是错误的,这似乎是 FreeBSD 主义。

    【讨论】:

    • 人们似乎可以使用普通的 dlopen here
    • @yugr 你的建议正是提问者已经驳回的小事。
    • 不完全是,/run/shm 文件永远不会写入磁盘。
    • @yugr /run/shm 不是 POSIX,它是一种 Linux 主义,没有它,函数就会退回到只是写给 /tmp。无论文件是否进入磁盘(/tmp 在某些系统上可能是 ramdisk),您仍然必须与文件系统交互,有权创建它,控制其他人是否可以访问它,确保正确取消链接当你完成(或崩溃)。您为什么不在提案中发布答案,让人们对其进行评论和投票?
    • 好吧,我不认为这个小补充真的值得单独回答。同意 Linux 主义,但 OP 没有明确提到他需要一个符合 POSIX 的解决方案。至于文件系统 - 再次,好点,但我认为 OP 更关心实际的磁盘访问(“写入磁盘,然后由 dlopen 重新加载到内存中”)。
    【解决方案2】:

    我不明白您为什么要考虑 dlopen,因为这将需要更多不可移植的代码来在磁盘上生成正确的对象格式(例如 ELF)以供加载。如果您已经知道如何为您的架构生成机器代码,只需 mmap 内存和 PROT_READ|PROT_WRITE|PROT_EXEC 并将您的代码放在那里,然后将地址分配给函数指针并调用它。很简单。

    【讨论】:

    • 如果要开发的人不止几个,这似乎不是一个很好的方法。此外,您注入的代码是否不需要解析自己的函数指针,并且是 PIC 等?看起来就像编译一个 .so 然后能够dlopen 这样会好很多。
    • 我想这取决于您生成的代码类型。我在考虑虚拟机的 JIT 代码/模拟器的 dynrec,其中不会有任意调用和对调用程序中数据的访问。
    • 这确实是处理相对简单的自包含代码的好方法(另外:在一天结束时,您真正希望动态生成的代码多久能够进行任意调用?)
    • R.. 我当然考虑过这一点,但这也需要一个链接器,因为我正在使用的编译器的输出是目标代码,而不是机器代码。这就是为什么我有建议 3 和 4 的原因:如果我这样做了,我需要找到某种跨平台库来动态链接内存。但如果这不存在,那么这根本不是解决方案。
    • @Stephen Canon,实际上这在某些业务领域是非常常规的要求,并且在 Windows 上经常发生。然而,它是你写一次然后继续重复使用的类型。
    【解决方案3】:

    除了写出文件然后用dlopen()再次加载它之外,没有标准的方法。

    您可能会在您当前的特定平台上找到一些替代方法。由您决定这是否比使用“标准和(相对)便携”方法更好。

    由于首先生成目标代码是特定于平台的,因此其他特定于平台的技术对您来说可能并不重要。但这是一个判断要求 - 无论如何取决于是否存在非标准技术,这是相对不可能的。

    【讨论】:

    • 管道也算作 filedesktriptor 吗?所以你不能喜欢...管道到 dlopen() 吗?
    • @imacake - 它是一个文件描述符,但不是你可以查找或映射的。
    • “除了写出文件然后再次加载之外,没有标准的方法”应该更正为“您可以写出文件并加载它”,参见 R. . 回答。
    • @Simon:如果要加载的代码不需要调用任何其他函数(完全自包含),您可以直接使用mmap(),它可能会工作。如果要加载的代码调用其他函数,则必须通过某种方法解析这些符号的地址。这通常由dlopen() 为您完成。如果您将 dlopen() 短路,那么您作为代码创建者有责任确保您已将 ASLR 考虑在内,并在代码中的正确位置拥有正确的函数地址。
    • 一个需要注意的小问题:在 Linux 上,我发现如果我想让一个程序写出一个 .so,dlopen 它,dlsym 从中写出,然后再写出另一个 .so。所以,dlopen 和 dlsym ,那么这两个 .so 文件名必须不同。
    【解决方案4】:

    你不需要加载内存中生成的代码,因为它已经在内存中了!

    但是,您可以 - 以不可移植的方式 - 在内存中生成机器代码(前提是它在内存段 mmap 中 - 带有PROT_EXEC 标志)。

    (在这种情况下,不需要“链接”或重定位步骤,因为您生成的机器代码具有确定的绝对或相对地址,尤其是调用外部函数)

    存在一些这样做的库:在 x86x86-64 下的 GNU/Linux 上,我知道 GNU Lightning(它可以快速生成运行缓慢的机器代码)、DotGNU LibJIT(生成中等质量的代码)和LLVM & GCCJIT(能够在内存中生成相当优化的代码,但需要时间来发出它)。 LuaJit 也有一些类似的设施。自 2015 年以来,GCC 5 有一个 gccjit 库。

    当然,您仍然可以在文件中生成 C 代码,派生编译器以将其编译为共享对象,然后 dlopen 该共享对象文件。我在 GCC MELT 中这样做,这是一种扩展 GCC 的领域特定语言。它在实践中确实很有效。

    附录

    如果编写生成的 C 文件的性能是一个问题(不应该,因为编译 C 文件比编写它慢得多)考虑为此使用一些 tmpfs 文件系统(可能在 /tmp/ 中通常是 Linux 上的 tmpfs 文件系统)

    【讨论】:

    • 这个答案不值得投票。它完全误解了提问者的想法。
    【解决方案5】:

    我们在 Google 实施了一种方法来做到这一点。不幸的是,上游 glibc 未能理解这种需求,因此它从未被接受。带有补丁的feature request 已停止。它被称为dlopen_from_offset

    dlopen_with_offset glibc code 在 glibc google/grte* 分支中可用。但是没有人应该享受修改自己的 glibc。

    【讨论】:

      猜你喜欢
      • 2010-12-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-29
      • 1970-01-01
      • 2014-05-26
      • 2010-09-21
      相关资源
      最近更新 更多