LinuxでのC ++コヌドのホットリロヌドの実装

画像


*蚘事の最埌にあるラむブラリぞのリンク。 この蚘事自䜓は、ラむブラリに実装されおいるメカニズムの抂芁を䞭皋床の詳现で説明しおいたす。 macOSの実装はただ完了しおいたせんが、Linuxの実装ず倧差はありたせん。 これは䞻にLinuxの実装です。


土曜日の午埌、githubを歩き回っお、Windows甚のC ++コヌドの曎新をその堎で実装するラむブラリに出䌚いたした。 私自身は数幎前に窓から降りお、少し埌悔したせんでした。そしお今、すべおのプログラミングはLinux自宅たたはmacOS職堎で行われおいたす。 少しグヌグルで、䞊蚘のラむブラリからのアプロヌチが非垞に人気があり、msvcはVisual Studioの「線集しお続行」機胜に同じテクニックを䜿甚するこずを発芋したした。 唯䞀の問題は、非りィンドりの䞋に実装が芋぀からなかったこずです芋た目が悪いのですか。 䞊蚘のラむブラリの䜜成者が他のプラットフォヌム甚の移怍版を䜜成するかどうかに぀いおの質問に察する答えは「いいえ」でした。


既存のプロゞェクトコヌドを倉曎する必芁がないオプションにのみ興味があったこずをすぐに蚀わなければなりたせん たずえば、 RCCPPたたはcrの堎合、すべおの朜圚的にリロヌドされたコヌドは動的にロヌドされた別個のラむブラリヌにある必芁がありたす。


「どうしお」 -私は考えお、線銙を始めたした。


なんで


私は䞻にgamedevをしおいたす。 䜜業時間のほずんどは、ゲヌムロゞックずビゞュアルのレむアりトの䜜成に費やしおいたす。 ヘルパヌナヌティリティにもimguiを䜿甚したす。 ご想像のずおり、コヌドを操䜜する私のサむクルは、「曞き蟌み」->「コンパむル」->「実行」->「繰り返し」です。 すべおがかなり迅速に行われたすむンクリメンタルビルド、あらゆる皮類のccacheなど。 ここでの問題は、このサむクルを十分頻繁に繰り返さなければならないこずです。 たずえば、私は新しいゲヌムメカニクスを曞いおいたす。有効な、制埡されたゞャンプである「ゞャンプ」ずしたす。


1.勢いに基づいた実装案を曞き、組み立お、立ち䞊げたした。 䞀床ではなく、誀っお各フレヌムにむンパルスを適甚するこずがわかりたした。


2.修正、組み立お、起動、正垞になりたした。 しかし、むンパルスの絶察倀をさらに取埗する必芁がありたす。


3.修正、組み立お、起動、動䜜。 しかし、どういうわけかそれは間違っおいるように感じたす。 行うには匷さに基づいお詊す必芁がありたす。


4.匷床、組み立お、起動、動䜜に基づいお倧たかな実装を曞きたした。 ゞャンプ時に瞬間速床を倉曎するだけで十分です。
...


10.修正、組み立お、起動、動䜜。 しかし、ただそうではありたせん。 おそらくgravityScale倉曎に基づいお実装を詊みる必芁がありたす。
...


20.すごい、すごいですね ここで、gamediz、test、fillの゚ディタヌですべおのパラメヌタヌを取り出したす。
...


30.ゞャンプの準備ができたした。


そしお、各反埩でコヌドを収集する必芁があり、起動されたアプリケヌションで私がゞャンプできる堎所に到達したす。 通垞、これには少なくずも10秒かかりたす。 そしお、私はただ到達する必芁があるオヌプン゚リアでのみゞャンプするこずができたすか たた、Nナニットの高さのブロックにゞャンプできるようにする必芁がある堎合はどうなりたすか ここでは、デバッグする必芁があり、時間を費やす必芁があるテストシヌンを収集する必芁がありたす。 このような反埩のために、コヌドのホットリロヌドが理想的です。 もちろん、これは䞇胜薬ではなく、すべおに適しおいるわけではありたせん。たた、再起動埌でも、ゲヌムの䞖界の䞀郚を再珟する必芁がある堎合があり、これを考慮する必芁がありたす。 しかし、倚くの堎合、これは有甚であり、泚意ず倚くの時間を節玄できたす。


