【问题标题】:C function code in malloc'd memorymalloc 内存中的 C 函数代码
【发布时间】:2017-10-03 08:39:54
【问题描述】:

有没有办法 malloc 内存空间,然后在 C 中的空间内复制函数代码?

这个问题在实践中可能没有意义。我出于好奇提出这个问题,以便更好地了解 c 及其底层实现的工作原理。

如果可以将代码复制到堆中,以下是后续问题:

  1. 函数二进制码复制时如何确定大小?

  2. 我们可以使用函数指针来执行代码吗? (代码放在 malloc 的内存中,出于安全原因,这部分内存可能被标记为不可执行,但我不确定)

【问题讨论】:

  • 不。 C++ 不能以这种方式工作。现在,您要解决的真正问题是什么。不,不是关于将函数放入堆中的问题,而是您认为其解决方案涉及将函数放入堆中的真正问题。
  • 有关 C/C++ 的问题请使用“C/C++”标签。为了节省您搜索的时间:没有这样的标签,因为没有语言 C/C++。语言 C 和 C++ 是非常不同的语言。把你打算用的Pikc,去掉不相关的标签!
  • 两种语言都没有堆或段。这些是实现细节。您必须选择您要求的抽象层。而且您不能将函数指针转换为void *
  • 在具有特定编译器的特定环境中,您可能会成功破解完全未定义的行为以在堆上执行代码。除了,也许,为了让它工作的快感,这将是一个无用的练习。
  • 支持@BasileStarynkevitch:您尝​​试解决的实际问题是什么?如果这是一些学术问题,您必须添加更多信息。如果 oyu 有实际问题,你很可能使用了错误的方法。无论哪种方式,您都必须提供更多信息。

标签: c memory heap-memory


【解决方案1】:

C 标准(例如 C11,阅读 n1570)或 C++ 标准(例如 C++11、C++14 并注意它们有 lambda expressionsstd::function;阅读有关 @987654325 的更多信息@ ...) 没有定义什么是函数地址或指针(它只定义了调用这样的地址做了什么,那么函数指针应该指向现有函数并且没有标准 在运行时动态构建新的方法)。在某些系统(纯Harvard architectures)中,函数位于与C 堆不同的地址空间中(并且在这些系统上执行malloc-ed 堆中的任何内容都没有意义,并且是undefined behavior)。因此 C11 标准禁止将函数指针转换为数据指针,反之亦然。

所以,对于你的问题

有没有办法 malloc 内存空间,然后在 C 中将函数代码放入空间中?

答案通常是(但在一些系统上,您可以在运行时生成代码,见下文)。

但是,在台式机或笔记本电脑或服务器 PC 或平板电脑(运行常见的操作系统,如 Linux、Windows、MacOSX、Android)上,您通常有一个 Von Neumann architecture 并且(对于给定的 process)有一个 @ 987654330@ 共享代码和数据(特别是使用malloc 获得的堆数据)。该虚拟地址空间以pages 组织,每个页面都有自己的memory protection。阅读更多关于computer architectureinstruction setsMMUs 的信息。通过NX bit,堆分配的数据通常是不可执行的。

operating system 起着至关重要的作用。你需要阅读整本关于操作系统的书,比如Operating Systems : Three Easy Pieces

(我猜你想在运行时在你的程序中“创建”一些新函数并通过 C 函数指针调用它们;你应该解释原因;我想你正在为 PC 或具有类 Unix 操作系统的平板电脑,实际上是 Linux-x86_64 发行版,但您可以将我的答案调整为 Windows)

您可以为JIT compilation 使用一些库,例如asmjitlibgccjitLLVM(或libjit 或GNU lightning),它们会生成可执行的代码。

您还可以在某些plugin 上使用动态loading techniques;在 POSIX 系统上查看 dlopendlsym (可用于从加载的插件“创建”函数地址,超出 C11 标准允许的范围)。一种可能的方法是在临时文件中生成一些 C 代码,将其编译成插件,然后 dlopen 生成的插件。详情请见this answer

在 Linux 上,您可以使用mmap(2) 和相关的system calls(用于在您的C standard library 中实现malloc,也可以通过dlopen(3))来更改您的虚拟地址空间,以及mprotect(2)更改保护的系统调用(逐页)。所以如果你想显式地复制或生成一些功能代码,它必须进入一个可执行页面(PROT_EXEC)。

请注意,由于relocation 问题(以及offsets 或机器代码中的绝对地址),复制机器代码并不容易。使用memcpy 将给定函数代码的字节复制到某个可执行页面中通常不会没有痛苦:通常CALLJUMP machine instructions 使用PC-relative 寻址,所以复制它们而不改变它们的偏移量是行不通的。

如果可以将代码复制到堆中

