我想在Win32上分配一个可执行的缓冲区,但在Visual Studio中使用malloc函数返回非可执行内存区域时发生异常。我阅读到有一个NX标志可以禁用...我的目标是将字节码实时转换为x86汇编,同时考虑性能。
有人可以帮我吗?
我想在Win32上分配一个可执行的缓冲区,但在Visual Studio中使用malloc函数返回非可执行内存区域时发生异常。我阅读到有一个NX标志可以禁用...我的目标是将字节码实时转换为x86汇编,同时考虑性能。
有人可以帮我吗?
在C++程序中,您不应该使用malloc
。对于可执行内存,您也不应该使用new
。Windows系统提供了一个特定的函数VirtualAlloc
来保留内存,然后使用VirtualProtect
函数标记该内存为可执行,并应用例如PAGE_EXECUTE_READ
标志。
完成后,您可以将指向分配内存的指针转换为适当的函数指针类型并调用函数。使用完毕后别忘了调用VirtualFree
。
以下是一些基本示例代码,没有错误处理或其他的健全性检查,只是为了向您展示如何在现代C++中实现此操作(该程序打印5):
#include <windows.h>
#include <vector>
#include <iostream>
#include <cstring>
int main()
{
std::vector<unsigned char> const code =
{
0xb8, // move the following value to EAX:
0x05, 0x00, 0x00, 0x00, // 5
0xc3 // return what's currently in EAX
};
SYSTEM_INFO system_info;
GetSystemInfo(&system_info);
auto const page_size = system_info.dwPageSize;
// prepare the memory in which the machine code will be put (it's not executable yet):
auto const buffer = VirtualAlloc(nullptr, page_size, MEM_COMMIT, PAGE_READWRITE);
// copy the machine code into that memory:
std::memcpy(buffer, code.data(), code.size());
// mark the memory as executable:
DWORD dummy;
VirtualProtect(buffer, code.size(), PAGE_EXECUTE_READ, &dummy);
// interpret the beginning of the (now) executable memory as the entry
// point of a function taking no arguments and returning a 4-byte int:
auto const function_ptr = reinterpret_cast<std::int32_t(*)()>(buffer);
// call the function and store the result in a local std::int32_t object:
auto const result = function_ptr();
// free the executable memory:
VirtualFree(buffer, 0, MEM_RELEASE);
// use your std::int32_t:
std::cout << result << "\n";
}
与普通的C++内存管理相比,这非常不寻常,但并不是真正的火箭科学。难的部分是要正确获得实际的机器代码。请注意,我在这里示例的只是非常基本的x64代码。
new
(无论是“普通”的还是像std::allocator
这样的放置new)从自由存储区(而不是堆)动态分配内存。您理论上可以使用malloc
,但那时您实际上正在编写C而不是(惯用的)C ++。对于OP的问题,VirtualAlloc
和malloc
之间的区别也很重要。 - Christian HacklMEM_RESERVE
:https://devblogs.microsoft.com/oldnewthing/20151008-00/?p=91411 - PaulVirtualAlloc
分配内存并进行读写访问。VirtualProtect
更改该区域的保护级别以进行执行-读取访问。adr = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
// write code to the region
ok = VirtualProtect(adr, size, PAGE_EXECUTE_READ, &oldProtection);
// execute the code in the region
VirtualAlloc
的文档,指定要分配的页面区域的内存保护。如果这些页面正在被提交,则可以指定任何一个内存保护常量之一。其中包括:
详见此处。PAGE_EXECUTE 0x10 启用对已提交页面区域的执行访问。试图写入已提交的页面区域将导致访问冲突异常。 此标志不受 CreateFileMapping 函数支持。
PAGE_EXECUTE_READ 0x20 启用对已提交页面区域的执行或只读访问。试图写入已提交的页面区域将导致访问冲突异常。 Windows Server 2003 和 Windows XP:该属性直到 Windows XP with SP2 和 Windows Server 2003 with SP1 才受 CreateFileMapping 函数支持。
PAGE_EXECUTE_READWRITE 0x40 启用对已提交页面区域的执行、只读或读/写访问。 Windows Server 2003 和 Windows XP:该属性直到 Windows XP with SP2 和 Windows Server 2003 with SP1 才受 CreateFileMapping 函数支持。
基于Christian Hackl的回答,这是一个C版本。
我认为VirtualAlloc
中的SIZE_T dwSize
应该是代码大小(以字节为单位),而不是system_info.dwPageSize
(如果代码的大小超过system_info.dwPageSize
怎么办?)。
我不懂C语言,不知道sizeof(code)
是否是获取机器代码大小的“正确”方法。
这个程序可以在C++下编译,所以我猜它不会偏题lol
#include <Windows.h>
#include <stdio.h>
int main()
{
// double add(double a, double b) {
// return a + b;
// }
unsigned char code[] = { //Antonio Cuni - How to write a JIT compiler in 30 minutes: https://www.youtube.com/watch?v=DKns_rH8rrg&t=118s
0xf2,0x0f,0x58,0xc1, //addsd %xmm1,%xmm0
0xc3, //ret
};
LPVOID buffer = VirtualAlloc(NULL, sizeof(code), MEM_COMMIT, PAGE_READWRITE);
memcpy(buffer, code, sizeof(code));
//protect after write, because protect will prevent writing.
DWORD oldProtection;
VirtualProtect(buffer, sizeof(code), PAGE_EXECUTE_READ, &oldProtection);
double (*function_ptr)(double, double) = (double (*)(double, double))buffer; //is there a cleaner way to write this ?
// double result = (*function_ptr)(2, 234); //NOT SURE WHY THIS ALSO WORKS
double result = function_ptr(2, 234);
VirtualFree(buffer, 0, MEM_RELEASE);
printf("%f\n", result);
}
在编译时,链接器将通过将内存分配到数据段和代码段来组织程序的内存占用。CPU 将确保程序计数器(硬 CPU 寄存器)值保持在代码段内,否则 CPU 将因违反内存边界而抛出硬件异常。这通过确保您的程序仅执行有效代码来提供一定的安全性。Malloc 用于分配数据内存。您的应用程序具有堆,堆的大小由链接器确定,并标记为数据内存。因此,在运行时,malloc 只是从堆中获取一些虚拟内存,该内存始终为数据。
我希望这可以帮助您更好地理解正在发生的事情,尽管这可能不足以让您达到所需的目标。也许您可以预先分配一个“代码堆”或内存池以用于运行时生成的代码。您可能需要调整链接器才能完成此操作,但我不知道任何详细信息。
__attribute__
在数组上实现。 - Peter Cordes