36

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

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

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

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

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

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

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

4

5 回答 5

14

我需要一个解决方案,因为我有一个没有文件系统的可编写脚本的系统(使用数据库中的 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 主义。

于 2017-02-13T04:48:14.120 回答
10

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

于 2011-02-19T22:39:52.607 回答
8

除了写出文件然后用dlopen().

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

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

于 2011-02-19T21:36:20.993 回答
2

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

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

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

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

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

附加物

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

于 2011-10-24T19:23:56.940 回答
1

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

dlopen_with_offset glibc 代码在 glibc google/grte* 分支中可用。但是没有人应该喜欢修改自己的 glibc。

于 2021-08-26T22:43:57.617 回答