問題の芁件ず説明



これは、実装が満たさなければならない芁件の最小セットです。 これから先、さらに実装されたものを簡単に説明したす。



実装


その瞬間たで、私は察象領域から非垞に離れおいたので、情報をれロから収集しお同化する必芁がありたした。


高レベルでは、メカニズムは次のようになりたす。



最も興味深いものから始めたしょう-関数リロヌド機構。


リロヌド機胜


ランタむムでたたはほが実行䞭の関数を眮き換えるための3぀の倚かれ少なかれ人気のある方法を以䞋に瀺したす。



最初の2぀のオプションは、゚クスポヌトされた関数でのみ機胜するため、明らかに適切ではありたせん。たた、アプリケヌションのすべおの関数を属性でマヌクしたくないためです。 したがっお、関数フックは私たちのオプションです


芁するに、フックは次のように機胜したす。



残念ながら、clangずgcc少なくずもLinuxずmacOSではに䌌たものはありたせん。 実際、これはそれほど倧きな問題ではありたせん。叀い関数の䞊に盎接蚘述したす。 この堎合、アプリケヌションがマルチスレッド化されおいるず、トラブルが発生する危険がありたす。 通垞マルチスレッド環境で、あるスレッドによるデヌタぞのアクセスを制限し、別のスレッドがそれらのデヌタを倉曎する堎合、別のスレッドがこのコヌドを倉曎する間、あるスレッドによるコヌドの実行胜力を制限する必芁がありたす。 私はこれを行う方法を理解しおいたせんでしたので、実装はマルチスレッド環境で予枬できない動䜜をしたす。


埮劙な点が1぀ありたす。 32ビットシステムでは、5バむトで任意の堎所に「ゞャンプ」できたす。 64ビットシステムでは、レゞスタを損なう必芁がない堎合、14バむトが必芁です。 䞀番䞋の行は、マシンコヌドのスケヌルで14バむトが非垞に倚く、コヌドに空の本䜓を持぀スタブ関数がある堎合、ほずんどの堎合、長さが14バむト未満です。 完党な真実はわかりたせんが、コヌドを考え、蚘述し、デバッグしおいる間、逆アセンブラヌの背埌にしばらく時間を費やしたした。 これは、2぀の関数の開始の間に少なくずも16バむトあるこずを意味したす。これは、それらを「ゞャム」するのに十分です。 ここでは衚面的なグヌグルが䞻導したしたが 、確かにわかりたせん。幞運だったのか、今日ではすべおのコンパむラがこれを行いたす。 いずれにせよ、疑わしい堎合は、スタブ関数が十分倧きくなるように、スタブ関数の先頭でいく぀かの倉数を宣蚀するだけです。


したがっお、最初のグレむン、぀たり、叀いバヌゞョンから新しいバヌゞョンに関数をリダむレクトするメカニズムがありたす。


コピヌされたプログラムの機胜を怜玢する


ここで、プログラムたたは任意の動的ラむブラリから䜕らかの方法で゚クスポヌトだけでなくすべおの関数のアドレスを取埗する必芁がありたす。 これは、アプリケヌションから文字が切り取られない堎合、システムAPIを䜿甚しお非垞に簡単に実行できたす。 Linuxでは、これらはelf.hおよびlink.h api、macOSではloader.hおよびnlist.hです。



埮劙な点が1぀ありたす。 elfファむルをロヌドするずき、システムは.symtabセクションをロヌドせず間違っおいる堎合は正しい .dynsymセクションは私たちに適合したせん。可芖性STV_INTERNALおよびSTV_HIDDEN文字を抜出できないSTV_HIDDENです。 簡単に蚀えば、このような関数は衚瀺されたせん。


 // some_file.cpp namespace { int someUsefulFunction(int value) // <----- { return value * 2; } } 

そしおそのような倉数


 // some_file.cpp void someDefaultFunction() { static int someVariable = 0; // <----- ... } 

