// // MethodBodyReader.cs // // Author: // Jb Evain (jbevain@novell.com) // // (C) 2009 - 2010 Novell, Inc. (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; using UnityEngine; namespace Mono.Reflection { class MethodBodyReader { static readonly OpCode [] one_byte_opcodes; static readonly OpCode [] two_bytes_opcodes; static MethodBodyReader () { one_byte_opcodes = new OpCode [0xe1]; two_bytes_opcodes = new OpCode [0x1f]; var fields = typeof (OpCodes).GetFields ( BindingFlags.Public | BindingFlags.Static); foreach (var field in fields) { var opcode = (OpCode) field.GetValue (null); if (opcode.OpCodeType == OpCodeType.Nternal) continue; if (opcode.Size == 1) one_byte_opcodes [opcode.Value] = opcode; else two_bytes_opcodes [opcode.Value & 0xff] = opcode; } } readonly MethodBase method; readonly MethodBody body; readonly Module module; readonly Type [] type_arguments; readonly Type [] method_arguments; readonly ByteBuffer il; readonly ParameterInfo this_parameter; readonly ParameterInfo [] parameters; readonly IList locals; readonly List instructions; MethodBodyReader (MethodBase method) { this.method = method; this.body = method.GetMethodBody (); if (this.body == null) throw new ArgumentException ("Method has no body"); var bytes = body.GetILAsByteArray (); if (bytes == null) throw new ArgumentException ("Can not get the body of the method"); if (!(method is ConstructorInfo)) method_arguments = method.GetGenericArguments (); if (method.DeclaringType != null) type_arguments = method.DeclaringType.GetGenericArguments (); if (!method.IsStatic) this.this_parameter = new ThisParameter (method); this.parameters = method.GetParameters (); this.locals = body.LocalVariables; this.module = method.Module; this.il = new ByteBuffer (bytes); this.instructions = new List ((bytes.Length + 1) / 2); } 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; } ResolveBranches (); } void ReadOperand (Instruction instruction) { switch (instruction.OpCode.OperandType) { case OperandType.InlineNone: break; case OperandType.InlineSwitch: int length = il.ReadInt32 (); int base_offset = il.position + (4 * length); int [] branches = new int [length]; for (int i = 0; i < length; i++) branches [i] = il.ReadInt32 () + base_offset; 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: { int metaDataToken = il.ReadInt32 (); try { instruction.Operand = module.ResolveMember (metaDataToken, type_arguments, method_arguments); } catch(BadImageFormatException) { instruction.Operand = module.ResolveMember (metaDataToken); } break; } case OperandType.InlineType: instruction.Operand = module.ResolveType (il.ReadInt32 (), type_arguments, method_arguments); break; case OperandType.InlineMethod: { int metaDataToken = il.ReadInt32 (); try { instruction.Operand = module.ResolveMethod (metaDataToken, type_arguments, method_arguments); } catch(TypeLoadException) { } break; } case OperandType.InlineField: instruction.Operand = module.ResolveField (il.ReadInt32 (), type_arguments, method_arguments); 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 (); } } void ResolveBranches () { foreach (var instruction in instructions) { switch (instruction.OpCode.OperandType) { case OperandType.ShortInlineBrTarget: case OperandType.InlineBrTarget: instruction.Operand = GetInstruction (instructions, (int) instruction.Operand); break; case OperandType.InlineSwitch: var offsets = (int []) instruction.Operand; var branches = new Instruction [offsets.Length]; for (int j = 0; j < offsets.Length; j++) branches [j] = GetInstruction (instructions, offsets [j]); instruction.Operand = branches; break; } } } static Instruction GetInstruction (List instructions, int offset) { var size = instructions.Count; if (offset < 0 || offset > instructions [size - 1].Offset) return null; int min = 0; int max = size - 1; while (min <= max) { int mid = min + ((max - min) / 2); var instruction = instructions [mid]; var instruction_offset = instruction.Offset; if (offset == instruction_offset) return instruction; if (offset < instruction_offset) max = mid - 1; else min = mid + 1; } return null; } object GetVariable (Instruction instruction, int index) { return TargetsLocalVariable (instruction.OpCode) ? (object) GetLocalVariable (index) : (object) GetParameter (index); } static bool TargetsLocalVariable (OpCode opcode) { return opcode.Name.Contains ("loc"); } LocalVariableInfo GetLocalVariable (int index) { return locals [index]; } ParameterInfo GetParameter (int index) { if (method.IsStatic) return parameters [index]; if (index == 0) return this_parameter; return parameters [index - 1]; } OpCode ReadOpCode () { byte op = il.ReadByte (); return op != 0xfe ? one_byte_opcodes [op] : two_bytes_opcodes [il.ReadByte ()]; } public static List GetInstructions (MethodBase method) { var reader = new MethodBodyReader (method); reader.ReadInstructions (); return reader.instructions; } class ThisParameter : ParameterInfo { public ThisParameter (MethodBase method) { this.MemberImpl = method; this.ClassImpl = method.DeclaringType; this.NameImpl = "this"; this.PositionImpl = -1; } } } }