.NET CLRでのコードインジェクション:実行時のILコードの変更

まえがき





実行時にMSILコードの.NETメソッドを変更することは非常に便利です。 とてもクールなので、関数呼び出しを傍受(フック)して、ソフトウェアやその他の驚くべきものを保護できます。 それが私が長い間これをしたかった理由ですが、1つの問題がありました-このコードで何かをする前に、MSILコードはJITを使用してマシンコードにコンパイルされます。 また、.NET CLRは文書化されておらず、バージョンごとに変更されるため、メモリ内のアドレスの正確な位置に関係なく、安定した信頼できるパスを探します。



最後に、一週間の研究の後、私はそれをしました。 簡単な方法が用意されています。

protected string CompareOneAndTwo() { int a = 1; int b = 2; if (a < b) { return "Number 1 is less than 2"; } else { return "Number 1 is greater than 2 (O_o)"; } }
      
      





ご覧のとおり、「Number 1 is less than 2」を返します。 この誤解を修正して、返される結果が「Number 1 is than than 2(O_o)」になるようにこのメソッドを変更してみましょう。

このメソッドのMSILコードを見ると、Bge_SオペコードをBlt_Sに置き換えることで目標を達成できます。



そして、デモアプリケーションを実行すると、間違った結果が表示されます。



以下は、ILを置き換えるためのコードです。 コードを理解するのに十分なコメントがあると思います。

 //    Type type=this.GetType(); MethodInfo methodInfo=type.GetMethod("CompareOneAndTwo", BindingFlags.NonPublic|BindingFlags.Instance); // ,   ,  //     ,   ,  JIT //     ,       JIT-  :) RuntimeHelpers.PrepareMethod(methodInfo.MethodHandle); //  IL-   byte[] ilCodes=methodInfo.GetMethodBody().GetILAsByteArray(); //       //      for (int i=0; i<ilCodes.Length; i++) { if (ilCodes[i]==OpCodes.Bge_S.Value) { // Bge_S  Blt_S ilCodes[i]=(byte)OpCodes.Blt_S.Value; } } // IL- InjectionHelper.UpdateILCodes(methodInfo, ilCodes);
      
      





デモをダウンロードして試してみることができます。





コードの使用



必要なメソッドを含むInjectionHelper.csをプロジェクトにコピーします。

 public static class InjectionHelper { // Load the unmanaged injection.dll, the initlaization happens in a background thread // you can check if the initialization is completed by GetStatus() public static void Initialize() // Unload the unmanaged injection.dll public static void Uninitialize() // Update the IL Code of a Method. public static void UpdateILCodes(MethodInfo method, byte[] ilCodes) // The method returns until the initialization is completed public static Status WaitForIntializationCompletion() // Query the current status of the unmanaged dll, returns immediately. public static Status GetStatus() }
      
      





Injectionhelper :: Initializeメソッドは、アセンブリが配置されているディレクトリからアンマネージコードで構成されるinjection.dllを読み込むため、変更するすべてのファイルがそこにあるはずです。 または、必要に応じてソースを修正できます:)

ファイルリスト:

