web-dev-qa-db-ja.com

実行可能メモリバッファを割り当てる方法は?

Win32で実行できるバッファを割り当てたいのですが、Visual Studioに例外があり、malloc関数が実行不可能なメモリゾーンを返します。無効にするNXフラグがあることを読みました...私の目標は、パフォーマンスを念頭に置いて、バイトコードをオンザフライでasmx86に変換することです。

誰かが私を助けることができますか?

JS

9
John Smith

そのために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コードであることに注意してください。

13
Christian Hackl

上記の答えを拡張すると、良い習慣は次のとおりです。

  • VirtualAlloc および読み取り-書き込み-アクセスを使用してメモリを割り当てます。
  • その地域にコードを入力します
  • そのリージョンの保護を VirtualProtect to 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
5
zx485

documentation for VirtualAllocで述べたように

flProtect [in]

割り当てられるページの領域のメモリ保護。ページがコミットされている場合は、メモリ保護定数のいずれかを指定できます。

それらの1つは:

PAGE_EXECUTE0x10ページのコミットされた領域への実行アクセスを有効にします。コミットされた領域に書き込もうとすると、アクセス違反が発生します。このフラグは、CreateFileMapping関数ではサポートされていません。

PAGE_EXECUTE_READ0x20ページのコミットされた領域への実行または読み取り専用アクセスを有効にします。コミットされた領域に書き込もうとすると、アクセス違反が発生します。 Windows Server2003およびWindowsXP:この属性は、Windows XP SP2およびWindowsServer 2003 SP1)までCreateFileMapping関数でサポートされていません。

PAGE_EXECUTE_READWRITE 0x40ページのコミットされた領域への実行、読み取り専用、または読み取り/書き込みアクセスを有効にします。 Windows Server2003およびWindowsXP:この属性は、Windows XP SP2およびWindowsServer 2003 SP1)までCreateFileMapping関数でサポートされていません。

など ここ