【问题标题】:How to alloc a executable memory buffer?如何分配可执行内存缓冲区?
【发布时间】:2017-04-17 14:52:11
【问题描述】:

我想分配一个可以在 Win32 上执行的缓冲区,但在 Visual Studio 中有一个异常,因为 malloc 函数返回一个不可执行的内存区域。我读到有一个 NX 标志要禁用...我的目标是将字节码即时转换为 asm x86,同时牢记性能。

有人可以帮助我吗?

JS

【问题讨论】:

  • 您可能想看看asmJit,这是一个免费代码,它为编写 JIT 的程序集/内存管理部分完成了所有繁重的工作,包括设置内存保护位,以便你可以执行你的代码。

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


【解决方案1】:

您不要为此使用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 代码。

【讨论】:

  • 在 C++ 程序中使用 malloc 的原因是从堆中分配动态内存。完全正常。
  • @KyleSweet:在 C++ 中,您使用 new(“普通”或像 std::allocator 这样的新放置)从空闲存储(而不是堆)动态分配。从技术上讲,您可以使用malloc,但是您实际上是在编写 C 而不是(惯用的)C++。对于OP的问题,VirtualAllocmalloc之间的区别也很重要。
  • 我只是在回答你提出的问题。如果您只想从堆中获取一些快速而肮脏的内存,请不要害怕使用 malloc!
  • 内存池是一个很好的例子,或者任何需要使用内存的东西——这仅受您的想象力的限制。我同意,在使用指针时必须非常注意。如果您“害怕”,您可能应该考虑另一种语言。
  • 吹毛求疵:你错过了MEM_RESERVEdevblogs.microsoft.com/oldnewthing/20151008-00/?p=91411
【解决方案2】:

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

  • 使用VirtualAlloc 和读写访问分配内存。
  • 用您的代码填充该区域
  • 使用 VirtualProtectto execute-read-access 更改该区域的保护
  • 跳转到/调用该区域的入口点

所以它可能看起来像这样:

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

【讨论】:

  • 根据规范,如果最后一个参数为 NULL,VirtualProtect 将失败。必须使用指向(虚拟)输出变量的指针。
  • @user3042599:谢谢。修好了。
【解决方案3】:

documentation 中所述,VirtualAlloc

flProtect [输入]

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

其中之一是:

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

【讨论】:

  • 看起来这与内存的有关。这里合适吗?
  • 是的。如果需要控制模式,需要分配pages
【解决方案4】:

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

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

【讨论】:

  • 动态分配的内存页面的读/写/执行权限与链接器无关。如果您想要一些 静态分配 写入+执行页面,您可以使用链接器脚本或 GNU C __attribute__ 数组上的东西来做到这一点。
  • 另外,不,通过检查程序计数器(x86-64 上的 RIP)是否保持在特定范围内,内存保护不起作用。这就是分段的工作方式(使用基本/限制),但 x86-64 甚至不支持 64 位模式下的分段限制。内存保护是基于每页的,页表中的位(由操作系统设置)。有关图表,请参见 stackoverflow.com/questions/46509152/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-09
  • 1970-01-01
  • 2014-11-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多