// dnlib: See LICENSE.txt for more info using System; using System.Collections.Generic; using SR = System.Reflection; using System.Reflection.Emit; using System.IO; using dnlib.DotNet.MD; using dnlib.IO; namespace dnlib.DotNet.Emit { /// /// options /// [Flags] public enum DynamicMethodBodyReaderOptions { /// /// No option is enabled /// None = 0, /// /// Some fields/methods have an unknown declaring type and don't have a context with /// that information. If this is enabled, the reader will try to guess it but it doesn't /// always work. If you get an , try enabling this option. /// UnknownDeclaringType = 0x00000001, } /// /// Reads code from a DynamicMethod /// public class DynamicMethodBodyReader : MethodBodyReaderBase, ISignatureReaderHelper { static readonly ReflectionFieldInfo rtdmOwnerFieldInfo = new ReflectionFieldInfo("m_owner"); static readonly ReflectionFieldInfo dmResolverFieldInfo = new ReflectionFieldInfo("m_resolver", "_resolver"); static readonly ReflectionFieldInfo rslvCodeFieldInfo = new ReflectionFieldInfo("m_code"); static readonly ReflectionFieldInfo rslvDynamicScopeFieldInfo = new ReflectionFieldInfo("m_scope"); static readonly ReflectionFieldInfo rslvMethodFieldInfo = new ReflectionFieldInfo("m_method"); static readonly ReflectionFieldInfo rslvLocalsFieldInfo = new ReflectionFieldInfo("m_localSignature"); static readonly ReflectionFieldInfo rslvMaxStackFieldInfo = new ReflectionFieldInfo("m_stackSize"); static readonly ReflectionFieldInfo rslvExceptionsFieldInfo = new ReflectionFieldInfo("m_exceptions"); static readonly ReflectionFieldInfo rslvExceptionHeaderFieldInfo = new ReflectionFieldInfo("m_exceptionHeader"); static readonly ReflectionFieldInfo scopeTokensFieldInfo = new ReflectionFieldInfo("m_tokens"); static readonly ReflectionFieldInfo gfiFieldHandleFieldInfo = new ReflectionFieldInfo("m_field", "m_fieldHandle"); static readonly ReflectionFieldInfo gfiContextFieldInfo = new ReflectionFieldInfo("m_context"); static readonly ReflectionFieldInfo gmiMethodHandleFieldInfo = new ReflectionFieldInfo("m_method", "m_methodHandle"); static readonly ReflectionFieldInfo gmiContextFieldInfo = new ReflectionFieldInfo("m_context"); static readonly ReflectionFieldInfo ehCatchAddrFieldInfo = new ReflectionFieldInfo("m_catchAddr"); static readonly ReflectionFieldInfo ehCatchClassFieldInfo = new ReflectionFieldInfo("m_catchClass"); static readonly ReflectionFieldInfo ehCatchEndAddrFieldInfo = new ReflectionFieldInfo("m_catchEndAddr"); static readonly ReflectionFieldInfo ehCurrentCatchFieldInfo = new ReflectionFieldInfo("m_currentCatch"); static readonly ReflectionFieldInfo ehTypeFieldInfo = new ReflectionFieldInfo("m_type"); static readonly ReflectionFieldInfo ehStartAddrFieldInfo = new ReflectionFieldInfo("m_startAddr"); static readonly ReflectionFieldInfo ehEndAddrFieldInfo = new ReflectionFieldInfo("m_endAddr"); static readonly ReflectionFieldInfo ehEndFinallyFieldInfo = new ReflectionFieldInfo("m_endFinally"); static readonly ReflectionFieldInfo vamMethodFieldInfo = new ReflectionFieldInfo("m_method"); static readonly ReflectionFieldInfo vamDynamicMethodFieldInfo = new ReflectionFieldInfo("m_dynamicMethod"); static readonly ReflectionFieldInfo dmDynamicILInfoFieldInfo = new ReflectionFieldInfo("m_DynamicILInfo", "_dynamicILInfo"); static readonly ReflectionFieldInfo dynILInfoMaxStackFieldInfo = new ReflectionFieldInfo("m_maxStackSize"); readonly ModuleDef module; readonly Importer importer; readonly GenericParamContext gpContext; readonly MethodDef method; readonly int codeSize; readonly int maxStack; readonly bool initLocals; readonly List tokens; readonly IList ehInfos; readonly byte[] ehHeader; readonly string methodName; readonly DynamicMethodBodyReaderOptions options; class ReflectionFieldInfo { SR.FieldInfo fieldInfo; readonly string fieldName1; readonly string fieldName2; public ReflectionFieldInfo(string fieldName) => fieldName1 = fieldName; public ReflectionFieldInfo(string fieldName1, string fieldName2) { this.fieldName1 = fieldName1; this.fieldName2 = fieldName2; } public object Read(object instance) { if (fieldInfo is null) InitializeField(instance.GetType()); if (fieldInfo is null) throw new Exception($"Couldn't find field '{fieldName1}' or '{fieldName2}'"); return fieldInfo.GetValue(instance); } public bool Exists(object instance) { InitializeField(instance.GetType()); return fieldInfo is not null; } void InitializeField(Type type) { if (fieldInfo is not null) return; const SR.BindingFlags flags = SR.BindingFlags.Instance | SR.BindingFlags.Public | SR.BindingFlags.NonPublic; fieldInfo = type.GetField(fieldName1, flags); if (fieldInfo is null && fieldName2 is not null) fieldInfo = type.GetField(fieldName2, flags); } } /// /// Constructor /// /// Module that will own the method body /// This can be one of several supported types: the delegate instance /// created by DynamicMethod.CreateDelegate(), a DynamicMethod instance, a RTDynamicMethod /// instance or a DynamicResolver instance. public DynamicMethodBodyReader(ModuleDef module, object obj) : this(module, obj, new GenericParamContext()) { } /// /// Constructor /// /// Module that will own the method body /// This can be one of several supported types: the delegate instance /// created by DynamicMethod.CreateDelegate(), a DynamicMethod instance, a RTDynamicMethod /// instance or a DynamicResolver instance. /// Generic parameter context public DynamicMethodBodyReader(ModuleDef module, object obj, GenericParamContext gpContext) : this(module, obj, new Importer(module, ImporterOptions.TryToUseDefs, gpContext), DynamicMethodBodyReaderOptions.None) { } /// /// Constructor /// /// Module that will own the method body /// This can be one of several supported types: the delegate instance /// created by DynamicMethod.CreateDelegate(), a DynamicMethod instance, a RTDynamicMethod /// instance or a DynamicResolver instance. /// Importer public DynamicMethodBodyReader(ModuleDef module, object obj, Importer importer) : this(module, obj, importer, DynamicMethodBodyReaderOptions.None) { } /// /// Constructor /// /// Module that will own the method body /// This can be one of several supported types: the delegate instance /// created by DynamicMethod.CreateDelegate(), a DynamicMethod instance, a RTDynamicMethod /// instance or a DynamicResolver instance. /// Importer /// Options public DynamicMethodBodyReader(ModuleDef module, object obj, Importer importer, DynamicMethodBodyReaderOptions options) : base(module.Context) { this.module = module; this.importer = importer; this.options = options; gpContext = importer.gpContext; methodName = null; if (obj is null) throw new ArgumentNullException(nameof(obj)); if (obj is Delegate del) { obj = del.Method; if (obj is null) throw new Exception("Delegate.Method is null"); } if (obj.GetType().ToString() == "System.Reflection.Emit.DynamicMethod+RTDynamicMethod") { obj = rtdmOwnerFieldInfo.Read(obj) as DynamicMethod; if (obj is null) throw new Exception("RTDynamicMethod.m_owner is null or invalid"); } if (obj is DynamicMethod dynMethod) { methodName = dynMethod.Name; obj = dmResolverFieldInfo.Read(obj) ?? dmDynamicILInfoFieldInfo.Read(obj);; if (obj is null) throw new Exception("No resolver found"); } string objTypeName = obj.GetType().ToString(); bool isILInfo = objTypeName == "System.Reflection.Emit.DynamicILInfo"; if (objTypeName != "System.Reflection.Emit.DynamicResolver" && !isILInfo) throw new Exception("Couldn't find DynamicResolver or DynamicILInfo"); var code = rslvCodeFieldInfo.Read(obj) as byte[]; if (code is null) throw new Exception("No code"); codeSize = code.Length; var delMethod = rslvMethodFieldInfo.Read(obj) as DynamicMethod; if (delMethod is null) throw new Exception("No method"); initLocals = delMethod.InitLocals; maxStack = isILInfo ? (int)dynILInfoMaxStackFieldInfo.Read(obj) : (int)rslvMaxStackFieldInfo.Read(obj); var scope = rslvDynamicScopeFieldInfo.Read(obj); if (scope is null) throw new Exception("No scope"); var tokensList = scopeTokensFieldInfo.Read(scope) as System.Collections.IList; if (tokensList is null) throw new Exception("No tokens"); tokens = new List(tokensList.Count); for (int i = 0; i < tokensList.Count; i++) tokens.Add(tokensList[i]); if (isILInfo) ehHeader = rslvExceptionsFieldInfo.Read(obj) as byte[]; else { ehInfos = (IList)rslvExceptionsFieldInfo.Read(obj); ehHeader = rslvExceptionHeaderFieldInfo.Read(obj) as byte[]; } UpdateLocals(rslvLocalsFieldInfo.Read(obj) as byte[]); reader = ByteArrayDataReaderFactory.CreateReader(code); method = CreateMethodDef(delMethod); parameters = method.Parameters; } class ExceptionInfo { public int[] CatchAddr; public Type[] CatchClass; public int[] CatchEndAddr; public int CurrentCatch; public int[] Type; public int StartAddr; public int EndAddr; public int EndFinally; } static List CreateExceptionInfos(IList ehInfos) { if (ehInfos is null) return new List(); var infos = new List(ehInfos.Count); int count = ehInfos.Count; for (int i = 0; i < count; i++) { var ehInfo = ehInfos[i]; var eh = new ExceptionInfo { CatchAddr = (int[])ehCatchAddrFieldInfo.Read(ehInfo), CatchClass = (Type[])ehCatchClassFieldInfo.Read(ehInfo), CatchEndAddr = (int[])ehCatchEndAddrFieldInfo.Read(ehInfo), CurrentCatch = (int)ehCurrentCatchFieldInfo.Read(ehInfo), Type = (int[])ehTypeFieldInfo.Read(ehInfo), StartAddr = (int)ehStartAddrFieldInfo.Read(ehInfo), EndAddr = (int)ehEndAddrFieldInfo.Read(ehInfo), EndFinally = (int)ehEndFinallyFieldInfo.Read(ehInfo), }; infos.Add(eh); } return infos; } void UpdateLocals(byte[] localsSig) { if (localsSig is null || localsSig.Length == 0) return; var sig = SignatureReader.ReadSig(this, module.CorLibTypes, localsSig, gpContext) as LocalSig; if (sig is null) return; var sigLocals = sig.Locals; int count = sigLocals.Count; for (int i = 0; i < count; i++) locals.Add(new Local(sigLocals[i])); } MethodDef CreateMethodDef(SR.MethodBase delMethod) { bool isStatic = true; var method = new MethodDefUser(); var retType = GetReturnType(delMethod); var pms = GetParameters(delMethod); if (isStatic) method.Signature = MethodSig.CreateStatic(retType, pms.ToArray()); else method.Signature = MethodSig.CreateInstance(retType, pms.ToArray()); method.Parameters.UpdateParameterTypes(); method.ImplAttributes = MethodImplAttributes.IL; method.Attributes = MethodAttributes.PrivateScope; if (isStatic) method.Attributes |= MethodAttributes.Static; return module.UpdateRowId(method); } TypeSig GetReturnType(SR.MethodBase mb) { if (mb is SR.MethodInfo mi) return importer.ImportAsTypeSig(mi.ReturnType); return module.CorLibTypes.Void; } List GetParameters(SR.MethodBase delMethod) { var pms = new List(); foreach (var param in delMethod.GetParameters()) pms.Add(importer.ImportAsTypeSig(param.ParameterType)); return pms; } /// /// Reads the code /// /// public bool Read() { ReadInstructionsNumBytes((uint)codeSize); CreateExceptionHandlers(); return true; } void CreateExceptionHandlers() { if (ehHeader is not null) { if (ehHeader.Length < 4) return; var reader = new BinaryReader(new MemoryStream(ehHeader)); byte b = reader.ReadByte(); if ((b & 0x40) == 0) { // DynamicResolver only checks bit 6 // Calculate num ehs exactly the same way that DynamicResolver does int numHandlers = (ushort)((reader.ReadByte() - 2) / 12); reader.ReadUInt16(); for (int i = 0; i < numHandlers; i++) { if (reader.BaseStream.Position + 12 > reader.BaseStream.Length) break; var eh = new ExceptionHandler(); eh.HandlerType = (ExceptionHandlerType)reader.ReadUInt16(); int offs = reader.ReadUInt16(); eh.TryStart = GetInstructionThrow((uint)offs); eh.TryEnd = GetInstruction((uint)(reader.ReadByte() + offs)); offs = reader.ReadUInt16(); eh.HandlerStart = GetInstructionThrow((uint)offs); eh.HandlerEnd = GetInstruction((uint)(reader.ReadByte() + offs)); if (eh.IsCatch) eh.CatchType = ReadToken(reader.ReadUInt32()) as ITypeDefOrRef; else if (eh.IsFilter) eh.FilterStart = GetInstruction(reader.ReadUInt32()); else reader.ReadUInt32(); exceptionHandlers.Add(eh); } } else { reader.BaseStream.Position--; int numHandlers = (ushort)(((reader.ReadUInt32() >> 8) - 4) / 24); for (int i = 0; i < numHandlers; i++) { if (reader.BaseStream.Position + 24 > reader.BaseStream.Length) break; var eh = new ExceptionHandler(); eh.HandlerType = (ExceptionHandlerType)reader.ReadUInt32(); var offs = reader.ReadUInt32(); eh.TryStart = GetInstructionThrow((uint)offs); eh.TryEnd = GetInstruction((uint)(reader.ReadUInt32() + offs)); offs = reader.ReadUInt32(); eh.HandlerStart = GetInstructionThrow((uint)offs); eh.HandlerEnd = GetInstruction((uint)(reader.ReadUInt32() + offs)); if (eh.IsCatch) eh.CatchType = ReadToken(reader.ReadUInt32()) as ITypeDefOrRef; else if (eh.IsFilter) eh.FilterStart = GetInstruction(reader.ReadUInt32()); else reader.ReadUInt32(); exceptionHandlers.Add(eh); } } } else if (ehInfos is not null) { foreach (var ehInfo in CreateExceptionInfos(ehInfos)) { var tryStart = GetInstructionThrow((uint)ehInfo.StartAddr); var tryEnd = GetInstruction((uint)ehInfo.EndAddr); var endFinally = ehInfo.EndFinally < 0 ? null : GetInstruction((uint)ehInfo.EndFinally); for (int i = 0; i < ehInfo.CurrentCatch; i++) { var eh = new ExceptionHandler(); eh.HandlerType = (ExceptionHandlerType)ehInfo.Type[i]; eh.TryStart = tryStart; eh.TryEnd = eh.IsFinally ? endFinally : tryEnd; eh.FilterStart = null; // not supported by DynamicMethod.ILGenerator eh.HandlerStart = GetInstructionThrow((uint)ehInfo.CatchAddr[i]); eh.HandlerEnd = GetInstruction((uint)ehInfo.CatchEndAddr[i]); eh.CatchType = importer.Import(ehInfo.CatchClass[i]); exceptionHandlers.Add(eh); } } } } /// /// Returns the created method. Must be called after . /// /// A new instance public MethodDef GetMethod() { var cilBody = new CilBody(initLocals, instructions, exceptionHandlers, locals); cilBody.MaxStack = (ushort)Math.Min(maxStack, ushort.MaxValue); instructions = null; exceptionHandlers = null; locals = null; method.Body = cilBody; method.Name = methodName; return method; } /// protected override IField ReadInlineField(Instruction instr) => ReadToken(reader.ReadUInt32()) as IField; /// protected override IMethod ReadInlineMethod(Instruction instr) => ReadToken(reader.ReadUInt32()) as IMethod; /// protected override MethodSig ReadInlineSig(Instruction instr) => ReadToken(reader.ReadUInt32()) as MethodSig; /// protected override string ReadInlineString(Instruction instr) => ReadToken(reader.ReadUInt32()) as string ?? string.Empty; /// protected override ITokenOperand ReadInlineTok(Instruction instr) => ReadToken(reader.ReadUInt32()) as ITokenOperand; /// protected override ITypeDefOrRef ReadInlineType(Instruction instr) => ReadToken(reader.ReadUInt32()) as ITypeDefOrRef; object ReadToken(uint token) { uint rid = token & 0x00FFFFFF; switch (token >> 24) { case 0x02: return ImportType(rid); case 0x04: return ImportField(rid); case 0x06: case 0x0A: return ImportMethod(rid); case 0x11: return ImportSignature(rid); case 0x70: return Resolve(rid) as string; default: return null; } } IMethod ImportMethod(uint rid) { var obj = Resolve(rid); if (obj is null) return null; if (obj is RuntimeMethodHandle) { if ((options & DynamicMethodBodyReaderOptions.UnknownDeclaringType) != 0) { // Sometimes it's a generic type but obj != `GenericMethodInfo`, so pass in 'default' and the // runtime will try to figure out the declaring type. https://github.com/0xd4d/dnlib/issues/298 return importer.Import(SR.MethodBase.GetMethodFromHandle((RuntimeMethodHandle)obj, default)); } else return importer.Import(SR.MethodBase.GetMethodFromHandle((RuntimeMethodHandle)obj)); } if (obj.GetType().ToString() == "System.Reflection.Emit.GenericMethodInfo") { var context = (RuntimeTypeHandle)gmiContextFieldInfo.Read(obj); var method = SR.MethodBase.GetMethodFromHandle((RuntimeMethodHandle)gmiMethodHandleFieldInfo.Read(obj), context); return importer.Import(method); } if (obj.GetType().ToString() == "System.Reflection.Emit.VarArgMethod") { var method = GetVarArgMethod(obj); if (!(method is DynamicMethod)) return importer.Import(method); obj = method; } if (obj is DynamicMethod dm) throw new Exception("DynamicMethod calls another DynamicMethod"); return null; } SR.MethodInfo GetVarArgMethod(object obj) { if (vamDynamicMethodFieldInfo.Exists(obj)) { // .NET Framework 4.0+ var method = vamMethodFieldInfo.Read(obj) as SR.MethodInfo; var dynMethod = vamDynamicMethodFieldInfo.Read(obj) as DynamicMethod; return dynMethod ?? method; } else { // .NET Framework 2.0 // This is either a DynamicMethod or a MethodInfo return vamMethodFieldInfo.Read(obj) as SR.MethodInfo; } } IField ImportField(uint rid) { var obj = Resolve(rid); if (obj is null) return null; if (obj is RuntimeFieldHandle) { if ((options & DynamicMethodBodyReaderOptions.UnknownDeclaringType) != 0) { // Sometimes it's a generic type but obj != `GenericFieldInfo`, so pass in 'default' and the // runtime will try to figure out the declaring type. https://github.com/0xd4d/dnlib/issues/298 return importer.Import(SR.FieldInfo.GetFieldFromHandle((RuntimeFieldHandle)obj, default)); } else return importer.Import(SR.FieldInfo.GetFieldFromHandle((RuntimeFieldHandle)obj)); } if (obj.GetType().ToString() == "System.Reflection.Emit.GenericFieldInfo") { var context = (RuntimeTypeHandle)gfiContextFieldInfo.Read(obj); var field = SR.FieldInfo.GetFieldFromHandle((RuntimeFieldHandle)gfiFieldHandleFieldInfo.Read(obj), context); return importer.Import(field); } return null; } ITypeDefOrRef ImportType(uint rid) { var obj = Resolve(rid); if (obj is RuntimeTypeHandle) return importer.Import(Type.GetTypeFromHandle((RuntimeTypeHandle)obj)); return null; } CallingConventionSig ImportSignature(uint rid) { var sig = Resolve(rid) as byte[]; if (sig is null) return null; return SignatureReader.ReadSig(this, module.CorLibTypes, sig, gpContext); } object Resolve(uint index) { if (index >= (uint)tokens.Count) return null; return tokens[(int)index]; } /// public override void RestoreMethod(MethodDef method) { base.RestoreMethod(method); var body = method.Body; body.InitLocals = initLocals; body.MaxStack = (ushort)Math.Min(maxStack, ushort.MaxValue); } ITypeDefOrRef ISignatureReaderHelper.ResolveTypeDefOrRef(uint codedToken, GenericParamContext gpContext) { if (!CodedToken.TypeDefOrRef.Decode(codedToken, out uint token)) return null; switch (MDToken.ToTable(token)) { case Table.TypeDef: case Table.TypeRef: case Table.TypeSpec: return module.ResolveToken(token) as ITypeDefOrRef; } return null; } TypeSig ISignatureReaderHelper.ConvertRTInternalAddress(IntPtr address) => importer.ImportAsTypeSig(MethodTableToTypeConverter.Convert(address)); } }