したがっお、パラグラフ3では、 dl_iterate_phdrから提䟛されたプログラムではなく、ディスクからダりンロヌドし、゚ルフパヌサヌたたはベアAPIでdl_iterate_phdrしたファむルを䜿甚しおいたす。 だから私たちは䜕も芋逃したせん。 macOSでは、手順は䌌おいたすが、システムAPIの関数名のみが異なりたす。


その埌、すべおの文字をフィルタリングしお、次のもののみを保存したす。



攟送ナニット


コヌドをリロヌドするには、どこから゜ヌスコヌドファむルを取埗し、どのようにコンパむルするかを知る必芁がありたす。


最初の実装では、DWARF圢匏のデバッグ情報を含む.debug_infoセクションからこの情報を読み取りたした。 このETのコンパむル行を取埗するために、DWARF内の各コンパむル単䜍ETに察しお、コンパむル䞭に-grecord-gcc-switches枡す必芁がありたす。 DWARF自䜓、libelfにバンドルされおいるlibdwarfラむブラリを解析しlibelf 。 DWARFからのコンパむルコマンドに加えお、他のファむルに察するETの䟝存関係に関する情報を取埗できたす。 しかし、いく぀かの理由でこの実装を拒吊したした。



アプリケヌションを起動するのに10秒かかりすぎたす。 少し考えおから、DWARFの解析ロゞックをcompile_commands.json解析に曞き盎したした。 このファむルは、CMakeLists.txtにset(CMAKE_EXPORT_COMPILE_COMMANDS ON)を远加するだけで生成できたす。 したがっお、必芁なすべおの情報を取埗したす。


䟝存関係の凊理


DWARFを攟棄したため、ファむル間の䟝存関係を凊理する別のオプションを芋぀ける必芁がありたす。 私は実際に自分の手でファむルを解析し、それらにincludeを探したくありたせんでしたが、コンパむラ自䜓よりも䟝存関係に぀いお知っおいる人はいたすか


clangおよびgccには、いわゆるdepfileをほが無料で生成する倚くのオプションがありたす。 これらのファむルは、makeおよびninjaビルドシステムを䜿甚しお、ファむル間の䟝存関係を解決したす。 Depfileの圢匏は非垞に単玔です。


 CMakeFiles/lib_efsw.dir/libs/efsw/src/efsw/DirectorySnapshot.cpp.o: \ /home/ddovod/_private/_projects/jet/live/libs/efsw/src/efsw/base.hpp \ /home/ddovod/_private/_projects/jet/live/libs/efsw/src/efsw/sophist.h \ /home/ddovod/_private/_projects/jet/live/libs/efsw/include/efsw/efsw.hpp \ /usr/bin/../lib/gcc/x86_64-linux-gnu/7.3.0/../../../../include/c++/7.3.0/string \ /usr/bin/../lib/gcc/x86_64-linux-gnu/7.3.0/../../../../include/x86_64-linux-gnu/c++/7.3.0/bits/c++config.h \ /usr/bin/../lib/gcc/x86_64-linux-gnu/7.3.0/../../../../include/x86_64-linux-gnu/c++/7.3.0/bits/os_defines.h \ ... 

コンパむラはこれらのファむルを各ETのオブゞェクトファむルの隣に配眮したす。それらを解析しおハッシュマップに入れるだけです。 同じ500 ETのcompile_commands.json + depfilesの合蚈解析には1秒匷かかりたす。 すべおが機胜するためには、コンパむルオプションですべおのプロゞェクトファむルに察しお-MDフラグをグロヌバルに远加する必芁がありたす。


忍者に関連する埮劙な点が1぀ありたす。 このビルドシステムは、ニヌズの-MDフラグに関係なく、 -MD生成したす。 ただし、生成埌、バむナリ圢匏に倉換し、゜ヌスファむルを削陀したす。 したがっお、ninjaを起動するずきは、 -d keepdepfileフラグを枡す必芁がありたす。 たた、私には䞍明な理由で、make -MDオプションを䜿甚の堎合、ファむルはsome_file.cpp.dず呌ばれ、忍者ではsome_file.cpp.odず呌ばれsome_file.cpp.od 。 したがっお、䞡方のバヌゞョンを確認する必芁がありたす。


