Sunday, May 8, 2011

Introduction to IL Rewriting with Cecil, Part 1–Rewriting FizzBuzz and the Art of Redirecting Method Calls

lesson1

The simplest possible code example that anyone can learn from.

Introduction

When I first started learning IL rewriting and Cecil about several years ago, one of the difficulties that I struggled with was the fact that there were very few practical examples on how to take an existing assembly and modify it at runtime. In many ways, I was stranded in heavily undocumented territory, and needless to say, this lack of documentation made it very difficult to learn how to do anything useful with Cecil.

Meanwhile, in the Year 2011…

It’s now 2011, and I think it’s safe to say that for many people, IL rewriting (much less Cecil) is still a “big mystery wrapped in an enigma containing frustration”. Indeed, Cecil is an incredible library that can let you do some incredible things, but at the same time, it can be very frustrating since the learning curve is still steep and there are still no practical guides for using it. As a user, there must be some sample code out there that shows how to do the most basic tasks with Cecil, right?

Establishing the Feedback Loop

In order to learn any skill (such as IL rewriting), we need to establish a simple feedback loop that allows users to easily experiment with the tools they are given so that they know:

  • What went wrong if it doesn’t work
  • Where to fix it if it breaks
  • How to see the results of their experiments without getting mired in the implementation details of the tests themselves

In this case, we’ll need to set up a basic environment that will let users experiment and learn how to modify assemblies at runtime with Cecil. We will need:

  1. A test fixture that loads a sample assembly and gives users the chance to modify it before reloading the modified assembly into memory (An NUnit base fixture)
  2. A way to display/diagnose any invalid assembly errors that occur due to making changes to the original assembly (PEVerify)
  3. To make it easy to change so that we can experiment with different approaches to modifying IL, thus “closing” the feedback loop

Lost in Bytecode

Given these requirements, where would we even begin? It’s not every day that one decides to randomly parse .NET assemblies and learn how to change the underlying bytecode that ultimately defines their behavior. This can seem like a daunting task for even the most intelligent of budding interlopers, but fortunately for my readers, most of the work has already been done for you in these examples. All you need to do is sit back and scroll down the page, as I proceed to tell you the “ins” and “outs” about Cecil, and the practical lessons learned from rewriting IL. With that in mind, let’s get started!

A WriteLine for Another WriteLine

One of the simplest things that you can possibly do with Cecil is to swap a single static method call for another static method call with the same parameters and the same return type. (It’s a fairly simple operation since both methods have the same signature, and you don’t need to add any additional instructions to make it happen). In this case, I opted to swap all calls to Console.WriteLine() with calls to FakeConsole.WriteLine():

As you can see from the example above, I used a simple LINQ query to identify all the call instructions that needed to be modified. More experienced Cecil users will probably notice that I decided to rewrite all method calls to point to FakeConsole.WriteLine() instead of individually checking to make sure that the method call I was replacing was indeed Console.WriteLine(). Indeed, that was an intentional move, given that the FizzBuzz.Print() method doesn’t make any other external calls to any other methods besides Console.WriteLine().

Assuming that I somehow created an instruction that caused an invalid modified assembly, however, how would I be able to know what went wrong, much less know how to fix it?

PEVerify, how do I love and hate thee…

As it turns out, there is a tool called PEVerify.exe that can tell you whether or not the assemblies that you modify with Cecil are valid or invalid. For example, if I were to remove all the IL instructions out of the FizzBuzz.Print() method, PEVerify would give me the following error message:

peverify1

(Believe me, it’s much prettier when it’s zoomed out)

PEVerify will examine any given assembly and be able to tell you whether or not the compiler (or in this case, you, the human compiler) made any mistakes in creating the assembly. It can be a very useful tool, and that’s why I modified the sample test fixture to run PEVerify right after the user modifies the sample assembly. If you don’t already have PEVerify installed, make sure you download it and configure the Lesson1 app.config file to point to where PEVerify is installed:


Once PEVerify has been configured as part of the tests, the rest is up to your imagination.

Exploring Method Replacement and Beyond

Now that the basic IL rewriting setup has been laid out for you, the onus is on you to explore the possibilities with Cecil and IL rewriting, even if it means that you have to start with some small, basic steps. In the next installment in this series, I’ll show you how to use PEVerify and Cecil to keep the stack balanced so that you can do things like swap static method calls for instance method calls, and even do things like install runtime hooks so you can change your code as your application is running. Stay tuned!

Wednesday, May 4, 2011

Dynamically Intercepting Thrown Exceptions with LinFu.AOP 2.0

exception

A screenshot of LinFu dynamically catching a thrown exception.

On Error, Resume Interception

Another useful thing that LinFu.AOP allows you to do is to intercept (and rethrow) exceptions within your applications at runtime. LinFu makes it so easy, in fact, that all you have to do is add the following lines to your CSProj file:


Exceptionally Simple

To use LinFu.AOP's dynamic exception handling capabilities, all you need to do is make the following call to handle all exceptions being thrown in your application:


Try/Catch Me, If You Can

The call to ExceptionHandlerRegistry.SetHandler tells LinFu to hook the SampleExceptionHandler into your application so that all exceptions that will be thrown will automatically be handled by the given exception handler. Under normal circumstances (where interception is disabled), the call to account.Deposit() will cause the app to crash, but as this example shows, LinFu.AOP was able to intercept the thrown exception before it could crash the rest of the app.

What makes this even more interesting, however, is the IExceptionHandlerInfo instance that describes the context from which the exception was thrown:


More Information Than You Can Throw An Exception At


The IExceptionHandlerInfo interface has enough information to describe the method that caused the exception, as well as having properties such as ShouldSkipRethrow that allow you to decide whether or not LinFu should just swallow the exception and keep running the program as if an exception was never thrown. The ReturnValue property, in turn, allows you to alter the return value of a given method in case you want to resume the method and provide an alternate return value as if no exceptions were ever thrown.

As you can see, LinFu.AOP makes it really easy to transparently handle exceptions in your applications, and if this post saves at least a few developers a few headaches from having to manually diagnose their applications, then I'd consider it to be a gratifying success.

Enjoy!

EDIT: You can get the code examples for LinFu.AOP's dynamic exception handling here.

Ratings by outbrain