ファイル名 説明
Injection32.dll タスクを実行するアンマネージdll(x86バージョン)
Injection64.dll タスクを実行するアンマネージdll(x64バージョン)
EasyHook32.dll x86 EasyHook DLL(http://easyhook.codeplex.com/)(Injection32.dllを使用)
EasyHook64.dll X64 EasyHook DLL(http://easyhook.codeplex.com/)(Injection64.dllを使用)
x86 / * x86用のWindowsデバッグツール
x64 / * x64用のWindowsデバッグツール
PDB_symbols / * PDBファイル。 それらは削除できますが、これにより初期化が遅くなります。




舞台裏



まず、CLRとJITがどのように機能するかを見てみましょう。



JITを記述するライブラリ(.NET 4.0の場合はclr.dll / .NET 2.0+の場合はmscorwks.dll)は、_stdcall getJitメソッドを提供し、ICorJitCompilerインターフェイスを返します。

CLRを記述するライブラリ(.NET 4.0の場合はclr.dll / .NET 2.0+の場合はmscorwks.dll)は、getJitメソッドを呼び出してICorJitCompilerインターフェイスを取得します。

 CorJitResult compileMethod(ICorJitInfo * pJitInfo, CORINFO_METHOD_INFO * pMethodInfo, UINT nFlags, LPBYTE * pEntryAddress, ULONG * pSizeOfCode);
      
      





この部分は簡単です。compileMethodメソッドのアドレスを見つけて、それをEasyHookに置き換えるだけです。

 // ICorJitCompiler interface from JIT dll class ICorJitCompiler { public: typedef CorJitResult (__stdcall ICorJitCompiler::*PFN_compileMethod)(ICorJitInfo * pJitInfo, CORINFO_METHOD_INFO * pMethodInfo, UINT nFlags, LPBYTE * pEntryAddress, ULONG * pSizeOfCode); CorJitResult compileMethod(ICorJitInfo * pJitInfo, CORINFO_METHOD_INFO * pMethodInfo, UINT nFlags, LPBYTE * pEntryAddress, ULONG * pSizeOfCode) { return (this->*s_pfnComplieMethod)( pJitInfo, pMethodInfo, nFlags, pEntryAddress, pSizeOfCode); } private: static PFN_compileMethod s_pfnComplieMethod; }; //    LPVOID pAddr = tPdbHelper.GetJitCompileMethodAddress(); LPVOID* pDest = (LPVOID*)&ICorJitCompiler::s_pfnComplieMethod; *pDest = pAddr; //    compileMethod CorJitResult __stdcall CInjection::compileMethod(ICorJitInfo * pJitInfo , CORINFO_METHOD_INFO * pCorMethodInfo , UINT nFlags , LPBYTE * pEntryAddress , ULONG * pSizeOfCode ) { ICorJitCompiler * pCorJitCompiler = (ICorJitCompiler *)this; // TO DO:  IL-     CorJitResult result = pCorJitCompiler->compileMethod( pJitInfo, pCorMethodInfo, nFlags, pEntryAddress, pSizeOfCode); return result; } //    JIT-    NTSTATUS ntStatus = LhInstallHook( (PVOID&)ICorJitCompiler::s_pfnComplieMethod , &(PVOID&)CInjection::compileMethod , NULL , &s_hHookCompileMethod );
      
      





JITコンパイル済みメソッドのILコードの変更



上記のcompileMethodメソッドは、JITでコンパイルされたメソッドのCLRによって呼び出されません。 この問題を解決するために、CLRデータ構造を保存し、JITコンパイルの前にそれらを復元しました。 この場合、compileMethodが再度呼び出されるとすぐに、ILを置き換えることができます。

したがって、CLRの実装を少し見る必要があります。SSCLI(共有ソース共通言語インフラストラクチャ)は情報の優れたソースですが、かなり古くなっているため、コードで使用することはできません。



はい、この図は時代遅れですが、全体的な構造は保持されています。 .NETの各クラスには、メモリ内に少なくとも1つのMethodTable構造があります。 また、各MethodTable構造は、リフレクションおよびその他の目的のためにランタイム情報を格納するEEClassに関連付けられています。

各メソッドには、フラグ、スロットアドレス、入力アドレスなどに関する情報を含む少なくとも1つのMethodDesc構造があります。

メソッドがJITコンパイルされる前に、スロットはJMIコンバーターを指します。これにより、JITコンパイルが切り替わります。 ILコードがコンパイルされると、JMIへのポインターがスロットに書き込まれ、実行中にコードはコンパイルされたコードに直接移動します。

情報の構造を復元するには、最初にフラグをクリアしてから、エントリポイントのアドレスを一時的なものに変更する必要があります。 テスト中に、メモリを直接変更してこれを行いました。 しかし、これは少なくとも、データ構造のアドレスへの依存性があり、異なる.NETバージョンのコードが異なるためです。

私は正しい方法を探していましたが、幸いなことに、SSCLIソースコード(vm / method.cpp)にMethodDesc :: Resetメソッドが見つかりました。

 void MethodDesc::Reset() { CONTRACTL { THROWS; GC_NOTRIGGER; } CONTRACTL_END //      ,      . //            _ASSERTE(IsEnCMethod() || //     IsDynamicMethod() || GetLoaderModule()->IsReflection()); //    ClearFlagsOnUpdate(); if (HasPrecode()) { GetPrecode()->Reset(); } else { //      Reflection- _ASSERTE(GetLoaderModule()->IsReflection()); InterlockedUpdateFlags2(enum_flag2_HasStableEntryPoint | enum_flag2_HasPrecode, FALSE); *GetAddrOfSlotUnchecked() = GetTemporaryEntryPoint(); } _ASSERTE(!HasNativeCode()); }
      
      





ご覧のとおり、このコードは正しいことを行います。 したがって、JITコンパイルの前にMethodDescに対して呼び出す必要があります。

厳密に言えば、MicrosoftによってMethodDescが内部的に使用されているため、SSCLIのMethodDescを使用することはできません。

幸いなことに、この内部メソッドのアドレスはMicrosoft SymbolサーバーのPDB cに存在し、これが私の問題を解決します。 CLR DLLのReset()メソッドのアドレスは、PDBを解析するだけで見つけることができます!

現在、1つの重要なパラメーターが残っています-MethodDescへのこのポインター。 入手はそれほど難しくありません。 一般的に、MethodBase.MethodHandle.Value == CORINFO_METHOD_HANDLE == MethodDescアドレス== MethodDescへのこのポインター。

 class MethodDesc { typedef void (MethodDesc::*PFN_Reset)(void); typedef BOOL (MethodDesc::*PFN_IsGenericMethodDefinition)(void); typedef ULONG (MethodDesc::*PFN_GetNumGenericMethodArgs)(void); typedef MethodDesc * (MethodDesc::*PFN_StripMethodInstantiation)(void); typedef BOOL (MethodDesc::*PFN_HasClassOrMethodInstantiation)(void); typedef BOOL (MethodDesc::*PFN_ContainsGenericVariables)(void); typedef MethodDesc * (MethodDesc::*PFN_GetWrappedMethodDesc)(void); typedef AppDomain * (MethodDesc::*PFN_GetDomain)(void); typedef Module * (MethodDesc::*PFN_GetLoaderModule)(void); public: void Reset(void) { (this->*s_pfnReset)(); } BOOL IsGenericMethodDefinition(void) { return (this->*s_pfnIsGenericMethodDefinition)(); } ULONG GetNumGenericMethodArgs(void) { return (this->*s_pfnGetNumGenericMethodArgs)(); } MethodDesc * StripMethodInstantiation(void) { return (this->*s_pfnStripMethodInstantiation)(); } BOOL HasClassOrMethodInstantiation(void) { return (this->*s_pfnHasClassOrMethodInstantiation)(); } BOOL ContainsGenericVariables(void) { return (this->*s_pfnContainsGenericVariables)(); } MethodDesc * GetWrappedMethodDesc(void) { return (this->*s_pfnGetWrappedMethodDesc)(); } AppDomain * GetDomain(void) { return (this->*s_pfnGetDomain)(); } Module * GetLoaderModule(void) { return (this->*s_pfnGetLoaderModule)(); } private: static PFN_Reset s_pfnReset; static PFN_IsGenericMethodDefinition s_pfnIsGenericMethodDefinition; static PFN_GetNumGenericMethodArgs s_pfnGetNumGenericMethodArgs; static PFN_StripMethodInstantiation s_pfnStripMethodInstantiation; static PFN_HasClassOrMethodInstantiation s_pfnHasClassOrMethodInstantiation; static PFN_ContainsGenericVariables s_pfnContainsGenericVariables; static PFN_GetWrappedMethodDesc s_pfnGetWrappedMethodDesc; static PFN_GetDomain s_pfnGetDomain; static PFN_GetLoaderModule s_pfnGetLoaderModule; };
      
      





静的変数は、内部MethodDescメソッドのアドレスを格納し、アンマネージDLLがロードされると初期化されます。 また、パブリックメソッドは内部メソッドを呼び出すだけです。

これで、Microsoftの内部メソッドを簡単に呼び出すことができます。

 MethodDesc * pMethodDesc = (MethodDesc*)pMethodHandle; pMethodDesc->Reset();
      
      





PDBファイルで内部メソッドのアドレスを見つける



アンマネージDLLが読み込まれると、読み込まれているCLR / JIT環境のバージョンがチェックされます。 また、PDBファイルから内部メソッドのアドレスを取得しようとしています。 それらが見つからなかった場合、Windowsデバッグツールからsymchk.exeを実行して、Microsoft Symbolサーバーから対応するPDBファイルをダウンロードしようとします。 この手順には、数秒から数分という非常に長い時間がかかります。 おそらく、ハッシュをカウントしてCLR / JITライブラリのアドレスをキャッシュすることで、このプロセスを高速化できます。

コンパイルされた非JITへのメソッドの復元



これですべて準備が整いました。 アンマネージライブラリは、マネージコードのメソッドをエクスポートし、マネージコードからILコードとMethodBase.MethodHandle.Valueを取得します。

 // structure to store the IL code for replacement typedef struct _ILCodeBuffer { LPBYTE pBuffer; DWORD dwSize; } ILCodeBuffer, *LPILCodeBuffer; // method to be called by managed code BOOL CInjection::StartUpdateILCodes( MethodTable * pMethodTable , CORINFO_METHOD_HANDLE pMethodHandle , mdMethodDef md , LPBYTE pBuffer , DWORD dwSize ) { MethodDesc * pMethodDesc = (MethodDesc*)pMethodHandle; // reset this MethodDesc pMethodDesc->Reset(); ILCodeBuffer tILCodeBuffer; tILCodeBuffer.pBuffer = pBuffer; tILCodeBuffer.dwSize = dwSize; tILCodeBuffer.bIsGeneric = FALSE; // save the IL code for the method s_mpILBuffers.insert( std::pair< CORINFO_METHOD_HANDLE, ILCodeBuffer>( pMethodHandle, tILCodeBuffer) ); return TRUE; }
      
      





このコードは、単にReset()を呼び出し、ILコードをマップに保存します。マップは、メソッドのコンパイル時にcompileMethodによって使用されます。

そして、compileMethodで、ILコードを置き換えるだけです:

 CorJitResult __stdcall CInjection::compileMethod(ICorJitInfo * pJitInfo , CORINFO_METHOD_INFO * pCorMethodInfo , UINT nFlags , LPBYTE * pEntryAddress , ULONG * pSizeOfCode ) { ICorJitCompiler * pCorJitCompiler = (ICorJitCompiler *)this; LPBYTE pOriginalILCode = pCorMethodInfo->ILCode; unsigned int nOriginalSize = pCorMethodInfo->ILCodeSize; ILCodeBuffer tILCodeBuffer = {0}; MethodDesc * pMethodDesc = (MethodDesc*)pCorMethodInfo->ftn; // find the method to be replaced std::map< CORINFO_METHOD_HANDLE, ILCodeBuffer>::iterator iter = s_mpILBuffers.find((CORINFO_METHOD_HANDLE)pMethodDesc); if( iter != s_mpILBuffers.end() ) { tILCodeBuffer = iter->second; pCorMethodInfo->ILCode = tILCodeBuffer.pBuffer; pCorMethodInfo->ILCodeSize = tILCodeBuffer.dwSize; } CorJitResult result = pCorJitCompiler->compileMethod( pJitInfo, pCorMethodInfo, nFlags, pEntryAddress, pSizeOfCode); return result; }
      
      





一般的な方法



ジェネリックメソッドは、MethodDescのメモリに表示されます。 ただし、異なるタイプのパラメーターでGenericメソッドを呼び出すと、CLRが同じメソッドの異なるエンティティを作成する可能性があります。

以下の行は、デモプログラムからの単純なジェネリックです。

 string GenericMethodToBeReplaced<T, K>(T t, K k)
      
      





GenericMethodToBeReplaced <string、int>(“ 11”、2)を初めて呼び出すことにより、CLRは、InstMethodHashTableにメソッドデータ構造を保存する、InstantiatedMethodDesc型(MethodDescの子であり、フラグがmcInstantiedとしてマークされている)のオブジェクトを作成します。

そして、GenericMethodToBeReplaced <long、int>(1、2)を呼び出すことにより、CLRは別のInstantiatedMethodDescオブジェクトを作成します。

したがって、すべてのInstantiatedMethodDescジェネリックメソッドを見つけて、それらに対してResetを実行する必要があります。

SSCLIソースコード(vm / proftoeeinterfaceimpl.cpp)には、使用可能なLoadedMethodDescIteratorクラスがあります。 入力として3つのパラメーターを受け取り、メソッド識別子(MethodToken)でメソッドを検索します。

 LoadedMethodDescIterator MDIter(ADIter.GetDomain(), pModule, methodId); while(MDIter.Next()) { MethodDesc * pMD = MDIter.Current(); if (pMD) { _ASSERTE(pMD->IsIL()); pMD->SetRVA(rva); } }
      
      





PDBファイルからコンストラクターのアドレス、Next、Currentメソッドを取得できることに注意してください。



LoadedMethodDescIteratorの正確なサイズがわからないのはそれほど悪くはありません。ストレージに大きなメモリブロックを割り当てるだけです。

 class LoadedMethodDescIterator { private: BYTE dummy[10240]; };
      
      





また、.NETがバージョン2.0から4.5に移行したときに、Next()メソッドに小さな変更があったという事実にも注目したいと思います。

 // .Net 2.0 & 4.0 LoadedMethodDescIterator(AppDomain * pAppDomain, Module *pModule, mdMethodDef md) BOOL LoadedMethodDescIterator::Next(void) // .Net 4.5 LoadedMethodDescIterator(AppDomain * pAppDomain, Module *pModule, mdMethodDef md,enum AssemblyIterationMode mode) BOOL LoadedMethodDescIterator::Next(CollectibleAssemblyHolder<DomainAssembly *> *)
      
      





したがって、メソッドを正しく呼び出すためには、.NETフレームワークの現在のバージョンを判別する必要があります。

 // detect the version of CLR BOOL DetermineDotNetVersion(void) { WCHAR wszPath[MAX_PATH] = {0}; ::GetModuleFileNameW( g_hClrModule, wszPath, MAX_PATH); CStringW strPath(wszPath); int nIndex = strPath.ReverseFind('\\'); if( nIndex <= 0 ) return FALSE; nIndex++; CStringW strFilename = strPath.Mid( nIndex, strPath.GetLength() - nIndex); if( strFilename.CompareNoCase(L"mscorwks.dll") == 0 ) { g_tDotNetVersion = DotNetVersion_20; return TRUE; } if( strFilename.CompareNoCase(L"clr.dll") == 0 ) { DWORD dwHandle = NULL; UINT nSize = 0; LPBYTE lpBuffer = NULL; BYTE szTempBuf[2048] = {0}; DWORD dwSize = GetFileVersionInfoSizeW( wszPath, &dwHandle); if (dwSize != NULL) { LPVOID pData = szTempBuf; if (GetFileVersionInfo( wszPath, dwHandle, dwSize, pData)) { if (VerQueryValueW( pData, L"\\",(VOID FAR* FAR*)&lpBuffer,&nSize)) { if (nSize) { VS_FIXEDFILEINFO * pVerInfo = (VS_FIXEDFILEINFO *)lpBuffer; if (pVerInfo->dwSignature == 0xfeef04bd) { int nMajor = HIWORD(pVerInfo->dwFileVersionMS); int nMinor = LOWORD(pVerInfo->dwFileVersionMS); int nBuildMajor = HIWORD(pVerInfo->dwFileVersionLS); int nBuildMinor = LOWORD(pVerInfo->dwFileVersionLS); if( nMajor == 4 && nMinor == 0 && nBuildMajor == 30319 ) { if( nBuildMinor < 10000 ) g_tDotNetVersion = DotNetVersion_40; else g_tDotNetVersion = DotNetVersion_45; return TRUE; } } } } } return FALSE; } } return FALSE; }
      
      





これで、CLRと連携して動作するLoadMethodDescIteratorを宣言できます。

 enum AssemblyIterationMode { AssemblyIterationMode_Default = 0 }; class LoadedMethodDescIterator { typedef void (LoadedMethodDescIterator::*PFN_LoadedMethodDescIteratorConstructor)(AppDomain * pAppDomain, Module *pModule, mdMethodDef md); typedef void (LoadedMethodDescIterator::*PFN_LoadedMethodDescIteratorConstructor_v45)(AppDomain * pAppDomain, Module *pModule, mdMethodDef md, AssemblyIterationMode mode); typedef void (LoadedMethodDescIterator::*PFN_Start)(AppDomain * pAppDomain, Module *pModule, mdMethodDef md); typedef BOOL (LoadedMethodDescIterator::*PFN_Next_v4)(LPVOID pParam); typedef BOOL (LoadedMethodDescIterator::*PFN_Next_v2)(void); typedef MethodDesc* (LoadedMethodDescIterator::*PFN_Current)(void); public: LoadedMethodDescIterator(AppDomain * pAppDomain, Module *pModule, mdMethodDef md) { memset( dummy, 0, sizeof(dummy)); memset( dummy2, 0, sizeof(dummy2)); if( s_pfnConstructor ) (this->*s_pfnConstructor)( pAppDomain, pModule, md); if( s_pfnConstructor_v45 ) (this->*s_pfnConstructor_v45)( pAppDomain, pModule, md, AssemblyIterationMode_Default); } void Start(AppDomain * pAppDomain, Module *pModule, mdMethodDef md) { (this->*s_pfnStart)( pAppDomain, pModule, md); } BOOL Next() { if( s_pfnNext_v4 ) return (this->*s_pfnNext_v4)(dummy2); if( s_pfnNext_v2 ) return (this->*s_pfnNext_v2)(); return FALSE; } MethodDesc* Current() { return (this->*s_pfnCurrent)(); } private: // we don't know the exact size of LoadedMethodDescIterator, so add enough memory here BYTE dummy[10240]; // class CollectibleAssemblyHolder<class DomainAssembly *> parameter for Next() in .Net4.0 and above BYTE dummy2[10240]; // constructor for .Net2.0 & .Net 4.0 static PFN_LoadedMethodDescIteratorConstructor s_pfnConstructor; // constructor for .Net4.5 static PFN_LoadedMethodDescIteratorConstructor_v45 s_pfnConstructor_v45; static PFN_Start s_pfnStart; static PFN_Next_v4 s_pfnNext_v4; static PFN_Next_v2 s_pfnNext_v2; static PFN_Current s_pfnCurrent; public: static void MatchAddress(PSYMBOL_INFOW pSymbolInfo) { LPVOID* pDest = NULL; if( wcscmp( L"LoadedMethodDescIterator::LoadedMethodDescIterator", pSymbolInfo->Name) == 0 ) { switch(g_tDotNetVersion) { case DotNetVersion_20: case DotNetVersion_40: pDest = (LPVOID*)&(LoadedMethodDescIterator::s_pfnConstructor); break; case DotNetVersion_45: pDest = (LPVOID*)&(LoadedMethodDescIterator::s_pfnConstructor_v45); break; default: ATLASSERT(FALSE); return; } } else if( wcscmp( L"LoadedMethodDescIterator::Next", pSymbolInfo->Name) == 0 ) { switch(g_tDotNetVersion) { case DotNetVersion_20: pDest = (LPVOID*)&(LoadedMethodDescIterator::s_pfnNext_v2); break; case DotNetVersion_40: case DotNetVersion_45: pDest = (LPVOID*)&(LoadedMethodDescIterator::s_pfnNext_v4); break; default: ATLASSERT(FALSE); return; } } else if( wcscmp( L"LoadedMethodDescIterator::Start", pSymbolInfo->Name) == 0 ) pDest = (LPVOID*)&(LoadedMethodDescIterator::s_pfnStart); else if( wcscmp( L"LoadedMethodDescIterator::Current", pSymbolInfo->Name) == 0 ) pDest = (LPVOID*)&(LoadedMethodDescIterator::s_pfnCurrent); if( pDest ) *pDest = (LPVOID)pSymbolInfo->Address; } };
      
      





最後に、LoadedMethodDescIteratorを使用して、汎用メソッドのMethodDescでReset()を呼び出します。

 Module * pModule = pMethodDesc->GetLoaderModule(); AppDomain * pAppDomain = pMethodDesc->GetDomain(); if( pModule ) { LoadedMethodDescIterator * pLoadedMethodDescIter = new LoadedMethodDescIterator( pAppDomain, pModule, md); while(pLoadedMethodDescIter->Next()) { MethodDesc * pMD = pLoadedMethodDescIter->Current(); if( pMD ) pMD->Reset(); } delete pLoadedMethodDescIter; }
      
      





コンパイルと最適化



メソッドが非常に小さく、サイズが数バイトしかない場合、インラインモードでコンパイルされることがわかりました。 したがって、MethodDesc :: Reset()は役に立ちません。実行時に、このメソッドを呼び出すことさえできないためです。 CEEInfo :: canInline(SSCLIのvm。\ / Jitinterface.cpp)にもう少し情報があります。

動的メソッド



ILコードに動的メソッドを追加するときは、非常に注意する必要があります。 他のタイプのメソッドに間違ったILコードを追加するとInvalidApplicationExceptionが発生するだけですが、間違ったILコードを動的メソッドに追加すると、CLRとプロセスが完全に失敗する可能性があります。 動的コードILは、他とは異なります。 最善の方法は、別の動的メソッドからILコードを生成してから、コピーして貼り付けることです。

ソースファイル






All Articles