Предисловие
Изменение .NET метода MSIL кода во время выполнения приложения – это очень круто. Это настолько круто, что можно перехватывать вызовы функций (hooking), сделать защиту своего ПО и другие удивительные вещи. Именно поэтому мне уже давно хотелось это осуществить, но была одна проблема – 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 greater than 2 (О_о)».
Посмотрев 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);
Вы можете загрузить себе демонстрационную программу и попробовать.
- Поддерживает .NET от 2.0 до 4.0
- Поддерживает множество типов методов, включая dynamic и generic методы.
- Поддерживает релизные версии .NET приложений
- Поддерживает x86 и x64
Использование кода
Скопируйте 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, состоящую из неуправляемого (unmanaged) кода, из директории, в которой лежит сборка, поэтому все файлы, которые вы хотите модифицировать, должны лежать там же. Либо вы можете поправить исходники, кому как удобнее :)
Список файлов:
Имя файла | Описание |
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/* | Windows Debug Tool for x86 |
x64/* | Windows Debug Tool for x64 |
PDB_symbols/* | Файлы PDB. Их можно удалить, но это замедлит инициализацию |
За кулисами
Давайте сначала посмотрим, как работают CLR и JIT.
Библиотека, описывающая JIT (clr.dll для .NET 4.0 / mscorwks.dll для .NET 2.0+), предоставляет _stdcall метод getJit, который возвращает интерфейс ICorJitCompiler.
Библиотека, описывающая CLR (clr.dll для .NET 4.0 / mscorwks.dll для .NET 2.0+), вызывает метод 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
);
Изменение IL-кода для JIT-скомпилированных методов
Метод compileMethod, описанный выше, не будет вызываться CLR для JIT-скомпилированного метода. Для решения этой проблемы, я сделал сохранение структур данных CLR и последующее их восстановление перед JIT-компиляцией. И в этом случае, как только compileMethod будет вызван снова, мы можем заменить IL.
Таким образом, нам надо немного посмотреть на реализацию CLR, SSCLI (Shared Source Common Language Infrastructure / Инфраструктура общего языка) является хорошим источником информации, но так как она довольно устарела, мы не можем использовать её в нашем коде.
Да-да, именно эта диаграмма устарела, но общая структура сохранилась. Каждый класс в .NET имеет как минимум одну структуру MethodTable в памяти. А каждая структура MethodTable связана с EEClass, который хранит информацию времени выполнения для рефлексии и других целей.
Для каждого метода, есть как минимум одна MethodDesc структура, содержащая информацию о флагах, адресах слота, адресе входа и т.п.
Перед тем, как метода будет JIT-скомрилирован, слот указывает на JMI преобразователь, который переключает JIT-компиляцию; Когда IL-код будет скомпилирован, в слот будет записан указатель на JMI, и код при выполнении будет попадать прямо на скомпилированный код.
Чтобы восстановить структуру информации, сначала необходимо очистить флаги, затем модифицировать адрес точки входа на временный и т.д. Во время тестирования, я делал это, изменяя память напрямую. Но это грязно, как минимум, потому что есть зависимость от адресов структур данных и код разных .NET версий отличается.
Я искал правильных способ, и к счастью, я нашёл метод MethodDesc::Reset в исходном коде SSCLI (vm/method.cpp).
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());
}
Как вы видите, этот код делает что надо. Поэтому мне просто надо его вызвать для MethodDesc перед JIT-компиляцией.
Строго говоря, я не могу использовать MethodDesc из SSCLI, так как MethodDesc используется внутри Microsoft, и никто не знает, что из-за этого может быть.
К счастью, адрес этого внутреннего метода существует в PDB c сервера Microsoft Symbol, и это решает мою проблему. Адрес метода Reset() в CLR DLL можно найти просто распарсив PDB!
Теперь остался один важный параметр – это указатель this на MethodDesc. Получить его не так трудно. Вообще, MethodBase.MethodHandle.Value == CORINFO_METHOD_HANDLE == вдрес MethodDesc == указатель this на 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. А public методы просто вызывают внутренние методы.
Теперь мы можем запросто вызывать внутренние методы Microsoft:
MethodDesc * pMethodDesc = (MethodDesc*)pMethodHandle;
pMethodDesc->Reset();
Поиск адресов внутренних методов в PDB-файле
Когда загружается неуправляемая DLL, она проверяет версию среды CLR/JIT, в которой она загружена. И она также пытается получить адреса внутренних методов из PDB-файла. Если найти их не удалось, она попытается запустить symchk.exe из Windows Debug Tools, чтобы загрузить соответствующие PDB-файлы с сервера Microsoft Symbol. Эта процедура занимает довольно длительное время, от нескольких секунд до нескольких минут. Возможно, мы можем ускорить этот процесс, кэшируя адрес 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-коды в map, которая будет использоваться 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;
}
Generic метод
Generic метод отображается в памяти в MethodDesc. Но вызов Generic метода с различающимися типами параметров могут заставить CLR создать различные сущности одного метода.
Строчка ниже – это простой generic из демонстрационной программы.
string GenericMethodToBeReplaced<T, K>(T t, K k)
Вызывая GenericMethodToBeReplaced<string, int>(“11”, 2) в первый раз, CLR создаёт объект типа InstantiatedMethodDesc (дочерний от MethodDesc, и его флаг помечается как mcInstantied), который хранит в InstMethodHashTable структуру данных метода.
И вызывая GenericMethodToBeReplaced<long, int>(1, 2), CLR создаёт другой объект InstantiatedMethodDesc.
Поэтому нам надо найти все InstantiatedMethodDesc generic метода и выполнить им Reset.
В исходном коде SSCLI (vm/proftoeeinterfaceimpl.cpp) есть класс LoadedMethodDescIterator, который мы можем использовать. Он получает на вход три параметра и ищет методы по идентификатору метода (MethodToken).
LoadedMethodDescIterator MDIter(ADIter.GetDomain(), pModule, methodId);
while(MDIter.Next())
{
MethodDesc * pMD = MDIter.Current();
if (pMD)
{
_ASSERTE(pMD->IsIL());
pMD->SetRVA(rva);
}
}
Заметим, что адреса конструктора, методов Next, Current мы можем получить из PDB-файла.
Не так страшно, что мы не знает точного размера LoadedMethodDescIterator, просто выделим под его хранения большой блок памяти.
class LoadedMethodDescIterator
{
private:
BYTE dummy[10240];
};
Ещё хотелось бы обратить внимание, что произошли небольшие изменения в методе Next() при переходе .NET от версии 2.0 к 4.5.
// .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;
}
Теперь мы можем объявить наш LoadMethodDescIterator, который будет работать вместе с CLR.
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 для вызова Reset() у MethodDesc для generic методов.
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;
}
Компиляция и оптимизация
Я обнаружил, что если метод очень мал и его размер составляет всего несколько байт, то он будет компилироваться в inline режиме. Поэтому MethodDesc::Reset() не поможет, потому что во время выполнения, до вызова этого метода даже дело не дойдёт. Немного больше информации можно найти в CEEInfo::canInline (vm./jitinterface.cpp в SSCLI)
Динамические методы
При добавлении в IL-код динамических методов, надо быть очень осторожным. Добавление неверного IL-кода для других типов методов лишь вызовет InvalidApplicationException, но добавление некорректного IL-кода в динамический метод может привести к полному сбою CLR и нашего процесса. IL-код динамических методов отличается от остальных. Самый лучший выход – генерировать IL-код и другого динамического метода, а потом копировать и вставлять.
Исходный файлы
via codeproject.com
Автор: NeonMercury