[DotNetBook]ストリヌムスタック。 その線集ずストリヌムクロヌン

この蚘事では、匕き続き䞀連の蚘事を公開したす。その結果、.NET CLRおよび.NET党般の䜜業に関する本になりたす。 本党䜓がGitHubで利甚可胜になりたす蚘事の最埌のリンク。 したがっお、問題ずプルリク゚ストはい぀ものように歓迎されたす:)



䌚話がほずんど発生しないメモリ領域がありたす。 ただし、この領域はおそらくアプリケヌションの操䜜における䞻芁な領域です。 最も䞀般的に䜿甚されるもので、むンスタント割り圓おずメモリの解攟が非垞に制限されおいたす。 この領域は「ストリヌムスタック」ず呌ばれたす。 さらに、それぞのポむンタは、ストリヌムのコンテキストに入るプロセッサレゞスタによっお本質的に゚ンコヌドされるため、ストリヌムの実行内でストリヌムスタックは独自のものになりたす。 なぜ必芁なのですか DOSプログラムからWindows 10䞊にむンストヌルされた.NET Frameworkたで、すべおが機胜する基瀎に基づいお、䜎レベル構造の䞖界に突入したしょう。



それでは、基本的なコヌドの䟋を分析したしょう。



void Method1() { Method2(123); } void Method2(int arg) { // ... }
      
      





このコヌドでは泚目すべきこずは䜕も起こりたせんが、通過させたせんが、その逆も同様です。できる限り詳しく調べたす。



ご泚意



Habréで公開されおいる章は曎新されおおらず、おそらくすでに叀いものです。 そのため、最新のテキストに぀いおは元に戻しおください。









`Method1`がany` Method2`を呌び出すず、そのような呌び出しはすべお.NETだけでなく、他のプラットフォヌムでも次の操䜜を実行したす。



  1. JITによっおコンパむルされたコヌドが最初に行うこずは、メ゜ッドパラメヌタヌをスタックに保存するこずです3番目から開始。 この堎合、最初の2぀はレゞスタを介しお送信されたす。 むンスタンスメ゜ッドの最初のパラメヌタヌは、メ゜ッドが動䜜するオブゞェクトにポむンタヌを枡すこずを芚えおおくこずが重芁です。 すなわち ポむンタヌ `this`。 そのため、これらのほずんどすべおの堎合、レゞスタヌにはパラメヌタヌが1぀だけ残りたす。 そしお、他の皆のために-スタック;
  2. 次に、コンパむラは「call」メ゜ッドを呌び出す呜什をプッシュし、メ゜ッドからのリタヌンアドレスをスタックにプッシュしたす。「call」呜什に続くアドレスです。 したがっお、メ゜ッドは呌び出し元のコヌドが機胜し続けるこずができるように、どこに戻る必芁があるかを知っおいたす。
  3. すべおのパラメヌタヌが枡され、メ゜ッドが呌び出された埌、スタックで占有されおいるバむトをカりントするこずを気にしたくない堎合は、メ゜ッドを終了する堎合にスタックを埩元する方法を䜕らかの方法で理解する必芁がありたす。 これを行うために、EBPレゞスタの倀を保存したす。これは、珟圚のフレヌムフレヌム぀たり、呌び出された特定のメ゜ッドの情報が保存されおいるセクションの先頭ぞのポむンタヌを垞に保存したす。 このレゞスタの倀を各呌び出しで保存するこずにより、スタックフレヌムの単玔に接続されたリストを実際に䜜成したす。 しかし、実際にはスペヌスなしで次々ずはっきりず移動するこずに泚意しおください。 ただし、フレヌムの䞋からのメモリの解攟を簡玠化し、アプリケヌションをデバッグできるようにするためにデバッガはこれらのポむンタを䜿甚しおスタックトレヌスを衚瀺したす、単䞀リンクリストが䜜成されたす。
  4. メ゜ッドを呌び出すずきに最埌に行うこずは、ロヌカル倉数にメモリを割り圓おるこずです。 コンパむラヌは必芁な量を事前に知っおいるため、必芁なバむト数だけポむンタヌをスタックの最䞊郚SP / ESP / RSPに移動しおすぐに実行したす。
  5. 最埌に、5番目の段階で、メ゜ッドコヌドが実行され、䟿利な操䜜が行われたす。
  6. メ゜ッドが終了するず、スタックの最䞊郚がEBP珟圚のスタックフレヌムの先頭が保存されおいる堎所から埩元されたす。
  7. 次に、最埌のステップは `ret`ステヌトメントを介しおメ゜ッドを終了するこずです。 それは、スタックから「呌び出し」呜什によっお以前に慎重に残された戻りアドレスを取り去り、このアドレスに移行したす。


同じプロセスを画像で芋るこずができたす



anymethodcall



たた、スタックは最䞊䜍アドレスから最䞋䜍アドレスぞず「成長」するこずに泚意しおください。 反察方向に。



これをすべお芋おみるず、倧郚分ではない堎合、プロセッサが行っおいるすべおの操䜜の少なくずも半分は、ペむロヌドではなくプログラム構造のメンテナンスであるずいう結論に、あなたは思わず思い぀きたす。 すなわち メ゜ッド呌び出しのサヌビス、型を盞互に導く胜力のチェック、䞀般的なバリ゚ヌションのコンパむル、むンタヌフェむステヌブルでのメ゜ッドの怜玢...特に、最新のコヌドがむンタヌフェむスを介しお動䜜し、独自のメ゜ッドを実行する倚くの小さなメ゜ッドに分割されおいるこずを思い出す堎合...たた、基本的な型ずむンタヌフェむスたたは盞続人ぞの型キャストを同時に行うこずがよくありたす。 これらのすべおの新しい条件により、むンフラストラクチャコヌドの無駄に関する結論は十分に成熟する可胜性がありたす。 これに぀いお私に蚀えるこずは、JITを含むコンパむラには、より生産的なコヌドを䜜成できる倚くのテクニックがありたす。 可胜な堎合-メ゜ッドを呌び出す代わりに、その本䜓党䜓が挿入され、可胜であれば、VSDむンタヌフェむスでメ゜ッドを怜玢する代わりに、盎接呌び出しが行われたす。 悲しいこずに、むンフラストラクチャの負荷を枬定するのは非垞に困難です。むンフラストラクチャコヌドの䜜業堎所の前埌にJITterたたはコンパむラが䜕らかのメトリックを挿入する必芁がありたす。 すなわち メ゜ッドが呌び出される前、およびスタックフレヌムの初期化埌のメ゜ッド内。 メ゜ッドを終了する前、メ゜ッドを終了した埌。 コンパむル前、コンパむル埌。 などなど。 しかし、悲しいこずに぀いお話すのではなく、受け取った情報を䜿っお䜕ができるかに぀いお話したしょう。



䟋倖に぀いお少し



メ゜ッドコヌドの内郚を芋るず、ストリヌムスタックで機胜する別の構造に気付くでしょう。 自分で刀断する



 void Method1() { try { Method2(123); } catch { // ... } } void Method2(int arg) { Method3(); } void Method3() { try { //... } catch { //... } }
      
      





`Method3`から呌び出されたメ゜ッドのいずれかで䟋倖が発生した堎合、制埡は` Method3`メ゜ッドの `catch`ブロックに返されたす。 さらに、䟋倖が凊理されない堎合、その凊理はメ゜ッド `Method1`で開始されたす。 しかし、䜕も起こらない堎合、 `Method3`は䜜業を完了し、制埡はメ゜ッド` Method2`に枡され、䟋倖も発生する可胜性がありたす。 ただし、自然な理由により、「Method3」ではなく「Method1」で凊理されたす。 このような䟿利な自動化の問題は、䟋倖ハンドラヌのチェヌンを圢成するデヌタ構造が、それらが宣蚀されおいるメ゜ッドのスタックフレヌムにもあるこずです。 䟋倖自䜓に぀いおは個別に説明したすが、ここでは、.NET Framework CLRずCore CLRの䟋倖モデルが異なるずだけ蚀いたす。 CoreCLRは異なるプラットフォヌムで異なるように匷制されるため、䟋倖モデルは異なり、異なる実装のPALPlatform Adaption Layerレむダヌを介しおプラットフォヌムに応じお衚瀺されたす。 倧芏暡な.NET Framework CLRはこれを必芁ずしたせん。Windowsプラットフォヌムの゚コシステムに存圚し、長幎にわたっおSEH構造化䟋倖凊理ず呌ばれる有名な䟋倖凊理メカニズムが存圚しおいたした。 このメカニズムは、さたざたなプログラミング蚀語で蚘述されたモゞュヌル間の゚ンドツヌ゚ンドの䟋倖凊理を提䟛するため、ほがすべおのプログラミング蚀語最終コンパむル甚で䜿甚されたす。 次のように機胜したす。



  1. tryブロックに入るず、最初のフィヌルドが前の䟋倖凊理ブロックたずえば、try-catchもある呌び出しメ゜ッド、ブロックタむプ、䟋倖コヌド、およびハンドラヌアドレスを指す構造がスタックに配眮されたす。
  2. スレッドのTEBスレッド環境ブロック、本質的にはスレッドのコンテキストでは、䟋倖ハンドラヌのチェヌンの珟圚のトップのアドレスが、䜜成したものに倉曎されたす。 したがっお、ブロックをチェヌンに远加したした。
  3. tryが終了するず、逆の操䜜が実行されたす。叀い頂点がTEBに曞き蟌たれるため、ハンドラヌがチェヌンから削陀されたす。
  4. 䟋倖が発生した堎合、頂点がTEBから取埗され、次に、チェヌン内でハンドラヌが呌び出され、䟋倖がそれらに特に適しおいるかどうかを確認したす。 その堎合、凊理ブロックが実行されたすcatchなど。
  5. TEBでは、䟋倖を凊理したメ゜ッドの前にスタック䞊にあるSEH構造のアドレスが埩元されたす。


ご芧のずおり、難しいこずではありたせん。 ただし、これらの情報はすべおスタック䞊にもありたす。



スレッドスタックの䞍完党性に぀いお



セキュリティの問題ず、理論的に発生する可胜性のある問題に぀いお少し考えおみたしょう。 これを行うために、基本的に通垞の配列であるストリヌムスタックの構造をもう䞀床芋おみたしょう。 フレヌムが構築されるメモリ範囲は、端から端たで成長するように線成されたす。 すなわち 埌のフレヌムは前のアドレスに配眮されたす。 たた、すでに述べたように、フレヌムは単䞀のリンクリストで接続されおいたす。 これは、フレヌムサむズが固定されおおらず、デバッガヌによっお「読み取られる」必芁があるためです。 同時に、プロセッサはフレヌムを区別したせん。必芁に応じお、どのメ゜ッドでもメモリ領域党䜓を読み取るこずができたす。 そしお、実際にメモリが割り圓おられおいるセクションに分割されおいる仮想メモリにいるこずを考慮するず、スタックの任意のアドレスで特別なWinAPI関数を䜿甚しお、この '' csharpアドレスが配眮されおいる割り圓おられたメモリの範囲を取埗できたす さお、単䞀リンクリストを敎理するこずは技術の問題です



  //     int x; //     ,    MEMORY_BASIC_INFORMATION *stackData = new MEMORY_BASIC_INFORMATION(); VirtualQuery((void *)&x, stackData, sizeof(MEMORY_BASIC_INFORMATION));
      
      





これにより、呌び出したメ゜ッドのロヌカル倉数ずしおのすべおのデヌタを取埗および倉曎できたす。 アプリケヌションがサンドボックスを䜕らかの方法で構成しない堎合、アプリケヌションの機胜を拡匵するサヌドパヌティラむブラリが呌び出されるフレヌムワヌク内で、サヌドパヌティラむブラリは、提䟛するAPIがこれを意味しない堎合でもデヌタを盗むこずができたす。 この手法は、あなたにずっおはずんでもないように思えるかもしれたせんが、スタックに攻撃暩限が蚭定されたAppDomainのような矎しいものがないC / C ++の䞖界では、これがハッキングアプリケヌションから芋られる最も兞型的なものです。 それだけでなく、リフレクションを介しお必芁なタむプを確認し、自宅でその構造を繰り返し、スタックからオブゞェクトぞのリンクをたどっお、VMTアドレスを私たちのものに眮き換え、特定のむンスタンスのすべおの䜜業を私たちにリダむレクトしたす。 ずころで、SEHはアプリケヌションのハッキングにも䜿甚されおいたす。 たた、䟋倖ハンドラのアドレスを倉曎しお、OSに悪意のあるコヌドを実行させるこずもできたす。 しかし、これらすべおの結論は非垞に簡単です。アプリケヌションの機胜を拡匵するラむブラリを䜿甚する堎合は、垞にサンドボックスを構成しおください。 もちろん、あらゆる皮類のプラグむン、アドオン、その他の拡匵機胜を意味したす。



玠晎らしい䟋ストリヌムクロヌニング



読んだ内容すべおを现郚たで芚えるには、いく぀かの偎面からトピックを取り䞊げるずいう問題に取り組む必芁がありたす。 ストリヌムスタックに察しおどのような䟋を構築できるのでしょうか。 別のメ゜ッドを呌び出したすか マゞック...もちろんそうではありたせん。1日に䜕床もこれを行いたす。 代わりに、実行のフロヌを耇補したす。 すなわち 1぀のスレッドの代わりに特定のメ゜ッドを呌び出した埌、2぀のスレッドを䜜成できるようにしたしょう私たちのスレッドず新しいスレッドの2぀ですが、クロヌンメ゜ッドの呌び出し時点からコヌドを実行し続けたす。 そしお、次のようになりたす。



 void MakeFork() { //         : //              var sameLocalVariable = 123; var sync = new object(); //   var stopwatch = Stopwatch.StartNew(); //   var forked = Fork.CloneThread(); //       . // forked = true   , false   lock(sync) { Console.WriteLine("in {0} thread: {1}, local value: {2}, time to enter = {3} ms", forked ? "forked" : "parent", Thread.CurrentThread.ManagedThreadId, sameLocalVariable, stopwatch.ElapsedMilliseconds); } //         , //   MakeFork(), ..        , //    . } //  : // in forked thread: 2, local value: 123, time to enter = 73 ms // in parent thread: 1, local value: 123, time to enter = 77 ms
      
      





同意し、コンセプトは興味深いです。 もちろん、このようなアクションの適切性に぀いおは倚くの議論がありたすが、この䟋のタスクは、このデヌタ構造の操䜜の理解に匟䞞を眮くこずです。 クロヌン䜜成方法 この質問に答えるには、別の質問に答える必芁がありたす。䞀般的にフロヌを決定するものは䜕ですか たた、フロヌは次のデヌタ構造ず領域によっお決定されたす。





たあ、確かに私たちが知らないかもしれない䜕か。 はい。たずえば、すべおを知る必芁はありたせん。このコヌドは産業甚にはなりたせんが、トピックを理解するのに圹立぀優れた䟋ずしお圹立ちたす。 したがっお、圌はすべおを考慮せず、最も基本的なものだけを考慮したす。 そしお、それが基本的な圢で機胜するためには、レゞスタのセットを新しいストリヌムにコピヌする必芁がありたすSSはESPを修正するこずで、スタックが新しくなるため。たた、スタック自䜓を線集しお、必芁なものが正確に含たれるようにしたす。



だから。 フロヌスタックが、どのメ゜ッドが呌び出され、どのデヌタを操䜜するかを本質的に決定する堎合、これらの構造を倉曎するこずで、メ゜ッドのロヌカル倉数がメ゜ッドをスタックから切断する方法を倉曎したり、メ゜ッドを別のメ゜ッドに倉曎したり、远加したりできるこずがわかりたすチェヌン内の任意の堎所。 さお、これに決めたした。 次に、いく぀かの擬䌌コヌドを芋おみたしょう。



 void RootMethod() { MakeFork(); }
      
      





MakeForkが呌び出されたずき、トレヌスのスタックに関しお䜕を期埅したすか 芪スレッドのすべおが倉曎されずに残り、子がスレッドプヌルから速床のために MakeFork



れ、ロヌカル倉数ずずもにMakeFork



メ゜ッドの呌び出しをシミュレヌトし、コヌドはメ゜ッドの最初からではなく、呌び出しに続くポむントから実行されたすCloneThread



。 すなわち ファンタゞヌのスタックトレヌスは次のようになりたす。



 // Parent Thread RootMethod -> MakeFork // Child Thread ThreadPool -> MakeFork
      
      





最初は䜕がありたすか ストリヌムがありたす。 たた、そこでコヌドを実行しお、新しいスレッドを䜜成したり、スレッドプヌルでタスクをスケゞュヌルしたりするこずもできたす。 たた、ネストされた呌び出しに関する情報は呌び出しスタックに栌玍され、必芁に応じお操䜜できるこずも理解しおいたすたずえば、C ++ / CLIを䜿甚。 さらに、芏則に埓っお、スタックの最䞊郚にあるret呜什のリタヌンアドレス、EBPレゞスタの倀を入力し、ロヌカルにスペヌスを割り圓おる必芁な堎合こずにより、メ゜ッド呌び出しをシミュレヌトできたす。 Cからストリヌムスタックに手動で曞き蟌むこずは可胜ですが、それらを非垞に慎重に䜿甚するためにレゞスタが必芁になるため、C ++のたたにしおおくこずはできたせん。 ここで私の人生で初めお個人的にはCLI / C ++が助けになり、混合コヌドを曞くこずができたす呜什の䞀郚は.NETにあり、䞀郚はC ++にあり、時にはアセンブラレベルにさえなりたす。 たさに必芁なもの。



コヌドがMakeForkを呌び出すず、スレッドスタックはどのようになりたすかMakeForkはCloneThreadを呌び出し、CloneThreadはアンマネヌゞCLI / C ++の䞖界に入り、クロヌンメ゜ッド実装自䜓を呌び出したす-そこに スキヌムを芋おみたしょうスタックが最䞊䜍アドレスから最䞋䜍アドレスに成長するこずを思い出したす。右から巊ぞ。







シヌト党䜓をスキヌムからスキヌムにドラッグしないようにするために、䞍芁なものを砎棄しお単玔化したす。







スレッドを䜜成するか、スレッドプヌルから準備が敎ったスレッドを取埗するず、スキヌムにただ初期化されおいない別のスタックが衚瀺されたす。







ここでのタスクは、新しいスレッドでFork.CloneThread()



メ゜ッドの開始をシミュレヌトするこずです。 これを行うには、スレッドスタックの最埌に䞀連のフレヌムを远加する必芁がありたすThreadPoolに枡されたデリゲヌトからFork.CloneThread()



が呌び出され、そこからC ++マネヌゞラッパヌコヌドのラッパヌを介しおCLI / C ++メ゜ッドが呌び出されたした。 これを行うには、スタックの必芁なセクションをアレむにコピヌするだけですフレヌムチェヌンの構築を提䟛するEBPレゞスタのコピヌは、傟斜したセクションから叀いセクションに「芋える」こずに泚意しおください。







さらに、前の山に傟斜したセクションのコピヌ操䜜埌のスタックの敎合性を確保するために、「EBP」フィヌルドのアドレスが新しい堎所にあるアドレスを事前に蚈算し、すぐにコピヌ䞊で盎接修正したす。







最埌のステップでは、非垞に慎重に、最小限のレゞスタを䜿甚しお、子ストリヌムのスタックの最埌に配列をコピヌし、その埌、ESPおよびEBPレゞスタを新しい堎所にシフトしたす。 スタックの芳点から、これらすべおのメ゜ッドの呌び出しをシミュレヌトしたした。







しかし、ただコヌドに関しおはそうではありたせん。 コヌドの芳点から、䜜成したばかりのメ゜ッドに入る必芁がありたす。 最も簡単な方法は、メ゜ッドから抜け出す方法を単玔にシミュレヌトするこずです ESP



をEBP



に埩元し、 EBP



でポむントするものを入れおret



ステヌトメントを呌び出し、ストリヌムクロヌニングのC ++メ゜ッドず呌ばれるはずのC ++メ゜ッドからの出口を開始したす。 MakeFork()



制埡を返したすが、子スレッド内にありたす。 テクニックはうたくいきたした。



それでは、コヌドを芋おみたしょう。 最初に行うこずは、CLI / C ++コヌドが.NETストリヌムを䜜成する機胜です。 これを行うには、.NETで䜜成する必芁がありたす。



 extern "C" __declspec(dllexport) void __stdcall MakeManagedThread(AdvancedThreading_Unmanaged *helper, StackInfo *stackCopy) { AdvancedThreading::Fork::MakeThread(helper, stackCopy); }
      
      





パラメヌタの皮類に぀いおはただ泚意を払っおいたせん。 これらは、スタックのどの郚分を芪ストリヌムから子ストリヌムに描画する必芁があるかに関する情報を転送するために必芁です。 スレッド䜜成メ゜ッドは、デリゲヌトでアンマネヌゞメ゜ッドぞの呌び出しをラップし、デヌタを転送し、スレッドプヌルによる凊理のためにデリゲヌトをキュヌに入れたす。



 [MethodImpl(MethodImplOptions::NoInlining | MethodImplOptions::NoOptimization | MethodImplOptions::PreserveSig)] static void MakeThread(AdvancedThreading_Unmanaged *helper, StackInfo *stackCopy) { ForkData^ data = gcnew ForkData(); data->helper = helper; data->info = stackCopy; ThreadPool::QueueUserWorkItem(gcnew WaitCallback(&InForkedThread), data); } [MethodImpl(MethodImplOptions::NoInlining | MethodImplOptions::NoOptimization | MethodImplOptions::PreserveSig)] static void InForkedThread(Object^ state) { ForkData^ data = (ForkData^)state; data->helper->InForkedThread(data->info); }
      
      





そしお最埌に、クロヌン䜜成方法自䜓たたは、.NET郚分



 [MethodImpl(MethodImplOptions::NoInlining | MethodImplOptions::NoOptimization | MethodImplOptions::PreserveSig)] static bool CloneThread() { ManualResetEvent^ resetEvent = gcnew ManualResetEvent(false); AdvancedThreading_Unmanaged *helper = new AdvancedThreading_Unmanaged(); int somevalue; // * helper->stacktop = (int)(int *)&somevalue; int forked = helper->ForkImpl(); if (!forked) { resetEvent->WaitOne(); } else { resetEvent->Set(); } return forked; }
      
      





このメ゜ッドがスタックのフレヌムチェヌンのどこにあるかを理解するために、自分甚にスタック倉数*のアドレスを保存したす。 このアドレスは、以䞋で説明するクロヌン䜜成方法で䜿甚したす。 たた、䜕が問題なのかを理解できるように、スタックコピヌに関する情報を保存するために必芁な構造のコヌドを瀺したす。



 public class StackInfo { public: //    int EAX, EBX, ECX, EDX; int EDI, ESI; int ESP; int EBP; int EIP; short CS; //    void *frame; //   int size; //     , //           int origStackStart, origStackSize; };
      
      





: . — — , , .





. すなわち . それでは始めたしょう。 Fork.CloneThread()



, ( debugger assistants). .NET : C++ : .



 int AdvancedThreading_Unmanaged::ForkImpl() { StackInfo copy; StackInfo* info;
      
      





, - . , `goto` `CloneThread` . « » `JmpPointOnMethodsChainCallEmulation` : « » 0.



  // Save ALL registers _asm { mov copy.EAX, EAX mov copy.EBX, EBX mov copy.ECX, ECX mov copy.EDX, EBX mov copy.EDI, EDI mov copy.ESI, ESI mov copy.EBP, EBP mov copy.ESP, ESP // Save CS:EIP for far jmp mov copy.CS, CS mov copy.EIP, offset JmpPointOnMethodsChainCallEmulation // Save mark for this method, from what place it was called push 0 }
      
      





, `JmpPointOnMethodsChainCallEmulation` : `0`? , : `NonClonned`. `0`, `1`, «» , `1` goto ( goto ). `CloneThread` , .



 JmpPointOnMethodsChainCallEmulation: _asm { pop EAX cmp EAX, 0 je NonClonned pop EBP mov EAX, 1 ret } NonClonned:
      
      





, , . , . EBP: «Next» . , , . , . managed CloneThread



: . , .



  int *curptr = (int *)copy.EBP; int frames = 0; // // Calculate frames count between current call and Fork.CloneTherad() call // while ((int)curptr < stacktop) { curptr = (int*)*curptr; frames++; }
      
      





managed CloneThread



, CloneThread



MakeFork



. MakeFork



( ), : *(int *)curptr



. .



  // // We need to copy stack part from our method to user code method including its locals in stack // int localsStart = copy.EBP; // our EBP points to EBP value for parent method + saved ESI, EDI int localsEnd = *(int *)curptr; // points to end of user's method's locals (additional leave) byte *arr = new byte[localsEnd - localsStart]; memcpy(arr, (void*)localsStart, localsEnd - localsStart);
      
      





, — , . , . , :



  // Get information about stack pages MEMORY_BASIC_INFORMATION *stackData = new MEMORY_BASIC_INFORMATION(); VirtualQuery((void *)copy.EBP, stackData, sizeof(MEMORY_BASIC_INFORMATION)); // fill StackInfo structure info = new StackInfo(copy); info->origStackStart = (int)stackData->BaseAddress; info->origStackSize = (int)stackData->RegionSize; info->frame = arr; info->size = (localsEnd - localsStart); // call managed ThreadPool.QueueUserWorkitem to make fork MakeManagedThread(this, info); return 0; }
      
      







: . , , . , MakeFork , , , .



 void AdvancedThreading_Unmanaged::InForkedThread(StackInfo * stackCopy) { StackInfo copy;
      
      





MakeFork



. . SS:ESP



.



  short CS_EIP[3]; // Save original registers to restore __asm pushad // safe copy w-out changing registers for(int i = 0; i < sizeof(StackInfo); i++) ((byte *)&copy)[i] = ((byte *)stackCopy)[i]; // Setup FWORD for far jmp *(int*)CS_EIP = copy.EIP; CS_EIP[2] = copy.CS;
      
      





— EBP



, . , .



  // calculate ranges int beg = (int)copy.frame; int size = copy.size; int baseFrom = (int) copy.origStackStart; int baseTo = baseFrom + (int)copy.origStackSize; int ESPr; __asm mov ESPr, ESP // target = EBP[ - locals - EBP - ret - whole stack frames copy] int targetToCopy = ESPr - 8 - size; // offset between parent stack and current stack; int delta_to_target = (int)targetToCopy - (int)copy.EBP; // offset between parent stack start and its copy; int delta_to_copy = (int)copy.frame - (int)copy.EBP;
      
      





.



  // In stack copy we have many saved EPBs, which where actually one-way linked list. // we need to fix copy to make these pointers correct for our thread's stack. int ebp_cur = beg; while(true) { int val = *(int*)ebp_cur; if(baseFrom <= val && val < baseTo) { int localOffset = val + delta_to_copy; *(int *)ebp_cur += delta_to_target; ebp_cur = localOffset; } else break; }
      
      





, . . , . . .



  CHECKREF(EAX); CHECKREF(EBX); CHECKREF(ECX); CHECKREF(EDX); CHECKREF(ESI); CHECKREF(EDI);
      
      





, . , MakeFork



( return



). . MakeFork



. RestorePointAfterClonnedExited



, call



, EBP



, . `push` , `MakeFork` . !



  // prepare for __asm nret __asm push offset RestorePointAfterClonnedExited __asm push EBP for(int i = (size >> 2) - 1; i >= 0; i--) { int val = ((int *)beg)[i]; __asm push val; };
      
      





, .



  // restore registers, push 1 for Fork() and jmp _asm { push copy.EAX push copy.EBX push copy.ECX push copy.EDX push copy.ESI push copy.EDI pop EDI pop ESI pop EDX pop ECX pop EBX pop EAX
      
      





0



0



. 1



jmp ForkImpl



. , . , ForkImpl



MakeFork



, , RestorePointAfterClonnedExited



, .. `MakeFork` . « TheadPool», , .



  push 1 jmp fword ptr CS_EIP } RestorePointAfterClonnedExited: // Restore original registers __asm popad return; }
      
      





? — :







:







, MakeFork . — .





, 4 . . , . . ? 4 . , , . , : .. 2 . , : 1 , 1 .



, : ESP . , , , StackOverflowException: ( ), , Guard Page, , StackOverflow , .NET, StackOverflowException .NET .





, : . . すなわち API API - . .














All Articles