fork on Windows
Win32サブシステムへの接続をのぞけば、動いた、ような、気がする。多分。
Device Drive Kitのヘッダファイルにも無い呼び出しとかがあったので(バージョンによるのか?)、検索して適当にヘッダファイルをでっち上げ、先日の日記のようにGetProcAddressでアドレスを取得。構造体等の定義もヘッダに入れないといけないので面倒でした。
あとは、WINDOWS NT/2000 NATIVE API REFERENCEの本の例を若干変更。コンパイルでエラーになる部分に明治的なキャストを入れるとか。これでコンパイルは通ったけど、実行時エラー。さんざん悩んだあげく、#pragma optimize("y", off)の場所を移動すると、動いた(ような気がする)。で、これが何なのかだが…
forkの定義がどうなっているかだけど(ものはこの辺にある)、
- ハンドルの継承を設定
- ZwCreateProcessでプロセス生成
- Thread生成のための情報を今のプロセスから取得:ここで、生成したスレッドが動き出す場所を指定
- ZwCreateThreadで先に生成したプロセス上にスレッドを生成
- 今のスレッドのexception handlerを新しく生成したスレッドにコピー(よくわからん)
- Win32サブシステムに接続を通知
- 生成したスレッドを起動
という感じ。本によると。
で、「生成したスレッドが動き出す場所」というのが、こう指定されている:
__declspec(naked) int child() { typedef BOOL (WINAPI *CsrpConnectToServer)(PWSTR); CsrpConnectToServer(0x77F8F65D)(L"\\Windows"); __asm mov eax, 0 __asm mov esp, ebp __asm pop ebp __asm ret } #pragma optimize("y", off) // disable frame pointer omission int fork() { ... context.Eip = ULONG(child); ... NT::ZwCreateThread(&hThread, THREAD_ALL_ACCESS, &oa, hProcess, &cid, &context, &stack, TRUE); .... }
fork()の中のcontext.Eip = ULONG(child)で、生成したスレッドがchildのアドレスから実行を開始することを指定している(インストラクションポインタの設定)。
で、childの方を見てみる。CsrpConnectToServerというのが、強制的にWin32サブシステムに接続する関数。これはntdll.dllから公開されていないので、アドレスを直書きしている。で、このアドレスがWindowsのバージョンによって異なると :-( (あ、先日の日記で変数のアドレスって書いたけど、それは間違いでした。)
で、このままだとエラーになったので、ここはとりあえずコメントアウト。
その下で、謎のアセンブリコードがあるけど、これは、関数からのリターンを指定しているみたい。fork()関数の中からgoto的に飛ぶので?(親プロセスのメモリ・スタックの状態のままで、インストラクションポインタがchildを指すようになるので)、fork()関数からのリターンになる様子。
eaxに0を指定することで、fork()の返り値を0にして、ebpをpopすることで、スタックポインタを戻している。で、これが動作するためには、「フレームポインタを使わない」という最適化をoffにする必要がある。それが、#pragma optimize("y", off)の正体。
とそこまではいいんだけど、僕の環境(Visual Studio 2005 / VISTA)では、#pragmaをchildの前に持っていかないと動かなかった。__declspec(naked)というのが謎なんだけど、以前の環境では、これがあると#pragmaは不要だったのかも知れない。
まー、何となく動いているという状態ではあるのだが、これからどうしようかなぁ。Win32サブシステムと接続しなくても、共有メモリが実現できれば並列計算はできるけど… I/Oが使えないのは寂しいが…