Static Method Interception in .NET with C# and Mono.Cecil

I was having a discussion at the speaker dinner for Richmond Code Camp 2009.1 with Kevin Hazzard and Al Tenhundfeld talking about rewriting static methods with .NET. Al was doing a talk on Intro To Mocking and had downloaded and implemented TypeMock Isolator in order to show people that an option was available for testing legacy code which was not written in a way which allowed for easy interception. He was curious about how TypeMock Isolator worked in order to do its runtime modification of static methods.

Since I have never really looked too deeply into the subject, I wasn’t entirely sure of the answer. I knew that TypeMock had a required runner that it used in order to execute its tests, and so I conjectured that there might be some kind of post-compile step ala Postsharp or Mono.Cecil that was occurring. But, as usual, I was incorrect. It turns out that TypeMock isolator is actually using the .NET Profiling API in order to inject IL into the running tests. The .NET profiling API is a COM interface implemented on the .NET runtime which allows a developer to write a library which can put hooks in place for the purpose of profiling application execution inside of a running application without having to modify the application. Due to the way in which it is implemented (and this is from reading other people’s articles) it basically allows the developer to sidestep the security in the .net framework and rewrite IL at JIT time. There is an old article on this available from the September 2003 issue of MSDN. I have no idea how up to date and accurate it is, so I’ll leave you to your own devices.

After I found this out, I honestly wasn’t really interested in the implementation anymore. Basically the profiler API requires an external application, can really degrade application performance, exposes potential security concerns, and just all around isn’t useful for a commercial application. (It was never designed to be used in a production application) So instead I used it as an excuse to start looking into some IL rewriting tools that exist out there. I began wondering if someone wanted to implement a similar style tool using post-compile IL modification, how would one do it?

Before we get to that though, let’s first talk about how Mono.Cecil works when it comes to post-compile IL modification. Mono.Cecil allows you to load an already compiled assembly, modify it in any number of ways, and then save the modified assembly back to disk. I will show you an example of how this works by creating two projects. My first project is a command line application that has a separate assembly that it references:

image

The program is very simple, it runs and calls the method “MyMethod” on the “TestClass” class and then writes the result out to the console:

static void Main(string[] args)
{
    string result = TestClass.MyMethod();
    Console.WriteLine(result);
}

The method on “TestClass” write also writes a value out to the console and then returns it:

public class TestClass
{
    public static string MyMethod()
    {
        string result = "Do Something";
        Console.WriteLine(result);
        return result;
    }
}

When we run this, we see this on the console:

CecilCommandLine

Very simple. So now we need to create a separate solution, this time we are going to create an application which is going to modify our already compiled assemblies. I created this new solution and dropped my application into the bin folder with the new app. The first thing I need to do is add a reference to the Mono.Cecil.dll and then reference the Mono.Cecil and Mono.Cecil.Cil namespaces.

Next I need to load up the dll that I am planning on working with:

AssemblyDefinition assembly = AssemblyFactory.GetAssembly("CecilTestLibrary.dll");

And then pull out the type that I am looking for (notice that Mono.Cecil implements all of its own classes instead of using .NET types such as “Type”):

TypeDefinition type = assembly.MainModule.Types["CecilTestLibrary.TestClass"];

Next I need to find the method on the type:

MethodDefinition foundMethod = type.Methods.OfType<MethodDefinition>()
    .Where(m => m.Name == "MyMethod" && m.Parameters.Count == 0).Single();

And then get a CilWorker off of the method. This allows me to make modifications to this method’s CIL.

CilWorker worker = foundMethod.Body.CilWorker;

Next I need to remove all of the current CIL from the method:

foundMethod.Body.Instructions.Clear();

And then create my new instructions. Notice that we are now just returning the value, we are no longer writing it out to the console. We replaced the entire logic of the method with just a dummy return value:

Instruction ins1 = worker.Create(OpCodes.Ldstr, "New Result!");
Instruction ins2 = worker.Create(OpCodes.Ret);

