Инъекция кода в .NET CLR: изменение IL-кода во время выполнения программы

в 7:48, , рубрики: .net, clr, MSIL, системное программирование, метки: , , , ,

Предисловие

Инъекция кода в .NET CLR: изменение IL кода во время выполнения программы
Изменение .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.
Инъекция кода в .NET CLR: изменение IL кода во время выполнения программы
И если вы запустите демонстрационное приложение, то оно покажет вам неверный результат.
Инъекция кода в .NET CLR: изменение IL кода во время выполнения программы
Чуть ниже находится код для замены 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.
Инъекция кода в .NET CLR: изменение IL кода во время выполнения программы
Библиотека, описывающая 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 CLR: изменение IL кода во время выполнения программы
Да-да, именно эта диаграмма устарела, но общая структура сохранилась. Каждый класс в .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-файла.
Инъекция кода в .NET CLR: изменение IL кода во время выполнения программы
Не так страшно, что мы не знает точного размера 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

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js