cdecl_thunk

asio_sinkを作成中。
ASIOのコールバック関数にコンテキストを渡せないので、仕方なくサンクを書く羽目になりました。
Windowsで一般的な__stdcall呼び出し規約でなく、__cdecl呼び出し規約なので実装が少々面倒です。
アセンブリ言語は得意でないので、コンパイラに似たコードを吐かせてから手で修正することにしました。
できたコードがこれです。

// Argcは引数の数(32ビット値のみ)
template<std::size_t Argc>
class cdecl_thunk
{
public:
    // funcは関数のポインタ、ptrはthisポインタ
    cdecl_thunk(void* func, void* ptr)
    {
        std::size_t pos = 0;

        // push ebp
        entry_[pos++] = 0x55;

        // mov ebp,esp
        entry_[pos++] = 0x8B;
        entry_[pos++] = 0xEC;

        for (std::size_t i = 0; i < Argc; ++i)
        {
            // mov eax,dword ptr [ebp + 4*(1+Argc-i)]
            entry_[pos++] = 0x8B;
            entry_[pos++] = 0x45;
            entry_[pos++] = 4*(1+Argc-i);

            // push eax
            entry_[pos++] = 0x50;
        }

        // push "ptr"
        entry_[pos++] = 0x68;
        std::memcpy(&entry_[pos], &ptr, 4);
        pos += 4;

        // call "func"
        boost::int32_t rel32 =
            reinterpret_cast<boost::int32_t>(func) -
            reinterpret_cast<boost::int32_t>(&entry_[pos+5]);
        entry_[pos++] = 0xE8;
        std::memcpy(&entry_[pos], &rel32, 4);
        pos += 4;

        // add esp, 4*(1+Argc)
        entry_[pos++] = 0x83;
        entry_[pos++] = 0xC4;
        entry_[pos++] = 4*(1+Argc);

        // pop ebp
        entry_[pos++] = 0x5D;

        // ret
        entry_[pos++] = 0xC3;
    }

    // 関数のエントリポイント
    void* get() { return entry_; }

private:
    boost::uint8_t entry_[18+4*Argc];
};

// ここからテストコード
#include <iostream>

class hoge
{
public:
    void foo(long a, long b)
    {
        std::cout
            << "this=" << static_cast<void*>(this)
            << ", a=" << a
            << ", b=" << b
            << std::endl;
    }

    static void cdecl_helper(hoge* this_ptr, long a, long b)
    {
        this_ptr->foo(a, b);
    }
};

typedef void (*func_type)(long, long);

int main()
{
    hoge h;
    h.foo(1,2); // 通常の呼び出し

    cdecl_thunk<2> thunk(reinterpret_cast<void*>(&hoge::cdecl_helper), &h);
    func_type func_ptr = reinterpret_cast<func_type>(thunk.get());
    (*func_ptr)(1,2); // サンク経由の呼び出し
}

これで一応動いています。
あとは、WindowsXP SP2のDEP対策(VirtualProtect)と命令キャッシュのクリア(FlushInstructionCache)が必要だったと記憶しています。
続きはまた明日。