Then inject those into the method:

worker.Append(ins1);
worker.Append(ins2);

Finally I need to save off the newly modified assembly:

AssemblyFactory.SaveAssembly(assembly, "CecilTestLibrary.Patched.dll");

Then all I do is rename my original assembly and then rename my patched assembly. Now when I run my method, I see this at the command prompt:

CecilCommandLineRewritten

Pretty awesome, and quite simple. But could we think of a way to implement a system like TypeMock using only post-compile assembly rewriting? No, probably not. In order to do it we would have to rewrite both the target and the running assembly. The reason is that if we need to mock out a static method call, we would still have to have multiple implementations. We could write branch logic into a static method call in order to have multiple implementations inside of it, but we would have to know at runtime which branch we needed to enter. So we would have to rewrite the calling assembly as well in order to pass some extra info (or replace the method call altogether) to the static method so that we can execute the code that we want.

If you have any great ideas on how this could be implemented, let me know! I probably won’t implement it, but I’d love to hear it. In the meantime, also let me know if you are using Mono.Cecil for anything, or if you have any great ideas for how you might use it. My initial thoughts are to actually use this for profiling code since it adds so little overhead. Although there are already tools like PostSharp that can add these abilities for you without having to spit out CIL. Hope this helps!

Be Sociable, Share!

9 comments

  1. Justin –
    Great post. And you’ve got a pretty good description on how Typemock Isolator works, and its primary uses.

    But here are some news: There’s an open source project called CThru (http://cthru.codeplex.com/) built on top of Isolator, that can allow you to use it as an AOP engine. You’d use it to write your custom code to run, without resorting to IL compilation.

    PS – Did you know that as an MVP you can get your own license of Isolator for free? If you’d like to try it, let me know!

    Gil Zilberfeld
    Typemock

  2. @Gil I was under the impression that the method which TypeMock uses for interception is not at all recommended for use in a production application. If this is the case, then why would I want to use it as an AOP framework? Sounds technically interesting though.

  3. Justin,

    Not sure about this recommendation – where did you here that?

    The only thing I can think of, is that you will pay a performance penalty. It doesn’t matter for tests, but may impact the performance in production. Sometimes it may not matter, though.

    If you want more technical info, let me know. I’ll be happy to discuss this offline.

  4. I’m using Mono.Cecil within mdoc (http://www.mono-project.com/Mdoc), specifically within [b]mdoc-update[/b], to look for all exception types that are created and then generate <exception/> documentation elements for those exception types.

    It currently has some corner cases (http://www.go-mono.org/docs/index.aspx?link=man:mdoc-update(1) lists them), but I’ve found it helpful when writing class library documentation.

  5. I tried to leave a comment yesterday… I thought it posted but apparently not.

    I’ve tried to do the same thing with PostSharp ( http://jamesryangray.blogspot.com/2008/10/stubbing-static-methods-with-postsharp.html ). You’ve definitely identified one of the most difficult challenges – intercepting method calls in the test assembly in addition to the assembly under test.

    In theory, though, using either of these frameworks to create a mocking framework with the capabilities of TypeMock should work. I plan to continue my project… sometime… :/

    I enjoy your blog; keep up the good work!

    -Ryan

  6. @Ryan Yeah, I can imagine a system where this could work. Your attempts with Post-Sharp are laudable, but like you mention in your post, don’t work for any assemblies other than your own. Theoretically, if you were to use Cecil then you could make it work on any assembly.

    Good luck when you get around to implementing it!

  7. You might be interested in Jason Bocks articles on CCI and Cecil too. Check em out here: http://www.jasonbock.net

  8. @Donn Thanks, I’ll check it out!

  9. I’ve been working with Cecil for years, and rewriting IL is really just the tip of the iceberg. Take a look:

    http://www.codeproject.com/KB/cs/LinFuPart6.aspx

    :)

Leave a Reply

Your email address will not be published. Required fields are marked *