C++で機械語を実行(Ubuntu/g++ ver.)

C++言語を使用している際に、直接アセンブリで記述した機械語(=マシン語)を実行します。これまでWindowsバージョンを紹介しましたので、ここではUbuntuへ対応させたものを示します。

はじめに

本例はint型の変数を加算します。

C++コード

機械語取り込んだC++のソースリストを示します。

#include <iostream>
#include <cstring>
#include <sys/mman.h>

using namespace std;

int main() {
    unsigned char code[] = {
        0x89, 0xf8,       // mov eax, edi
        0x01, 0xf0,       // add eax, esi
        0xc3              // ret
    };

    try
    {
        // alloc. executable memory
        void* mem = mmap(nullptr, sizeof(code),
                         PROT_READ | PROT_WRITE | PROT_EXEC,
                         MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
        if (mem == MAP_FAILED) 
			throw "mmap" ;

        // copy machine code
        memcpy(mem, code, sizeof(code));

        // cast as function pointer
        int (*func)(int, int) = (int (*)(int, int))mem;

        // 実行してみる
        int a =12, b =13;
        int r = func(a, b);  // add
        cout << a << " + " << b << " ="  << r << endl;

        // free memory
        munmap(mem, sizeof(code));
    }
    catch (const char* str)
    {
        cerr << str << endl;
        return -1;
    }
    return 0;
}

最初のcode配列は以下のアセンブリ命令を機械語に書き換えたものです。

mov eax, edi
add eax, esi
ret

WindowsバージョンでVirtualAllocを使っていた部分をmmapに書き換え実行可能メモリーを割り当てます。そして、そこへ受け取った機械語のコードをコピーします。
mmapの返却値はvoid*ですので、関数のシグネチャへ合わせたいためポインタへ代入します。これによってfuncを通常の関数と同じように呼び出すことができます。

実行

機械語で作成した関数を呼び出し、結果を表示します。

 実行結果

$ g++ -O2 -o addgpp addgpp.cpp
$ ./addgpp
12 + 13 =25

WindowsUbuntuVisual Studio とg++/Clang)

Windowsで説明を続けたのでUbuntuの例も示しました。基本的に、どちらでも同じ方法で実現可能です。ただ、関数呼び出し規約が異なること、そして実行可能メモリーの操作を行うAPIが異なります。また、Ubuntuでは、セキュリティがより厳しいため実行できない場合もあります。そのような場合は適宜解除法を調べてください。