是否可以将函数加载到一些分配的内存中并从那里运行它?
Posted
技术标签:
【中文标题】是否可以将函数加载到一些分配的内存中并从那里运行它?【英文标题】:Is it possible to load a function into some allocated memory and run it from there? 【发布时间】:2010-08-25 20:17:03 【问题描述】:我正在搞乱一些进程间通信的东西,我很好奇是否可以将一个函数复制到某个共享内存中并从任何一个进程从那里运行它。
类似:
memcpy(shared_memory_address, &func, &func + sizeof(func));
我知道你不能接受函数的大小,但这就是我脑海中浮现的想法。
【问题讨论】:
您可能会在某些系统上破解自己的方式来执行此操作(尽管任何解决方案都非常不可移植),但是将其用于 IPC 的想法是可怕的。 我怀疑(并希望)不会。有各种检查可以防止您执行任意内存块。 无法获取函数的大小,但可以获取函数开始到下一个函数开始的距离。并不是说我会提倡这样做。 为什么不创建一个DLL?该函数将被共享,您可以通过VS中的#pragma来控制数据是本地的还是共享的。 @meagar:可以防止意外将数据解释为可执行代码。但是,一旦您明确告诉操作系统您打算将其用作代码,就没有问题了。 【参考方案1】:理论上,由于函数只是内存中某处的字节码序列,因此您可以复制函数的内存块并调用(跳转)它。尽管 c++ 抽象出这种可能性,但正如您所注意到的,我们实际上无法知道函数的大小(尽管我们可以获得指向它的指针)。
仍然有图书馆。例如,您可以告诉远程可执行文件从动态库加载特定函数并执行它。参考wikipedia-article。
【讨论】:
【参考方案2】:这很有趣。 但看起来你可以。虽然我会从不这样做:
在运行 Windows 7 的 lenovo:T61p 上编译:使用 g++ 4.3.4
我会注意到,某些类型的硬件会阻止这种情况,因为您只能从特定内存区域(程序区域)执行代码,该内存区域在硬件内存映射文件中标记为只读(以防止自行修改代码)。
还要注意,函数的类型非常有限:
在这个例子中 func() 做的很少,因此可以工作。 但是,如果您执行以下任何操作,它将无法移植到其他进程:
调用函数或方法。 传递一个指针(或引用) 任何包含指针或引用的对象都不起作用。 使用全局变量。 您可以传递一个方法指针: 但使用它的对象必须按值传递。以上都不起作用,因为一个进程的地址空间与另一个进程的地址空间没有相似之处(因为它在硬件级别映射到物理内存)。
愚蠢的例子
#include <vector>
#include <iostream>
#include <string.h>
int func(int x)
return x+1;
typedef int (*FUNC)(int);
int main()
std::vector<char> buffer(5000);
::memcpy(&buffer[0],reinterpret_cast<char*>(&func),5000);
FUNC func = reinterpret_cast<FUNC>(&buffer[0]);
int result = (*func)(5);
std::cout << result << std::endl;
【讨论】:
你不是在进程之间共享。 这很有趣,但也要注意缓冲区内的代码需要重新链接到外部函数。尤其是并行处理跳转表,我认为乐趣会消失。 函数超过5000会怎样?你是如何确定这个数字的? @Paul Nathan:@Potatoswatter:@Thomas Matthews:是的,这很有趣,但仅此而已。在实际应用中使用是不切实际的。而在现实生活中,将它传递给另一个进程并让它工作的可能性可以忽略不计(任何比表达式操作稍微复杂一点的函数都会崩溃)。 我喜欢这个可移植的示例,因为您可以在不同的操作系统和硬件上试用它,看看启用了哪些排序执行预防机制。例如,Linux x86-64 在调用函数时会出现段错误,因为堆是不可执行的。【参考方案3】:上次我尝试这个时,我遇到了一个障碍:确定函数中的字节数。任务是使用函数的地址,将字节复制到内存中(假设代码被编译为位置无关代码,PIC)。
一种更独立于平台的方法是查看编译器文档以查看是否有#pragma
、编译器选项或关键字允许您指定要在加载时加载的函数地址或段。
另外,搜索嵌入式系统组,因为这是一种流行的技术:将闪存编程的代码加载到 RAM 中,在 RAM 中执行功能,然后重置系统。
希望对您有所帮助。
编辑: 一个建议:使用汇编语言文件或链接器指令(在构建脚本中)创建数据或代码段。将您的函数放入单独的代码文件中。告诉编译器和链接器将此函数编译到新的代码段中。可能有编译器特定的语句来获取段的起始地址和大小。此外,操作系统可能能够为您加载给定地址的段。
还可以在操作系统的帮助下查看可在运行时加载的 DLL 或共享库。
【讨论】:
【参考方案4】:如果您尝试这样的事情,您可能会遇到从内存中运行不应包含可执行代码的代码的问题。有关更多信息,请参阅此 Wikipedia 文章:http://en.wikipedia.org/wiki/Executable_space_protection
【讨论】:
【参考方案5】:是的。 Java VM 等即时代码生成器使用了类似的技术。事实上,您几乎可以说操作系统的运行时加载程序和链接器正在为您执行此操作,因为它将动态库加载到您的进程中。
不过,您必须向操作系统请求可执行内存。而且您要跳转的代码必须以允许其位于内存中任何位置(与位置无关)的方式编写。
【讨论】:
【参考方案6】:如果您生成代码字节并将其注入进程,则称为运行时代码生成 (RTCG)。你可以look upsome examples。
现代内核会阻止它在非特权级别工作, 所以你必须先输入正确的模式或ring。为了 找到代码大小,您(当然)必须计算 函数它的代码段,直到最后一个返回码。
Afaik 图形驱动程序有时在创建代码时使用 RTCG 用于即时光栅操作(问题依赖)。
【讨论】:
【参考方案7】:您可以合理地假设这在 Linux、Windows 或更复杂的嵌入式操作系统上是完全不可能的。
但是如果你没有使用这种讨厌限制,你可以在你的程序集中修补一些表示函数开始/结束的保护字节,并使用它们来帮助您将内容复制到共享内存中(当然使用程序集),然后将过程地址列表发布到任何感兴趣的进程(也使用程序集访问/运行)。
当然,有一个定义良好的机制可以为多个进程提供代码库,Linux 和 Windows 提供的动态库系统。可能不像你想要的那样灵活。 :-)
【讨论】:
困难,但并非不可能。对数据页不执行强制执行的操作系统还提供 API 以将页标记为可执行,即 Windows 上的VirtualProtect
。以上是关于是否可以将函数加载到一些分配的内存中并从那里运行它?的主要内容,如果未能解决你的问题,请参考以下文章
是否可以将 else-if 语句存储在 XML 文件中并从活动中调用它?