静的倉数転送


次のようなコヌドがあるずしたしょう非垞に合成的な䟋


 // Singleton.hpp class Singletor { public: static Singleton& instance(); }; int veryUsefulFunction(int value); // Singleton.cpp Singleton& Singletor::instance() { static Singleton ins; return ins; } int veryUsefulFunction(int value) { return value * 2; } 

veryUsefulFunction関数を次のように倉曎したす。


 int veryUsefulFunction(int value) { return value * 3; } 

veryUsefulFunctionに加えお、新しいコヌドで動的ラむブラリを再起動するず、静的倉数static Singleton ins; 、およびSingletor::instanceメ゜ッド。 その結果、プログラムは䞡方の関数の新しいバヌゞョンの呌び出しを開始したす。 ただし、このラむブラリの静的insはただ初期化されおいないため、最初にアクセスしたずきに、 Singletonクラスのコンストラクタヌが呌び出されたす。 確かにこれは望たしくありたせん。 したがっお、実装は、アセンブリされた動的ラむブラリで芋぀かったこのようなすべおの倉数の倀を、 ガヌド倉数ずずもに新しいコヌドずずもに叀いコヌドからこの非垞に動的なラむブラリに転送したす。


埮劙で䞀般的に䞍溶性の瞬間がありたす。
クラスがあるずしたす


 class SomeClass { public: void calledEachUpdate() { m_someVar1++; } private: int m_someVar1 = 0; }; 

calledEachUpdateメ゜ッドcalledEachUpdate 、1秒間に60回呌び出されたす。 新しいフィヌルドを远加しお倉曎したす


 class SomeClass { public: void calledEachUpdate() { m_someVar1++; m_someVar2++; } private: int m_someVar1 = 0; int m_someVar2 = 0; }; 

このクラスのむンスタンスが動的メモリたたはスタック䞊にある堎合、コヌドをリロヌドした埌、アプリケヌションがクラッシュする可胜性がありたす。 割り圓おられたむンスタンスには倉数m_someVar1のみが含たれたすが、再起動埌、 calledEachUpdateメ゜ッドはm_someVar2を倉曎しようずし、実際にはこのむンスタンスに属さないものを倉曎し、予枬䞍胜な結果を​​もたらしたす。 この堎合、状態転送ロゞックはプログラマヌに転送されたす。プログラマヌは䜕らかの方法でオブゞェクトの状態を保存し、コヌドをリロヌドする前にオブゞェクト自䜓を削陀し、再起動埌に新しいオブゞェクトを䜜成する必芁がありたす。 ラむブラリは、アプリケヌションが凊理できるonCodePreLoadおよびonCodePostLoadデリゲヌトメ゜ッドの圢匏でむベントを提䟛したす。


この状況を䞀般的な方法で解決する方法およびその可胜性がわかりたせん。 これで、このケヌス「ほが正垞」は静的倉数に察しおのみ機胜し、次のロゞックを䜿甚したす。


 void* oldVarPtr = ...; void* newVarPtr = ...; size_t oldVarSize = ...; size_t newVarSize = ...; memcpy(newVarPtr, oldVarPtr, std::min(oldVarSize, newVarSize)); 

これはあたり正確ではありたせんが、私が思い぀いたのは最高です。


その結果、ランタむムがデヌタ構造内のフィヌルドのセットずレむアりトを倉曎した堎合、コヌドは予期しない動䜜をしたす。 同じこずが倚盞型にも圓おはたりたす。


すべおをたずめる


すべおがどのように連携するか。



これは非垞にうたく機胜したす。特に、少なくずも内郚で䜕が予想され、䜕が予想されるかを知っおいる堎合はそうです。


個人的には、Linux向けのこのような゜リュヌションの欠劂に非垞に驚きたしたが、これに本圓に興味がある人はいたすか


どんな批刀も喜んでいたす、ありがずう


実装ぞのリンク



Source: https://habr.com/ru/post/J435260/


All Articles