public void GenerateFor(MethodDefinition method) { string nameEscaped = PathVerifyRegex.Replace(method.Name, ""); CurrentPath.Push(nameEscaped + ".il"); int pathCollision = 0; while (File.Exists(FullPath)) { pathCollision++; CurrentPath.Pop(); CurrentPath.Push(nameEscaped + "." + pathCollision + ".il"); } method.NoInlining = true; method.NoOptimization = true; using (StreamWriter writer = new StreamWriter(FullPath)) { Line = 1; writer.WriteLine("// MonoMod DebugILGenerator"); Line++; writer.Write("// Method: ("); writer.Write(method.Attributes); writer.Write(") "); writer.WriteLine(method.GetFindableID()); Line++; writer.WriteLine(); Line++; // TODO: [DbgILGen] Other method metadata? writer.WriteLine("// Body:"); Line++; if (!method.HasBody) { writer.WriteLine("// No body found."); Line++; } else { // TODO: [DbgILGen] Method body metadata? writer.Write(".maxstack "); writer.WriteLine(method.Body.MaxStackSize); Line++; // Always assure a debug scope exists! method.DebugInformation.GetOrAddScope().Variables.Clear(); if (method.Body.HasVariables) { if (method.Body.InitLocals) { writer.WriteLine(".locals init ("); } else { writer.WriteLine(".locals ("); } Line++; for (int i = 0; i < method.Body.Variables.Count; i++) { VariableDefinition @var = method.Body.Variables[i]; writer.Write(" ["); writer.Write(i); writer.Write("] "); if ([email protected] && [email protected]) { writer.Write("class "); } writer.Write(@var.VariableType.FullName); string name = @var.GenerateVariableName(method, i); method.DebugInformation.GetOrAddScope().Variables.Add(new VariableDebugInformation(@var, name)); writer.Write(" "); writer.Write(name); if (i < method.Body.Variables.Count - 1) { writer.WriteLine(","); } else { writer.WriteLine(); } Line++; } writer.WriteLine(")"); Line++; } writer.WriteLine("// Code:"); Line++; method.DebugInformation.SequencePoints.Clear(); Document symbolDoc = new Document(FullPath) { LanguageVendor = DocumentLanguageVendor.Microsoft, Language = DocumentLanguage.CSharp, // Even Visual Studio can't deal with Cil! HashAlgorithm = DocumentHashAlgorithm.None, Type = DocumentType.Text }; ILProcessor il = method.Body.GetILProcessor(); for (int instri = 0; instri < method.Body.Instructions.Count; instri++) { Instruction instr = method.Body.Instructions[instri]; string instrStr = Relative ? instr.ToRelativeString() : instr.ToString(); method.DebugInformation.SequencePoints.Add( new SequencePoint(instr, symbolDoc) { StartLine = Line, StartColumn = 1, EndLine = Line, EndColumn = instrStr.Length + 1 } ); writer.WriteLine(instrStr); Line++; } } writer.WriteLine(); } CurrentPath.Pop(); }
public void DumpMethod(string parent, int index, MethodDefinition method) { method.NoInlining = true; method.NoOptimization = true; using (DebugILWriter writer = new DebugILWriter(parent, method.Name, index)) { writer.WriteLine("// MonoMod DebugILGenerator"); writer.WriteLine($"// Method: ({method.Attributes}) {method.GetID()}"); writer.WriteLine(); // TODO: [DbgILGen] Other method metadata? writer.WriteLine("// Body:"); if (!method.HasBody) { writer.WriteLine("// No body found."); writer.WriteLine(); return; } // TODO: [DbgILGen] Method body metadata? if (!SkipMaxStack) { writer.WriteLine($".maxstack {method.Body.MaxStackSize}"); } // Always assure a debug scope exists! method.DebugInformation.GetOrAddScope().Variables.Clear(); if (method.Body.HasVariables) { writer.WriteLine(method.Body.InitLocals ? ".locals init (" : ".locals ("); for (int i = 0; i < method.Body.Variables.Count; i++) { VariableDefinition vd = method.Body.Variables[i]; string name = vd.GenerateVariableName(); method.DebugInformation.GetOrAddScope().Variables.Add(new VariableDebugInformation(vd, name)); writer.WriteLine($" [{i}] {(!vd.VariableType.IsPrimitive && !vd.VariableType.IsValueType ? "class " : "")}{vd.VariableType.FullName} {name}{(i < method.Body.Variables.Count - 1 ? "," : "")}"); } writer.WriteLine(")"); } writer.WriteLine("// Code:"); method.DebugInformation.SequencePoints.Clear(); Document symbolDoc = new Document(writer.FullPath) { LanguageVendor = DocumentLanguageVendor.Microsoft, Language = DocumentLanguage.CSharp, // Even Visual Studio can't deal with Cil! HashAlgorithm = DocumentHashAlgorithm.None, Type = DocumentType.Text }; // The exception block pretty printing is based off of // https://github.com/BepInEx/HarmonyX/blob/a570001c568629d745c88fbc46e70cc7d0c9becf/Harmony/Internal/Util/MethodBodyLogExtensions.cs#L44 // Thanks to ghorsington (denikson) for allowing MonoMod to use it! // Cache exception blocks for pretty printing Dictionary <Instruction, List <ExceptionBlock> > handlerMap = new Dictionary <Instruction, List <ExceptionBlock> >(); ExceptionBlock AddBlock(Instruction instr, ExceptionBlockType t) { if (instr == null) { return(new ExceptionBlock()); } if (!handlerMap.TryGetValue(instr, out List <ExceptionBlock> list)) { handlerMap[instr] = list = new List <ExceptionBlock>(); } ExceptionBlock block = new ExceptionBlock() { BlockType = t }; list.Add(block); return(block); } foreach (ExceptionHandler handler in method.Body.ExceptionHandlers) { AddBlock(handler.TryStart, ExceptionBlockType.BeginExceptionBlock); AddBlock(handler.TryEnd, ExceptionBlockType.EndExceptionBlock); AddBlock(handler.HandlerEnd, ExceptionBlockType.EndExceptionBlock); switch (handler.HandlerType) { case ExceptionHandlerType.Catch: AddBlock(handler.HandlerStart, ExceptionBlockType.BeginCatchBlock).CatchType = handler.CatchType ?? Modder.Module.TypeSystem.Object; break; case ExceptionHandlerType.Filter: AddBlock(handler.FilterStart, ExceptionBlockType.BeginExceptFilterBlock); break; case ExceptionHandlerType.Finally: AddBlock(handler.HandlerStart, ExceptionBlockType.BeginFinallyBlock); break; case ExceptionHandlerType.Fault: AddBlock(handler.HandlerStart, ExceptionBlockType.BeginFaultBlock); break; default: throw new NotSupportedException($"Unsupported exception handler type ${handler.HandlerType}"); } } var handlerStack = new Stack <string>(); for (int instri = 0; instri < method.Body.Instructions.Count; instri++) { Instruction instr = method.Body.Instructions[instri]; if (handlerMap.TryGetValue(instr, out List <ExceptionBlock> blocks)) { // Force exception close to the start for correct output blocks.Sort((a, b) => a.BlockType == ExceptionBlockType.EndExceptionBlock ? -1 : 0); foreach (ExceptionBlock block in blocks) { switch (block.BlockType) { case ExceptionBlockType.BeginExceptionBlock: writer.WriteLine(".try {"); handlerStack.Push(".try"); break; case ExceptionBlockType.BeginCatchBlock: writer.WriteLine($"catch {block.CatchType.FullName} {{"); handlerStack.Push("handler (catch)"); break; case ExceptionBlockType.BeginExceptFilterBlock: writer.WriteLine("filter {"); handlerStack.Push("handler (filter)"); break; case ExceptionBlockType.BeginFaultBlock: writer.WriteLine("fault {"); handlerStack.Push("handler (fault)"); break; case ExceptionBlockType.BeginFinallyBlock: writer.WriteLine("finally {"); handlerStack.Push("handler (finally)"); break; case ExceptionBlockType.EndExceptionBlock: writer.WriteLine($"}} // end {handlerStack.Pop()}"); break; default: throw new NotSupportedException($"Unsupported exception handler type ${block.BlockType}"); } } } string instrStr = Relative ? instr.ToRelativeString() : instr.ToString(); method.DebugInformation.SequencePoints.Add( new SequencePoint(instr, symbolDoc) { StartLine = writer.Line, StartColumn = 1, EndLine = writer.Line, EndColumn = instrStr.Length + 1 } ); writer.WriteLine(instrStr); } writer.WriteLine(); } }