実装は、最終プラットフォーム(.Net / Java)から独立しています。 C ++ / Asmで書かれていますが、ユーザーコードはC#で作成されています 私はそれに書きます。
勇気を得て、ようやく32ビットシステムの例を安定させたので、完全に準備が整ったとして公開する準備ができました。 そして、はい、繰り返します:適応中はどのプラットフォームでも動作します
まず、このサイクルのHabréに投稿された記事の全リスト
目標
この作業の目的は、オペレーティングシステムによって提供されないスレッドに関連する機能を構築することです。 たとえば、Linux Fork()メソッドを使用しましたが、これはWindows OSの現実に合わせて修正されました。
そのため、Originメソッドがあり、その内部でFork.CloneThread()メソッドが呼び出されると、実行の2番目のスレッドが表示されます。その開始はFork.CloneThread()メソッドの呼び出しポイントに等しく、メソッドの終了時に実行は終了します元のスレッドのローカル変数のすべての値が2番目の実行スレッドに保存されるような方法でオリジナル。 つまり、CloneThread()を呼び出して、現在のスレッドを2つに分割します。
読者から求められるもの
- アセンブラーを読むことへの恐怖の欠如。 ただ=)何かが明確でない場合は、グーグルを使用してください
- スレッドスタックはスレッドごとに1つであることを理解します。 目的を理解する
準備のための材料:
フロークローニング
最初は何がありますか? ストリームがあります。 また、そこでコードを実行して、新しいスレッドを作成したり、スレッドプールでタスクをスケジュールしたりすることもできます。 また、ネストされた呼び出しに関する情報は呼び出しスタックに格納され、必要に応じて操作できることも理解しています(たとえば、C ++ / CLIを使用)。 さらに、規則に従って、スタックの最上部にあるEBPレジスタの値、ret命令の戻りアドレスを入力し、ローカル命令にスペースを割り当てると(必要な場合)、この方法でメソッド呼び出しをシミュレートできます。
フローを複製するには何をしなければなりませんか?
- 保存中
- CloneThread(C#)メソッド内で、任意のローカル変数のアドレスを取得します
- C ++メソッドを呼び出して、このアドレスを渡します。 この段階で、呼び出しスタックは次のようになります。
さて、またはこのように短縮された方法で:
- 内部-EBPの値を取得します-呼び出しフレームへのポインターとチェーンに沿ってポインターを逆参照し、CloneThreadメソッドに進み、CloneThreadのローカル変数のアドレスで現在のEBPを確認します。 これは、JITterによって生成されるC#とC ++の間のすべてのプロキシ呼び出しを通過するために必要です。
- 1を追加してCloneThreadフレームを終了し、ライブラリ関数を呼び出すコードに入ります。 受信したアドレスからESPまではすべて、ユーザーコードからの呼び出しのチェーンです。 バッファに保存し、ストリームを作成(またはプールから取得)して、このバッファのアドレス(スタックのコピー)を渡します。
- 回復 。 新しいスレッドが親のコピーポイントから動作し続けるには、新しいスレッドで呼び出されたユーザーメソッド(実際には誰も呼び出されなかった)からのCloneThread()呼び出しをシミュレートする必要があります。 これを行うには、保存された親スレッドスタックの一部を呼び出しスタックの先頭に追加し、フレームのスタックを形成するEBPチェーンを修正して、コードを実行する必要があります。
- 最初、2番目のスレッドでコードが機能し始めたばかりのとき、次のような呼び出しスタックがあります。
- ESPアドレスを取得します。
- 現在のメソッドの本体のアドレスをスタックにプッシュする-シミュレーションが呼び出されるユーザーメソッドから戻る
- EBPをプッシュ-フレームの整合性を維持します。 ヒープ上のスタックのコピーとともに、次の種類の呼び出しスタックがあります。
- コピーする前に、スタックコピーに保存されたEBPチェーンを修正します(インプレースで実行できません)。
- プッシュコマンドを使用して、親スレッドのスタックのコピーを現在のスタックに挿入します(結果として多くのプロキシとC ++メソッドを呼び出すCloneThreadを呼び出すユーザーメソッドへの呼び出しをシミュレートします)
- C ++の遠いJMPをCloneThreadメソッドにします。CloneThreadメソッドでは、return runを提供します
- これにより、CloneThread(C#)が終了し、ユーザーメソッドが終了します。
- 出来上がり-両方のスレッドで、コードは同じポイントから実行されます。 ストリームの分岐が完了しました。
- 最初、2番目のスレッドでコードが機能し始めたばかりのとき、次のような呼び出しスタックがあります。
どうして
これが行われる最も重要なことは、すべてがどのように機能するか、そしてもしあなたが知っていればそれを操作し始めることができるかについての理解を強化することです。
資源
- GitHubプロジェクトDotNetEx :その中のプロジェクトはAdvancedThreadingLibraryと呼ばれ、RocketScience / 01-forkingThreadを使用して実行します。 ちなみに、同じライブラリには、sizeof(ReferenceType)、出荷されたアセンブリとヒープ内のオブジェクトのプールを持つIoCの例があります。