読者です 読者をやめる 読者になる 読者になる

eval() in C++

しばらく前にやったことですが、ネタとして面白い気がするのでサルベージ。

DynamicObjLoader を使って C++ で eval() を実装しました。
C++er が夢に見たり見なかったりしたこういうコードが動きます。

#include "DynamicObjLoader.h"

class Hoge
{
public:
    Hoge() : m_value(100) {}
    void doSomething() { printf("Hoge::doSomething(): %d\n", m_value); }
private:
    int m_value;
};

void* CreateAnyClass(const char *class_name)
{
    volatile void *obj = NULL;
    std::string source = std::string("obj = new ")+class_name+"();";
    DOL_Eval(source.c_str());
    return (void*)obj;
}

int main()
{
    Hoge *hoge = (Hoge*)CreateAnyClass("Hoge");
    hoge->doSomething();
    delete hoge;
}

DOL_Eval() で C++ ソースを生成し、コンパイルし、できた .obj を DynamicObjLoader でロード&リンクして実行しています。

DOL_Eval() は実際にはマクロになっていて、指定の C++ コード片以外に __FILE__, __FUNCTION__, その地点のスタックポインタ (esp) を実装関数に渡しています。

ソースの生成が今回の肝で、DOL_Eval() がある地点で見えているシンボルが見えるようにする必要があります。
global なシンボルについては __FILE__ を #include することで解決できます。
問題なのは関数内変数などの現在の scope にしかない変数ですが、これらは関数名と esp があれば ::SymSetContext() & ::SymEnumSymbols() で巡回することができます。(デバッガで見れる変数であれば情報を取れるのだと思われます)
変数名、型名、アドレスを取れるので DOL_Eval() がある地点のコンテキストを復元したように見せかけることもできそうです。

最終的に上の CreateAnyClass() の DOL_Eval() は以下のようなソースを生成、実行します。

#include "c:\somewhere\to\source.cpp"

extern "C" void evalblock()
{
    char*& class_name= *((char**)0x2cfa08);
    std::string& source= *((std::string*)0x2cf9c0);
    void*& obj= *((void**)0x2cf9e8);

    obj = new Hoge();
}

(リテラルのポインタ値!)

色々厳しい問題もあって、
scope 内変数は最適化が有効な場合メモリ上に存在しないことがよくあります。なので上の例の obj ように volatile をつけるなどで無理矢理メモリに繋ぎ止める対応が必要になります。
現状メンバ関数は未対応です。メンバ関数の場合 this を完璧に偽装するのが困難です。
現状複雑な書き方を要求される型 (配列や関数ポインタ) の場合うまいことソースが生成できません。
DynamicObjLoader の制限が全部ついてくるとか、逐次コンパイル&リンクするので超絶遅いなどの問題もあります。

あまりに問題が多すぎて結局ネタ以上の価値を見出だせず放棄してしまったんですが、イミディエイトウィンドウから任意の C++ コード片を任意の場所に注入可能にできたりしたら何か使い道があるかもしれません…。