IL disassembly in C#

by ingvar 14. august 2010 18:30

In this post I'm going to give an example of how to get the IL instructions (op-codes and operands) from any given .NET method body. I used this in a large project to get an overview of inter namespace referencing. Example: Finding references from the namespace A.B to the namespace A.C. There is tons of other uses, like finding what methods another method calls and so on. So stay tuned!

It all starts with the class MethodBody and the method GetILAsByteArray. To obtain a MethodBody you need to get a MethodInfo/MethodBase and then call the GetMethodBody method. GetILAsByteArray returns an array of IL bytes for the given method body. In code its done like this:

/* Find the MethodInfo of interest */
MethodInfo methodInfo =
   GetMethods(BindingFlags.NonPublic | BindingFlags.Static).
   Where(f => f.Name == "Main").

MethodBody methodBody = methodInfo.GetMethodBody();
byte[] ilBytes = methodBody.GetILAsByteArray();

So now we need to process this byte array. The array contains a series of operation pairs: (operation code, operand). These pairs vary in size. Some operation codes are 1 byte and some 2 bytes. Operands also vary in size, these sizes could be 0, 1, 4, 8 or variable number of bytes.
First lets find the operation code (System.Reflection.Emit.OpCode):

int offset = 0; /* this is used to keep track of our position in the ilBytes buffer. */

while (offset < ilBytes.Length)
  short code = (short)ilBytes[offset++];
  if (code == 0xfe)
     code = (short)(ilBytes[offset++] | 0xfe00);

  OpCode opCode =
     Where(f => f.GetValue(null) == value).

  /* Handle operand and update offset correctly. */

So now we have the operation code (or op-code for short). Using the op-code we can find the size of the operand and its meaning. In this post i skip most of the op-codes. Actually I only look at the one regarding method calls (OperandType.InlineMethod). The rest of the op-code cases only updates the offset variable. Note that newing up a new instanse of a type mean "calling" its constructor. In other words a OperandType.InlineMethod instruction. So by focusing on OperandType.InlineMethod I capture all type references whether its method calling or newing up a new instance.

/* This is the implementation for: Handle operand and update offset correctly. */
switch (opCode.OpCode.OperandType)
  case OperandType.InlineMethod:
     int metaDataToken = bytes.GetInt32(offset);

     Type[] genericMethodArguments = null;
     if (methodBase.IsGenericMethod == true)
         genericMethodArguments = methodBase.GetGenericArguments();

     instruction.Data =
     offset += 4;

  case OperandType.InlineNone:

  case OperandType.ShortInlineBrTarget:
  case OperandType.ShortInlineI:
  case OperandType.ShortInlineVar:
     offset += 1;
  case OperandType.InlineVar:
     offset += 2;

  case OperandType.InlineBrTarget:
  case OperandType.InlineField:
  case OperandType.InlineI:
  case OperandType.InlineSig:
  case OperandType.InlineString:
  case OperandType.InlineTok:
  case OperandType.InlineType:
  case OperandType.ShortInlineR:
     offset += 4;

  case OperandType.InlineI8:
  case OperandType.InlineR:
     offset += 8;

  case OperandType.InlineSwitch:
     int count = bytes.GetInt32(offset) + 1;
     offset += 4 * count;

     throw new NotImplementedException();

There we go! Now the only thing left is to iterate through all types and all methods and collect inter-namespace references. You can download a demo project below. The zip also contains a lot of other goodies! (7.17 kb)

Tags: , , ,

.NET | C#

About the author

Martin Ingvar Kofoed Jensen

Architect and Senior Developer at Composite on the open source project Composite C1 - C#/4.0, LINQ, Azure, Parallel and much more!

Follow me on Twitter

Read more about me here.

Read press and buzz about my work and me here.

Stack Overflow

Month List