Reflection based CIL reader 30 Apr 2009


Le diamant Update: the code has been moved to its own project page. As I was writing, earlier this month, when I worked on a static aspect weaver, the first library we used, to programmatically retrieve the CIL bytecode, was a library published by Lutz Roeder (the original author of the most famous Reflector tool), called ILReader. It suffered from a number of limitation, and you were tied to the whole System.Reflection infrastructure. Which, during the .net 1.0 time, was somewhat limited, and lacked a few features required to get access to every single detail in an assembly, including the CIL bytecode. It evolved since, for instance, starting from .net 2.0, there's a GetILAsByteArray on a MethodBody used to get the raw CIL code. Anyway, most of those concerns were addressed by Cecil, but still, for some use-cases, it could be nice to be able to have access to the CIL bytecode at a higher level of abstraction than a plain raw byte array. On .net, you can use a library also named ILReader, but it has a few checks that are specific to .net, there's no information about a license of the code, and also, I'm not especially fond of the way instructions are represented. So last time, for an hack I'll soon write about, I extracted Mono.Cecil's Instruction type, and wrote a cute extension method, or rock, as I like to call them. Its signature:
public IList GetInstructions (this MethodBase self)
I would have loved to declare the extension method on the System.Reflection.MethodBody type, to make things more consistent with the methods it already has, but there's no cross platform way to get a System.Reflection.MethodBase from a System.Reflection.MethodBody. Anyway, it's really easy to use if you've already used Cecil. The only difference is that for branches, the operand is the offset as an integer, not the target instruction. As a sample usage, here's a (very) incomplete CIL reflection based disassembler:
static void PrintByteCode (MethodInfo method)
{
	foreach (Instruction instruction in method.GetInstructions ())
		PrintInstruction (instruction);
}

static void PrintInstruction (Instruction instruction)
{
	Console.Write ("{0}: {1} ",
		Labelize (instruction.Offset),
		instruction.OpCode.Name);

	switch (instruction.OpCode.OperandType) {
	case OperandType.InlineNone :
		break;
	case OperandType.InlineSwitch :
		var branches = instruction.Operand as int [];
		for (int i = 0; i < branches.Length; i++) {
			if (i > 0)
				Console.Write (", ");
			Console.Write (Labelize (branches [i]));
		}
		break;
	case OperandType.ShortInlineBrTarget :
	case OperandType.InlineBrTarget :
		Console.Write (Labelize ((int) instruction.Operand));
		break;
	case OperandType.InlineString :
		Console.Write ("\"{0}\"", instruction.Operand);
		break;
	default :
		Console.WriteLine (instruction.Operand);
		break;
	}

	Console.WriteLine ();
}
And of course, you're welcome to have a look at the implementation, under the MIT/X11 license.