Permalink
Browse files

Sample code to mock Static methods in sealed classes

Sample code showing a very basic version of how TypeMock works
  • Loading branch information...
mattwarren committed Aug 11, 2014
1 parent 270ef3b commit 9f804cec8ef11b802e020e648180b436a429833f
@@ -1,9 +1,14 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Express 2012 for Windows Desktop
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DDDProfiler", "DDDProfiler\DDDProfiler.vcxproj", "{4433C836-63ED-47B5-ABD4-192945EF2AFA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProfilerHost", "ProfilerHost\ProfilerHost.csproj", "{70125EF7-1BBA-4508-8900-1D176D725368}"
ProjectSection(ProjectDependencies) = postProject
{4433C836-63ED-47B5-ABD4-192945EF2AFA} = {4433C836-63ED-47B5-ABD4-192945EF2AFA}
{7ADB7181-7442-4115-9E92-F703A0357C56} = {7ADB7181-7442-4115-9E92-F703A0357C56}
{82F88AB6-1CF9-4C5F-A711-AD298FC2BD6F} = {82F88AB6-1CF9-4C5F-A711-AD298FC2BD6F}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProfilerTarget", "ProfilerTarget\ProfilerTarget.csproj", "{7ADB7181-7442-4115-9E92-F703A0357C56}"
EndProject
@@ -75,8 +75,7 @@ HRESULT CCodeInjection::GetMsCorlibRef(ModuleID moduleId, mdModuleRef &mscorlibR
assembly.usRevisionNumber = 0;
BYTE publicKey[] = { 0xB7, 0x7A, 0x5C, 0x56, 0x19, 0x34, 0xE0, 0x89 };
COM_FAIL_RETURN(metaDataAssemblyEmit->DefineAssemblyRef(publicKey,
sizeof(publicKey), L"mscorlib", &assembly, NULL, 0, 0,
&mscorlibRef), S_OK);
sizeof(publicKey), L"mscorlib", &assembly, NULL, 0, 0, &mscorlibRef), S_OK);
}
/// <summary>Handle <c>ICorProfilerCallback::ModuleAttachedToAssembly</c></summary>
@@ -88,14 +87,20 @@ HRESULT STDMETHODCALLTYPE CCodeInjection::ModuleAttachedToAssembly(
{
ULONG dwNameSize = 512;
WCHAR szAssemblyName[512] = { 0 };
COM_FAIL_RETURN(m_profilerInfo3->GetAssemblyInfo(assemblyId,
dwNameSize, &dwNameSize, szAssemblyName, NULL, NULL), S_OK);
ATLTRACE(_T("::ModuleAttachedToAssembly(%X => ?, %X => %s)"),
moduleId, assemblyId, W2CT(szAssemblyName));
COM_FAIL_RETURN(m_profilerInfo3->GetAssemblyInfo(assemblyId, dwNameSize, &dwNameSize, szAssemblyName, NULL, NULL), S_OK);
ULONG dwModuleNameSize = 512;
WCHAR szModuleName[512] = { 0 };
COM_FAIL_RETURN(m_profilerInfo3->GetModuleInfo(moduleId, NULL, dwModuleNameSize, &dwModuleNameSize, szModuleName, NULL), S_OK);
ATLTRACE(_T("::ModuleAttachedToAssembly(%X => %s, %X => %s)"), assemblyId, W2CT(szAssemblyName), moduleId, W2CT(szModuleName));
if (lstrcmp(L"ProfilerTarget", szAssemblyName) == 0) {
if (lstrcmp(L"ProfilerTarget", szAssemblyName) == 0)
{
m_targetMethodRefLog = 0;
m_targetMethodRefLogAfter = 0;
m_targetMethodRefMocked = 0;
m_targetMethodRefShouldMock = 0;
m_targetMethodRef = 0;
// get reference to injected
mdModuleRef injectedRef;
COM_FAIL_RETURN(GetInjectedRef(moduleId, injectedRef), S_OK);
@@ -105,27 +110,50 @@ HRESULT STDMETHODCALLTYPE CCodeInjection::ModuleAttachedToAssembly(
COM_FAIL_RETURN(m_profilerInfo3->GetModuleMetaData(moduleId,
ofRead | ofWrite, IID_IMetaDataEmit, (IUnknown**)&metaDataEmit), S_OK);
static COR_SIGNATURE methodCallSignature[] =
static COR_SIGNATURE signatureLog[] =
{
IMAGE_CEE_CS_CALLCONV_DEFAULT,
0x01,
ELEMENT_TYPE_VOID,
ELEMENT_TYPE_OBJECT
};
static COR_SIGNATURE signatureLogAfter[] =
{
IMAGE_CEE_CS_CALLCONV_DEFAULT,
0x00, // arg count
ELEMENT_TYPE_VOID, // return value
};
// get method to call
mdTypeRef classTypeRef;
COM_FAIL_RETURN(metaDataEmit->DefineTypeRefByName(injectedRef,
L"Injected.Logger", &classTypeRef), S_OK);
COM_FAIL_RETURN(metaDataEmit->DefineMemberRef(classTypeRef,
L"Log", methodCallSignature,
sizeof(methodCallSignature), &m_targetMethodRef), S_OK);
COM_FAIL_RETURN(metaDataEmit->DefineTypeRefByName(injectedRef, L"Injected.Logger", &classTypeRef), S_OK);
COM_FAIL_RETURN(metaDataEmit->DefineMemberRef(classTypeRef, L"Log", signatureLog, sizeof(signatureLog), &m_targetMethodRefLog), S_OK);
COM_FAIL_RETURN(metaDataEmit->DefineMemberRef(classTypeRef, L"LogAfter", signatureLogAfter, sizeof(signatureLogAfter), &m_targetMethodRefLogAfter), S_OK);
static COR_SIGNATURE signatureMocked[] =
{
IMAGE_CEE_CS_CALLCONV_DEFAULT,
0x00, // arg count
ELEMENT_TYPE_I4, // return value (is this right, Int32 = 4 bytes??)
};
static COR_SIGNATURE signatureShouldMock[] =
{
IMAGE_CEE_CS_CALLCONV_DEFAULT,
0x01, // arg count
ELEMENT_TYPE_BOOLEAN, // return value
ELEMENT_TYPE_STRING, // paramater
};
COM_FAIL_RETURN(metaDataEmit->DefineTypeRefByName(injectedRef, L"Injected.Mocked", &classTypeRef), S_OK);
COM_FAIL_RETURN(metaDataEmit->DefineMemberRef(classTypeRef, L"MockedMethod", signatureMocked, sizeof(signatureMocked), &m_targetMethodRefMocked), S_OK);
COM_FAIL_RETURN(metaDataEmit->DefineMemberRef(classTypeRef, L"ShouldMock", signatureShouldMock, sizeof(signatureShouldMock), &m_targetMethodRefShouldMock), S_OK);
// get object ref
mdModuleRef mscorlibRef;
COM_FAIL_RETURN(GetMsCorlibRef(moduleId, mscorlibRef), S_OK);
COM_FAIL_RETURN(metaDataEmit->DefineTypeRefByName(mscorlibRef,
L"System.Object", &m_objectTypeRef), S_OK);
COM_FAIL_RETURN(metaDataEmit->DefineTypeRefByName(mscorlibRef, L"System.Object", &m_objectTypeRef), S_OK);
}
return S_OK;
@@ -172,23 +200,124 @@ HRESULT STDMETHODCALLTYPE CCodeInjection::JITCompilationStarted(
/* [in] */ FunctionID functionId, /* [in] */ BOOL fIsSafeToBlock)
{
ModuleID moduleId; mdToken funcToken;
std::wstring methodName = GetMethodName(functionId,
moduleId, funcToken);
ATLTRACE(_T("::JITCompilationStarted(%X -> %s)"),
functionId, W2CT(methodName.c_str()));
std::wstring methodName = GetMethodName(functionId, moduleId, funcToken);
ATLTRACE(_T("::JITCompilationStarted(%X -> %s)"), functionId, W2CT(methodName.c_str()));
if (L"ProfilerTarget.Program.OnMethodToInstrument" == methodName && m_targetMethodRefLog != 0 && m_targetMethodRefLogAfter != 0)
{
HRESULT status = AddLoggingToMethod(moduleId, functionId, funcToken);
if (status != S_OK)
return status;
}
else if (L"ProfilerTarget.ClassToMock.StaticMethodToMock" == methodName && m_targetMethodRefMocked != 0)
{
HRESULT status = AddMockingToMethod(moduleId, functionId, funcToken, methodName);
if (status != S_OK)
return status;
}
if (L"ProfilerTarget.Program.OnMethodToInstrument" == methodName &&
m_targetMethodRef !=0 ) {
return S_OK;
}
HRESULT STDMETHODCALLTYPE CCodeInjection::AddMockingToMethod(ModuleID moduleId, FunctionID functionId, mdToken funcToken, std::wstring methodName)
{
// get method body
LPCBYTE pMethodHeader = NULL;
ULONG iMethodSize = 0;
COM_FAIL_RETURN(m_profilerInfo3->GetILFunctionBody(
moduleId, funcToken, &pMethodHeader, &iMethodSize),
S_OK);
COM_FAIL_RETURN(m_profilerInfo3->GetILFunctionBody(moduleId, funcToken, &pMethodHeader, &iMethodSize), S_OK);
CComPtr<IMetaDataEmit> metaDataEmit;
COM_FAIL_RETURN(m_profilerInfo3->GetModuleMetaData(moduleId,
ofRead | ofWrite, IID_IMetaDataEmit, (IUnknown**)&metaDataEmit), S_OK);
COM_FAIL_RETURN(m_profilerInfo3->GetModuleMetaData(moduleId, ofRead | ofWrite, IID_IMetaDataEmit, (IUnknown**)&metaDataEmit), S_OK);
// parse IL
Method instMethod((IMAGE_COR_ILMETHOD*)pMethodHeader); // <--
instMethod.SetMinimumStackSize(3); // should be correct for this sample
/* Taken from Reflector (DEBUG Version) - NOTE all the nop's and the locals (used to store values returned from method calls)
.maxstack 2
.locals init(
[0] int32 CS$1$0000,
[1] bool CS$4$0001)
L_0000: nop
L_0001: ldstr "Profilier.ClassToMock.StaticMethodToMock"
L_0006: call bool [Injected]Injected.Mocked::ShouldMock(string)
L_000b: ldc.i4.0
L_000c: ceq
L_000e: stloc.1
L_000f: ldloc.1
L_0010: brtrue.s L_001a
L_0012: call int32 [Injected]Injected.Mocked::MockedMethod()
L_0017: stloc.0
L_0018: br.s L_002a
// From this point onwards it's the original code, which we can leave alone
L_001a: ldstr "StaticMethodToMockWhatWeWantToDo called, returning 42"
L_001f: call void [mscorlib]System.Console::WriteLine(string)
L_0024: nop
L_0025: ldc.i4.s 0x2a
L_0027: stloc.0
L_0028: br.s L_002a
L_002a: ldloc.0
L_002b: ret */
/* Taken from Reflector (RELEASE Version)
.maxstack 8
L_0000: ldstr "Profilier.ClassToMock.StaticMethodToMock"
L_0005: call bool [Injected]Injected.Mocked::ShouldMock(string)
L_000a: brfalse.s L_0012
L_000c: call int32 [Injected]Injected.Mocked::MockedMethod()
L_0011: ret
// From this point onwards it's the original code, which we can leave alone
L_0012: ldstr "StaticMethodToMockWhatWeWantToDo called, returning 42"
L_0017: call void [mscorlib]System.Console::WriteLine(string)
L_001c: ldc.i4.s 0x2a
L_001e: ret */
mdString mdsMessage = mdStringNil;
COM_FAIL_RETURN(metaDataEmit->DefineUserString(methodName.c_str(), methodName.size(), &mdsMessage), S_OK);
// insert new IL block
InstructionList instructions; // NOTE: this IL will be different for an instance method or if the local vars signature is different
instructions.push_back(new Instruction(CEE_LDSTR, mdsMessage));
instructions.push_back(new Instruction(CEE_CALL, m_targetMethodRefShouldMock));
Instruction *returnInstr = new Instruction(CEE_RET);
instructions.push_back(new Instruction(CEE_BRFALSE_S, returnInstr));
instructions.push_back(new Instruction(CEE_CALL, m_targetMethodRefMocked));
instructions.push_back(returnInstr);
instMethod.InsertSequenceInstructionsAtOriginalOffset(0, instructions);
ATLTRACE(_T("Re-written IL (AddMockingToMethod)"));
instMethod.DumpIL();
// allocate memory
CComPtr<IMethodMalloc> methodMalloc;
COM_FAIL_RETURN(m_profilerInfo3->GetILFunctionBodyAllocator(moduleId, &methodMalloc), S_OK);
void* pNewMethod = methodMalloc->Alloc(instMethod.GetMethodSize());
// write new method
instMethod.WriteMethod((IMAGE_COR_ILMETHOD*)pNewMethod);
COM_FAIL_RETURN(m_profilerInfo3->SetILFunctionBody(moduleId, funcToken, (LPCBYTE)pNewMethod), S_OK);
// update IL maps
ULONG mapSize = instMethod.GetILMapSize();
void* pMap = CoTaskMemAlloc(mapSize * sizeof(COR_IL_MAP));
instMethod.PopulateILMap(mapSize, (COR_IL_MAP*)pMap);
COM_FAIL_RETURN(m_profilerInfo3->SetILInstrumentedCodeMap(functionId, TRUE, mapSize, (COR_IL_MAP*)pMap), S_OK);
CoTaskMemFree(pMap);
return S_OK;
}
HRESULT STDMETHODCALLTYPE CCodeInjection::AddLoggingToMethod(ModuleID moduleId, FunctionID functionId, mdToken funcToken)
{
// get method body
LPCBYTE pMethodHeader = NULL;
ULONG iMethodSize = 0;
COM_FAIL_RETURN(m_profilerInfo3->GetILFunctionBody(moduleId, funcToken, &pMethodHeader, &iMethodSize), S_OK);
CComPtr<IMetaDataEmit> metaDataEmit;
COM_FAIL_RETURN(m_profilerInfo3->GetModuleMetaData(moduleId, ofRead | ofWrite, IID_IMetaDataEmit, (IUnknown**)&metaDataEmit), S_OK);
// parse IL
Method instMethod((IMAGE_COR_ILMETHOD*)pMethodHeader); // <--
@@ -209,48 +338,57 @@ HRESULT STDMETHODCALLTYPE CCodeInjection::JITCompilationStarted(
// insert new IL block
InstructionList instructions; // NOTE: this IL will be different for an instance method or if the local vars signature is different
// Signature of the method we are instrumenting "static void OnMethodToInstrument(object sender, EventArgs e)"
instructions.push_back(new Instruction(CEE_NOP));
instructions.push_back(new Instruction(CEE_LDC_I4_2));
instructions.push_back(new Instruction(CEE_NEWARR, m_objectTypeRef));
instructions.push_back(new Instruction(CEE_NEWARR, m_objectTypeRef)); // var paramaters = new Object[2] { sender, e };
instructions.push_back(new Instruction(CEE_STLOC_1));
instructions.push_back(new Instruction(CEE_LDLOC_1));
instructions.push_back(new Instruction(CEE_LDC_I4_0));
instructions.push_back(new Instruction(CEE_LDARG_0));
instructions.push_back(new Instruction(CEE_STELEM_REF));
instructions.push_back(new Instruction(CEE_LDARG_0)); // push local arg0 onto the stack ("sender")
instructions.push_back(new Instruction(CEE_STELEM_REF)); // store "sender" in parameters array[0]
instructions.push_back(new Instruction(CEE_LDLOC_1));
instructions.push_back(new Instruction(CEE_LDC_I4_1));
instructions.push_back(new Instruction(CEE_LDARG_1));
instructions.push_back(new Instruction(CEE_STELEM_REF));
instructions.push_back(new Instruction(CEE_LDARG_1)); // push local arg1 onto the stack ("e")
instructions.push_back(new Instruction(CEE_STELEM_REF)); // store "e" in parameters array[1]
instructions.push_back(new Instruction(CEE_LDLOC_1));
instructions.push_back(new Instruction(CEE_STLOC_0));
instructions.push_back(new Instruction(CEE_LDLOC_0));
instructions.push_back(new Instruction(CEE_CALL, m_targetMethodRef));
instructions.push_back(new Instruction(CEE_CALL, m_targetMethodRefLog)); // call Injected.Logger.Log(parameters)
instMethod.InsertSequenceInstructionsAtOriginalOffset(0, instructions);
ATLTRACE(_T("Re-written IL (AddLoggingToMethod, before adding call to Logger.LogAfter())"));
instMethod.DumpIL();
InstructionList afterInstructions;
afterInstructions.push_back(new Instruction(CEE_CALL, m_targetMethodRefLogAfter)); // call Injected.Logger.LogAfter()
//afterInstructions.push_back(new Instruction(CEE_NOP)); Do we need this??
instMethod.InsertSequenceInstructionsAtOriginalOffset(
0, instructions);
Instruction* lastButOneInst = instMethod.m_instructions.at(instMethod.m_instructions.size() - 2);
ATLTRACE(_T("lastButOneInst->m_offset %i "), lastButOneInst->m_offset, lastButOneInst);
instMethod.InsertSequenceInstructionsAtOffset(lastButOneInst->m_offset, afterInstructions);
ATLTRACE(_T("Re-written IL (AddLoggingToMethod)"));
instMethod.DumpIL();
// allocate memory
CComPtr<IMethodMalloc> methodMalloc;
COM_FAIL_RETURN(m_profilerInfo3->GetILFunctionBodyAllocator(
moduleId, &methodMalloc), S_OK);
COM_FAIL_RETURN(m_profilerInfo3->GetILFunctionBodyAllocator(moduleId, &methodMalloc), S_OK);
void* pNewMethod = methodMalloc->Alloc(instMethod.GetMethodSize());
// write new method
instMethod.WriteMethod((IMAGE_COR_ILMETHOD*)pNewMethod);
COM_FAIL_RETURN(m_profilerInfo3->SetILFunctionBody(moduleId,
funcToken, (LPCBYTE) pNewMethod), S_OK);
COM_FAIL_RETURN(m_profilerInfo3->SetILFunctionBody(moduleId, funcToken, (LPCBYTE)pNewMethod), S_OK);
// update IL maps
ULONG mapSize = instMethod.GetILMapSize();
void* pMap = CoTaskMemAlloc(mapSize * sizeof(COR_IL_MAP));
instMethod.PopulateILMap(mapSize, (COR_IL_MAP*)pMap);
COM_FAIL_RETURN(m_profilerInfo3->SetILInstrumentedCodeMap(
functionId, TRUE, mapSize, (COR_IL_MAP*)pMap), S_OK);
COM_FAIL_RETURN(m_profilerInfo3->SetILInstrumentedCodeMap(functionId, TRUE, mapSize, (COR_IL_MAP*)pMap), S_OK);
CoTaskMemFree(pMap);
}
return S_OK;
}
@@ -42,11 +42,18 @@ END_COM_MAP()
private:
CComQIPtr<ICorProfilerInfo3> m_profilerInfo3;
mdMemberRef m_targetMethodRefLog;
mdMemberRef m_targetMethodRefLogAfter;
mdMemberRef m_targetMethodRefMocked;
mdMemberRef m_targetMethodRefShouldMock;
mdTypeRef m_objectTypeRef;
std::wstring GetMethodName(FunctionID functionId, ModuleID& moduleId, mdToken& funcToken);
mdMemberRef m_targetMethodRef;
HRESULT GetInjectedRef(ModuleID moduleId, mdModuleRef &mscorlibRef);
HRESULT GetMsCorlibRef(ModuleID moduleId, mdModuleRef &mscorlibRef);
mdTypeRef m_objectTypeRef;
HRESULT STDMETHODCALLTYPE AddLoggingToMethod(ModuleID moduleId, FunctionID functionId, mdToken funcToken);
HRESULT STDMETHODCALLTYPE AddMockingToMethod(ModuleID moduleId, FunctionID functionId, mdToken funcToken, std::wstring methodName);
public:
virtual HRESULT STDMETHODCALLTYPE Initialize(
/* [in] */ IUnknown *pICorProfilerInfoUnk);
@@ -60,8 +67,6 @@ END_COM_MAP()
virtual HRESULT STDMETHODCALLTYPE JITCompilationStarted(
/* [in] */ FunctionID functionId,
/* [in] */ BOOL fIsSafeToBlock);
};
OBJECT_ENTRY_AUTO(__uuidof(CodeInjection), CCodeInjection)
@@ -1,13 +1,11 @@
// DDDProfiler.cpp : Implementation of DLL Exports.
#include "stdafx.h"
#include "resource.h"
#include "DDDProfiler_i.h"
#include "dllmain.h"
#include "xdlldata.h"
// Used to determine whether the DLL can be unloaded by OLE.
STDAPI DllCanUnloadNow(void)
{
@@ -86,5 +84,3 @@ STDAPI DllInstall(BOOL bInstall, LPCWSTR pszCmdLine)
return hr;
}
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
@@ -20,12 +20,14 @@
<UseDebugLibraries>true</UseDebugLibraries>
<UseOfAtl>Dynamic</UseOfAtl>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v120</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<UseOfAtl>Dynamic</UseOfAtl>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v120</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
Oops, something went wrong.

0 comments on commit 9f804ce

Please sign in to comment.