如何分配可执行内存缓冲区?

时间:2016-12-02 16:12:55

标签: c++ malloc executable compiler-optimization jit

我想分配一个我可以在Win32上执行的缓冲区,但我在visual studio中有一个例外,因为malloc函数返回一个非可执行的内存区域。我读到有一个NX标志要禁用...我的目标是在飞行时将字节码转换为asm x86并记住性能。

有人可以帮助我吗?

JS

4 个答案:

答案 0 :(得分:9)

您不能使用malloc。无论如何,你为什么要在C ++程序中?但是,您也不能将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代码。

答案 1 :(得分:4)

正如documentation VirtualAlloc

中所述
  

flProtect [in]

     

要分配的页面区域的内存保护。如果正在提交页面,则可以指定任何一个内存保护常量。

其中一个是:

  

PAGE_EXECUTE   为0x10   允许对已提交的页面区域执行访问。尝试写入已提交的区域会导致访问冲突。   CreateFileMapping函数不支持此标志。

     

PAGE_EXECUTE_READ   为0x20   启用对已提交页面区域的执行或只读访问。尝试写入已提交的区域会导致访问冲突。   Windows Server 2003和Windows XP:在Windows XP SP2和Windows Server 2003 SP1之前,CreateFileMapping函数不支持此属性。

     

PAGE_EXECUTE_READWRITE   0x40的   启用对已提交页面区域的执行,只读或读/写访问。   Windows Server 2003和Windows XP:在Windows XP SP2和Windows Server 2003 SP1之前,CreateFileMapping函数不支持此属性。

来自here

等等

答案 2 :(得分:4)

扩展上述答案,一个好的做法是:

  • 使用VirtualAlloc和读写访问权限分配内存。
  • 使用您的代码填写该区域
  • 使用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

答案 3 :(得分:-2)

在编译时,链接器将通过将内存分配到数据部分和代码部分来组织程序的内存占用。 CPU将确保程序计数器(硬CPU寄存器)值保留在代码段内,否则CPU将因违反内存限制而抛出硬件异常。这通过确保您的程序仅执行有效代码来提供一些安全性。 Malloc用于分配数据存储器。您的应用程序具有堆,并且链的大小由链接器建立,并标记为数据内存。因此,在运行时,malloc只是从堆中获取一些永远是数据的虚拟内存。

我希望这可以帮助您更好地了解正在发生的事情,但这可能不足以让您到达您需要的位置。也许您可以为运行时生成的代码预先分配“代码堆”或内存池。您可能需要对链接器大惊小怪才能完成此任务,但我不知道任何细节。