c++ 如何分配可执行文件的内存缓冲区?

uelo1irk  于 2022-11-27  发布在  其他
关注(0)|答案(4)|浏览(158)

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

z31licg0

z31licg01#

你不会用malloc来做这个。在C程序中,你为什么要用new呢?但是,你也不会用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代码。

vbkedwbf

vbkedwbf2#

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

  • 使用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
bmp9r5qi

bmp9r5qi3#

documentation中针对VirtualAlloc所述
flProtect [英寸]
要分配的页区域的内存保护。如果要提交页,则可以指定任何一个内存保护常量。
其中之一是:
PAGE_EXECUTE 0x 10启用对页的提交区域的执行访问。试图写入提交区域会导致访问冲突。CreateFileMapping函数不支持此标志。
PAGE_EXECUTE_READ 0x 20启用对页的提交区域的执行或只读访问。尝试写入提交区域将导致访问冲突。Windows Server 2003和Windows XP:在Windows XP SP2和Windows Server 2003 SP1之前,CreateFileMapping函数不支持此属性。
PAGE_EXECUTE_READWRITE 0x 40启用对页的提交区域的执行、只读或读/写访问。Windows Server 2003和Windows XP:在Windows XP SP2和Windows Server 2003 SP1之前,CreateFileMapping函数不支持此属性。
以此类推

rks48beu

rks48beu4#

在编译时,连接器将通过将内存分配到数据段和代码段来组织程序的内存占用。CPU将确保程序计数器(硬CPU寄存器)值保留在代码段中,否则CPU将因违反内存界限而抛出硬件异常。这通过确保程序只执行有效代码来提供一定的安全性。Malloc用于分配数据内存。您的应用程序有一个堆,堆的大小由链接器确定,并标记为数据内存。因此,在运行时,malloc只是从堆中抓取一些虚拟内存,这些内存将始终是数据。
我希望这能帮助你更好地理解正在发生的事情,尽管这可能不足以让你到达你需要的地方。也许你可以为你的运行时生成的代码预先分配一个“代码堆”或内存池。你可能需要与链接器争论来完成这一点,但我不知道任何细节。

相关问题