So we have been trying to develop a profiler for a long time. All the posts seem to require an inordinate amount ofMSIL knowledge or to know C++ to interface with the CLR directly. Both of these approaches were painful. All I wanted to do was inject a Profile.Start and Profile.End into our code base for every class giving us a simple profiler. With Mono.Cecil I could do exactly that, and without Mono installed on my computer. Go get the original example from the Mono.Cecil website or just download it directly here. There is a dll called lib. I just added that to my solution in VS2008 and wrote the following simple profiler.
static void Main(string[] args)
{
string pathBin = @"C:\Users\michael\Documents\Visual Studio 2008\Projects\ConsoleApplication1\ConsoleApplication2\bin\Debug\ConsoleApplication2.exe";
MethodInfo startMethod = typeof(ProfileClient).GetMethod("Start", new Type[] { typeof(string) });
MethodInfo endMethod =typeof(ProfileClient).GetMethod("End", new Type[] { typeof(string) });
AssemblyDefinition assembly = AssemblyFactory.GetAssembly(pathBin);
foreach (TypeDefinition type in assembly.MainModule.Types)
{
if (type.Name != "<Module>")
{
foreach (MethodDefinition method in type.Methods)
{
CilWorker worker = method.Body.CilWorker;
string sentence = method.Name;
MethodReference start=assembly.MainModule.Import(startMethod);
MethodReference end= assembly.MainModule.Import(endMethod);
//Creates the MSIL instruction for inserting the sentence
Instruction startSentence = worker.Create(OpCodes.Ldstr, sentence);
Instruction endSentence= worker.Create(OpCodes.Ldstr, sentence);
List<Instruction> parameters = new List<Instruction>();
//Creates the CIL instruction for calling the start and end
Instruction callStart= worker.Create(OpCodes.Call, start);
Instruction callEnd= worker.Create(OpCodes.Call, end);
//-2 cause you need to go before the return opcode
Instruction lastinstruction= method.Body.Instructions[method.Body.Instructions.Count-2];
worker.InsertAfter(lastinstruction, endSentence);
worker.InsertAfter(endSentence, callEnd);
Instruction firstinstruction = method.Body.Instructions[0];
worker.InsertBefore(firstinstruction, startSentence);
worker.InsertAfter(startSentence, callStart);
}
}
}
AssemblyFactory.SaveAssembly(assembly, pathBin.Replace(".exe","")+"-new-.exe");
}
public class ProfileClient
{
public static void Start(string method,params object[] args)
{
Console.WriteLine("Start " + method+">");
}
public static void End(string method, params object[] args)
{
Console.WriteLine(">End "+ method);
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("My Code2 ");
}
}
So if you build the last program and run it, it will output
My Code2
Once you run the second console application and then run ConsoleApplication2-new-.exe you will get
Start Main>
My Code2 ?
>End Main
I’m still not sure where the ? comes from but for now this simple profiler works and will be the basis of our more complex profiler we are planning to start constructing.