Source

CodeShape / src / CodeShapes / Disassembler / MethodBodyReader.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace CodeShapes.Disassembler
{
	internal class MethodBodyReader
	{
		public static readonly OpCode[] OneByteOpcodes;
		public static readonly OpCode[] TwoByteOpcodes;

		private readonly MethodBase _method;
		private readonly MethodBody _body;
		private readonly Module _module;
		private readonly Type[] _typeArguments;
		private readonly Type[] _methodArguments;
		private readonly ByteBuffer _il;
		private readonly ParameterInfo[] _parameters;
		private readonly IList<LocalVariableInfo> _locals;
		private readonly List<Instruction> _instructions = new List<Instruction>();

		static MethodBodyReader()
		{
			OneByteOpcodes = new OpCode[0xe1];
			TwoByteOpcodes = new OpCode[0x1f];

			var opCodes = GetOpCodeFields()
				.Select(t => (OpCode)t.GetValue(null))
				.Where(opcode => opcode.OpCodeType != OpCodeType.Nternal);

			foreach (var opcode in opCodes)
			{
				if (opcode.Size == 1)
					OneByteOpcodes[opcode.Value] = opcode;
				else
					TwoByteOpcodes[opcode.Value & 0xff] = opcode;
			}
		}

		private static IEnumerable<FieldInfo> GetOpCodeFields()
		{
			return typeof(OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static);
		}

		public MethodBodyReader(MethodBase method)
		{
			_method = method;

			_body = method.GetMethodBody();
			if (_body == null)
			{
				_il = new ByteBuffer(new byte[]{});
				return;
			}

			var bytes = _body.GetILAsByteArray();
			if (bytes == null)
				throw new ArgumentException();

			if (!(method is ConstructorInfo))
				_methodArguments = method.GetGenericArguments();

			if (method.DeclaringType != null)
				_typeArguments = method.DeclaringType.GetGenericArguments();

			_parameters = method.GetParameters();
			_locals = _body.LocalVariables;
			_module = method.Module;
			_il = new ByteBuffer(bytes);
		}

		private void ReadInstructions()
		{
			Instruction previous = null;

			while (_il.Position < _il.Buffer.Length)
			{
				var instruction = new Instruction(_il.Position, ReadOpCode());

				ReadOperand(instruction);

				if (previous != null)
				{
					instruction.Previous = previous;
					previous.Next = instruction;
				}

				_instructions.Add(instruction);
				previous = instruction;
			}
		}

		private void ReadOperand(Instruction instruction)
		{
			switch (instruction.OpCode.OperandType)
			{
				case OperandType.InlineNone:
					break;
				case OperandType.InlineSwitch:
					var length = _il.ReadInt32();
					var branches = new int[length];
					var offsets = new int[length];
					
					for (var i = 0; i < length; i++)
					{
						offsets[i] = _il.ReadInt32();
					}

					for (var i = 0; i < length; i++)
					{
						branches[i] = _il.Position + offsets[i];
					}

					instruction.Operand = branches;
					break;
				case OperandType.ShortInlineBrTarget:
					instruction.Operand = (sbyte)(_il.ReadByte() + _il.Position);
					break;
				case OperandType.InlineBrTarget:
					instruction.Operand = _il.ReadInt32() + _il.Position;
					break;
				case OperandType.ShortInlineI:
					if (instruction.OpCode == OpCodes.Ldc_I4_S)
					{
						instruction.Operand = (sbyte)_il.ReadByte();
					}
					else
					{
						instruction.Operand = _il.ReadByte();
					}
					break;
				case OperandType.InlineI:
					instruction.Operand = _il.ReadInt32();
					break;
				case OperandType.ShortInlineR:
					instruction.Operand = _il.ReadSingle();
					break;
				case OperandType.InlineR:
					instruction.Operand = _il.ReadDouble();
					break;
				case OperandType.InlineI8:
					instruction.Operand = _il.ReadInt64();
					break;
				case OperandType.InlineSig:
					instruction.Operand = _module.ResolveSignature(_il.ReadInt32());
					break;
				case OperandType.InlineString:
					instruction.Operand = _module.ResolveString(_il.ReadInt32());
					break;
				case OperandType.InlineTok:
					instruction.Operand = _module.ResolveMember(_il.ReadInt32(), _typeArguments, _methodArguments);
					break;
				case OperandType.InlineType:
					instruction.Operand = _module.ResolveType(_il.ReadInt32(), _typeArguments, _methodArguments);
					break;
				case OperandType.InlineMethod:
					instruction.Operand = _module.ResolveMethod(_il.ReadInt32(), _typeArguments, _methodArguments);
					break;
				case OperandType.InlineField:
					instruction.Operand = _module.ResolveField(_il.ReadInt32(), _typeArguments, _methodArguments);
					break;
				case OperandType.ShortInlineVar:
					instruction.Operand = GetVariable(instruction, _il.ReadByte());
					break;
				case OperandType.InlineVar:
					instruction.Operand = GetVariable(instruction, _il.ReadInt16());
					break;
				default:
					throw new NotSupportedException();
			}
		}

		private object GetVariable(Instruction instruction, int index)
		{
			if (TargetsLocalVariable(instruction.OpCode))
			{
				return GetLocalVariable(index);
			}
			else
			{
				return GetParameter(index);
			}
		}

		private static bool TargetsLocalVariable(OpCode opcode)
		{
			return opcode.Name.Contains("loc");
		}

		private LocalVariableInfo GetLocalVariable(int index)
		{
			return _locals[index];
		}

		private ParameterInfo GetParameter(int index)
		{
			if (!_method.IsStatic)
				index--;

			return _parameters[index];
		}

		private OpCode ReadOpCode()
		{
			byte op = _il.ReadByte();
			return op != 0xfe
			       	? OneByteOpcodes[op]
			       	: TwoByteOpcodes[_il.ReadByte()];
		}

		public static List<Instruction> GetInstructions(MethodBase method)
		{
			var reader = new MethodBodyReader(method);
			reader.ReadInstructions();
			return reader._instructions;
		}
	}
}