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)が必要だったと記憶しています。
続きはまた明日。