実行時に関数を生成する方法(Xbyak)

2009-08-19

XBYAKは演算子オーバーロードなどを使ってC++のコード中にx86のニーモニックとほとんど同じものを直接記述し、動的に実行コードを生成できるようにするライブラリ。その辺もすごそうだけど、それとは別にどうやって動的にコードを実行できるようにしているのか見てみた。 protectという関数:

/**
change exec permission of memory
@param addr [in] buffer address
@param size [in] buffer size
@param canExec [in] true(enable to exec), false(disable to exec)
@return true(success), false(failure)
*/
static inline bool protect(const void *addr, size_t size, bool canExec)
{
#ifdef __GNUC__
size_t pageSize = sysconf(_SC_PAGESIZE);
size_t iaddr = reinterpret_cast<size_t>(addr);
size_t roundAddr = iaddr & ~(pageSize - static_cast<size_t>(1));
int mode = PROT_READ | PROT_WRITE | (canExec ? PROT_EXEC : 0);
return mprotect(reinterpret_cast<void*>(roundAddr), size + (iaddr - roundAddr), mode) == 0;
#elif defined(_WIN32)
DWORD oldProtect;
return VirtualProtect(const_cast<void*>(addr), size, canExec ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE, &oldProtect) != 0;
#else
return true;
#endif
}

の中で、WindowsではVirtualProtect関数、Unix系ではmprotectを使って、メモリ領域の実行可能属性をセットしている。 この関数を使って、ヒープだかスタック領域だかに実行可能属性をつけると実行時に関数が作れる:

void* make_adder(int y) {
const unsigned char tbl[] = {
0x8b, 0x44, 0x24, 0x04, // mov(eax, ptr[esp+4]);
0x83, 0xc0, y, // add(eax, y);
0xc3, // ret();
};
size_t size = sizeof(tbl)/sizeof(*tbl);
void* buf = new char[size];
memcpy(buf, tbl, size);
protect(buf, size, true);
return buf;
}

int main()
{
int (*add3)(int) = (int (*)(int))make_adder(3);
printf("%d\n", add3(2));
}

もちろん、protect 関数を呼び出さないとヒープ領域のコードは実行できない…のかと思ったらそんなことはなくできてしまった…(WindowsXP)。Linuxでは試してない。

  • これがちゃんと効いてれば、バッファオーバーランもそんなに怖くない気がする
  • CPUのメモリマネージャって仕組みよく知らないんだけど、そんなに細かい単位でメモリの領域に対して実行可能属性とか設定できるのかな?
    • __GNUC__の場合はページ単位で設定するようになってる
  • x86のマシンコードを調べるのにてこずった…
    • gccだけでリストファイルを出力するオプションがわからず
    • 「gcc -S hoge.c」して「as -a hoge.s」
  • XBYAKを使えば、Compilers: Backend to Frontend and Back to Front Againを動的にできるかなぁ、などと妄想