malloc内存中的C函数代码

时间:2017-05-05 01:44:31

标签: c memory heap heap-memory

有没有办法使用malloc内存空间然后在C空间内复制功能代码?

这个问题在实践中可能没有意义。我出于好奇而问这个问题,这样我就可以更好地理解c及其底层实现是如何工作的。

如果可以将代码复制到堆中,那么这里是后续问题:

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

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

2 个答案:

答案 0 :(得分:3)

在大多数机器上都可以使用它(或类似的东西),但是你使用的技术是系统特定的 - 没有标准的C或C ++方法。

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

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

最后,虽然自修改代码可能是人们在考虑您的问题时可能会想到的第一件事,但是相关技术的合理合法使用可能是在本机代码中,只是在时间编译系统。

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

答案 1 :(得分:2)

C标准(例如C11,读n1570)或C ++标准(例如C ++ 11,C ++ 14并注意到它们有lambda expressions和{{3阅读更多关于std::function ...)没有定义什么是函数地址或指针(它只定义调用这样一个地址的函数,然后函数指针应该指向现有的函数并没有标准方法在运行时动态构建新的方法)。在某些系统(纯closures)中,函数位于与C堆不同的地址空间中(并且在这些系统上执行malloc中的任何内容 - 编辑堆没有意义且是Harvard architectures) 。因此C11标准禁止将数据指针转换为函数指针,反之亦然。

那么,对你的问题

  

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

答案通常是 NO (但在某些系统上,您可以在运行时生成代码,请参阅下文)。

但是,在台式机或笔记本电脑或服务器PC或平板电脑(运行常见操作系统,如Linux,Windows,MacOSX,Android)上,您通常会有一个undefined behavior而且(对于给定的Von Neumann architecture )单个process共享代码和数据(特别是使用malloc获得的堆数据)。该虚拟地址空间以virtual address space组织,每个页面都有自己的pages。详细了解memory protectioncomputer architectureinstruction sets s。堆分配的数据通常是MMU不可执行的。

NX bit起着至关重要的作用。您需要阅读有关操作系统的整本书,例如operating system

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

您可以使用Operating Systems : Three Easy Pieces的某些库,例如JIT compilationasmjitlibgccjit(或LLVM或GNU libjit)和它们生成可执行的代码。

您还可以在某些lightning上使用动态loading techniques;在POSIX系统上查看plugin& dlopen(可以用于"从加载的插件创建"函数地址,超出C11标准允许的范围)。一种可能的方法是在临时文件中生成一些C代码,将其编译为插件,以及生成插件的dlopen。有关详细信息,请参阅dlsym

在Linux上,您可以使用this answer和相关mmap(2)(用于在system calls中实施malloc,以及C standard library)来更改您的虚拟地址空间和dlopen(3)系统调用以更改保护(基于页面)。因此,如果要显式复制或生成某些功能代码,则必须进入可执行页面(PROT_EXEC)。

请注意,由于mprotect(2)问题(以及机器代码中的relocation或绝对地址),复制机器代码并不容易。将memcpy给定功能代码的字节复制到某个可执行页面通常会毫无痛苦地工作:通常CALLJUMP offsets正在使用 PC相对寻址,因此在不改变偏移的情况下复制它们将无法正常工作。

  

如果可以将代码复制到堆

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

  

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

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