C++で機械語を実行(文字列長, strlen)

C++言語を使用している際に、直接アセンブリで記述した機械語(=マシン語)を実行します。

はじめに

本例は、文字列長を求めるstrlenと同じ動作を行う機械語を示します。文字の扱いは複雑ですが、ここではASCII文字のみを考えます。以前の
arques.hatenablog.com
を改良します。

C++コード

C++で記述したソースコード機械語を取り込みます。プログラムは以前のものと同じ構造です。

include <iostream>
#include <windows.h>

using namespace std;

// alloc memory and copy code to it
void* allocAndCopyIt(unsigned char code[], size_t size)
{
    void* mem = VirtualAlloc(
                    NULL, size,
                    MEM_COMMIT | MEM_RESERVE,
                    PAGE_EXECUTE_READWRITE);

    if (!mem)
        throw "VirtualAlloc failed.";

    memcpy(mem, code, size);

    return mem;
}

// free memory
void freeMem(void* mem)
{
    if (!VirtualFree(mem, 0, MEM_RELEASE))
        throw "VirtualFree failed";
}

int main(void)
{
    unsigned char code[] = {
        0x48, 0x57,             // push        rdi
        0x48, 0x8B, 0xF9,       // mov         rdi,rcx
        0x48, 0xC7, 0xC1, 0xFF, // mov         rcx,7FFFFFFFh
        0xFF, 0xFF, 0x7F,
        0x32, 0xC0,             // xor         al,al
        0xFC,                   // cld
        0xF2, 0xAE,             // repne scas  byte ptr [rdi]
        0x48, 0xC7, 0xC0, 0xFE, // mov         rax,7FFFFFFFh
        0xFF, 0xFF, 0x7F,
        0x48, 0x2B, 0xC1,       // sub         rax,rcx
        0x5F,                   // pop         rdi
        0xC3                    // ret
    };

    try
    {
        void* mem = allocAndCopyIt(code, sizeof(code));
        int (*func)(char*) = (int (*)(char*))mem;

        char src[] = "This is a pen.";
        int length = func(src);
        cout << "strlen(\"" << src << "\") = " << length << endl;
        freeMem(mem);
    }
    catch (const char* str)
    {
        cerr << str << endl;
        return -1;
    }
    return 0;
}

allocAndCopyIt関数

以前説明したものと同じです、処理についてはソースコード、あるいは以前の説明を参照してください。

freeMem関数

以前説明したものと同じです、処理についてはソースコード、あるいは以前の説明を参照してください。

main関数

本関数の処理を順次説明します。
unsigned char code[]が機械語を保持するunsigned charの配列です。

allocAndCopyIt関数を呼び出し、必要な実行可能メモリーを割り当て、そこへ機械語をコピーします。返却値はvoid*ですので、関数のシグネチャへ合わせたいためポインタの代入を行います。これによってfuncを通常の関数と同じように呼び出すことができます。
func関数に文字列を与え、その長さを調べます。

機械語

unsigned char code[]が機械語の定義を行っている部分です。これを実行可能なメモリーへ転送し、C++から直接制御を渡します。この例では、以下のアセンブリ言語で記述したソースコード機械語化したものです。

include ksamd64.inc
INT_MAX = 7fffffffh                ; 2^31 - 1

_TEXT   segment

        public asmCode
asmCode proc    frame
        ; prolog
        rex_push_reg rdi        ; push rdi
        .endprolog ;


        mov     rdi, rcx        ; source address
        mov     rcx, INT_MAX    ; max string length
        xor     al, al          ; al = NULL  char
        cld                     ; clear direction flag
        repne   scasb           ; search NULL char

        mov     rax, INT_MAX-1
        sub     rax, rcx        ; length of string


        ; epilog
        pop rdi                 ; pop rdi

        ret
asmCode endp

_TEXT   ends
        end

機械語への変換はml64コマンドとdumpbin コマンドを使うと簡単に得られます。得られた16進数の値を以下に示します。コメントを参照すると何を行っているか分かるでしょう。

unsigned char code[] = {
    0x48, 0x57,             // push        rdi
    0x48, 0x8B, 0xF9,       // mov         rdi,rcx
    0x48, 0xC7, 0xC1, 0xFF, // mov         rcx,7FFFFFFFh
    0xFF, 0xFF, 0x7F,
    0x32, 0xC0,             // xor         al,al
    0xFC,                   // cld
    0xF2, 0xAE,             // repne scas  byte ptr [rdi]
    0x48, 0xC7, 0xC0, 0xFE, // mov         rax,7FFFFFFFh
    0xFF, 0xFF, 0x7F,
    0x48, 0x2B, 0xC1,       // sub         rax,rcx
    0x5F,                   // pop         rdi
    0xC3                    // ret
};