不,一般来说不可能;在实践中,它比你想象的要困难得多(即使在 Linux-x86_64 上,我提到的其他方法更可取);如果你想走这条路,你需要关心低级实现细节(指令集、处理器、编译器、calling conventionsABIs、重定位),你的代码将是不可移植和脆弱的。

函数二进制码复制时如何确定大小?

这个问题(以及函数大小的概念)通常没有意义。一些优化编译器能够发出一些在几个 C函数之间共享的机器代码,或者发出几个不连续的机器代码块用于给定的函数(gcc -O2 可能会进行这些优化,请参阅function cloning)。在 Linux 上,您可以使用dladdr(3)(或nmreadelf 程序)来获得ELF 意义上的“符号大小”,但该大小可能意义不大。正如我所解释的,你不能只是字节复制二进制机器代码,你需要重新定位它(某些部分)。

【讨论】:

  • 现代 CPU 使用内部哈佛架构和统一的外部地址空间。微分由 MMU、内存和缓存子系统完成。此外,现代操作系统将代码和数据地址空间分开,因此并不是那么简单。一些件数超过 x86 和 ARM 的嵌入式架构,其中两个是 AVR 和 PIC,也使用哈佛架构。即使对于较小的 MCU,从闪存复制到 RAM 也不容易归档,例如由于内存加密..
  • 应用代码看到一个统一的虚拟地址空间,采用冯诺依曼架构。
  • 这并不意味着代码可以复制到数据空间,更少的复制被执行。它不像今天的哈佛冯诺依曼那么简单。将函数指针转换为 void * 是有充分理由的未定义行为。它有 XY 问题的难闻气味。即使我们忽略所有其他问题,在功能齐全的操作系统上也没有任何用处。
  • 我同意,我在回答中确实说过(最后一段)
  • 我不仅仅指重定位。其他问题之一是缓存。至少您可能需要屏障和刷新/重新加载指令缓存。还有更多。
【解决方案2】:

这(或类似的东西)在大多数机器上都是可能的,但您使用的技术是特定于系统的——没有标准的 C 或 C++ 方法可以做到这一点。

即使弄清楚函数的长度以便复制它也很困难。如果函数在同一个翻译单元中,我认为你不能可靠地做到这一点,因为编译器可能已经完成了你看不到的优化魔法。但是,如果函数位于不同的文件中,那么它的接口可能会更可靠(尽管可能存在链接器魔术,您必须理解和模拟才能实现目标。)

其他问题(在某些系统上)是 malloc 的内存可能无法执行。 (这通常是通过防止执行放置在溢出缓冲区中的代码来提高安全性的情况。)但是,具有可执行保护的系统通常具有备用内存分配功能,可以为您提供一块可以放置可执行代码的内存,以及执行可以转移到哪个位置。实现共享库需要此功能的一些变体。

最后,虽然 自我修改代码 可能是人们在考虑您的问题时可能首先想到的,但合理、合法地使用相关技术可能是在原生代码中,即插即用-time 编译系统。

通过指定要执行此操作的特定操作系统和 CPU,您可能会得到更好的答案。

【讨论】:

  • 在某些微控制器中出现了另一种合法用途,其中 CPU 访问 RAM 比访问闪存快。如果您需要一个函数快速运行,您可以将其复制到 RAM,然后使用函数指针将控制转移到那里,而不是 Flash 中的副本。
  • 这首先调用了 UB,并且很可能无法在现代全尺寸操作系统上运行,以执行 No eXecute 和其他安全功能。更不用说缺少重定位等 WRT MCU:这取决于系统,但通常由启动代码或特殊系统功能完成。通常必须为此使用特殊选项编译该函数。无论如何,在桌面上,强烈建议不要这样做,甚至不考虑(JIT 编译器是一个特殊主题,但上面显然没有)。
  • 这是 2011 年的 Linux 示例:它使用 mmap 分配内存并请求将其标记为可执行。然后它将一些指令代码复制到内存中,通过函数指针将控制转移到内存中,并打印出结果。 burnttoys.blogspot.com/2011/04/… 正如@Olaf 所说,将内存指针转换为函数指针是粗略的,需要调查以查看它是否可以在任何特定机器上工作。
  • 我不知道它在技术上是否是 UB(我不是 C/C++ 标准专家),但必须有可靠的方法在任何操作系统上执行此操作,因为它是一个如果要从辅助存储加载可执行文件并开始执行,任何操作系统都必须能够执行。
  • 操作系统可以使用汇编语言,例如编译器扩展。这是关于应用程序软件,它不是操作系统。如果您回答有关标准行为的问题,阅读和理解标准至关重要。我强烈推荐它!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-24
  • 1970-01-01
  • 2019-10-19
  • 1970-01-01
相关资源
最近更新 更多