From 98aae10126795f38a12d46a03d2852a18e2b0173 Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Tue, 31 Oct 2017 23:42:25 -0700 Subject: [PATCH] Tools for debugging patch transpilers. --- .../Managers/PatchManager/DecoratedMethod.cs | 36 +++--- .../PatchManager/MethodRewritePattern.cs | 19 +++ .../Transpile/MethodTranspiler.cs | 109 +++++++++++++++++- 3 files changed, 148 insertions(+), 16 deletions(-) diff --git a/Torch/Managers/PatchManager/DecoratedMethod.cs b/Torch/Managers/PatchManager/DecoratedMethod.cs index ec0814c..c3d3e32 100644 --- a/Torch/Managers/PatchManager/DecoratedMethod.cs +++ b/Torch/Managers/PatchManager/DecoratedMethod.cs @@ -29,20 +29,30 @@ namespace Torch.Managers.PatchManager internal void Commit() { - if (!Prefixes.HasChanges() && !Suffixes.HasChanges() && !Transpilers.HasChanges()) - return; - Revert(); + try + { + if (!Prefixes.HasChanges() && !Suffixes.HasChanges() && !Transpilers.HasChanges()) + return; + Revert(); - if (Prefixes.Count == 0 && Suffixes.Count == 0 && Transpilers.Count == 0) - return; - _log.Debug($"Begin patching {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})"); - var patch = ComposePatchedMethod(); + if (Prefixes.Count == 0 && Suffixes.Count == 0 && Transpilers.Count == 0) + return; + _log.Debug( + $"Begin patching {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})"); + var patch = ComposePatchedMethod(); - _revertAddress = AssemblyMemory.GetMethodBodyStart(_method); - var newAddress = AssemblyMemory.GetMethodBodyStart(patch); - _revertData = AssemblyMemory.WriteJump(_revertAddress, newAddress); - _pinnedPatch = GCHandle.Alloc(patch); - _log.Debug($"Done patching {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})"); + _revertAddress = AssemblyMemory.GetMethodBodyStart(_method); + var newAddress = AssemblyMemory.GetMethodBodyStart(patch); + _revertData = AssemblyMemory.WriteJump(_revertAddress, newAddress); + _pinnedPatch = GCHandle.Alloc(patch); + _log.Debug( + $"Done patching {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})"); + } + catch + { + _log.Fatal($"Error patching {_method.DeclaringType?.FullName}#{_method}"); + throw; + } } internal void Revert() @@ -156,7 +166,7 @@ namespace Torch.Managers.PatchManager target.EmitComment("Prefixes End"); target.EmitComment("Original Begin"); - MethodTranspiler.Transpile(_method, (type) => new MsilLocal(target.DeclareLocal(type)), Transpilers, target, labelAfterOriginalContent); + MethodTranspiler.Transpile(_method, (type) => new MsilLocal(target.DeclareLocal(type)), Transpilers, target, labelAfterOriginalContent, PrintMsil); target.EmitComment("Original End"); target.MarkLabel(labelAfterOriginalContent); diff --git a/Torch/Managers/PatchManager/MethodRewritePattern.cs b/Torch/Managers/PatchManager/MethodRewritePattern.cs index 3696371..069bdf9 100644 --- a/Torch/Managers/PatchManager/MethodRewritePattern.cs +++ b/Torch/Managers/PatchManager/MethodRewritePattern.cs @@ -158,6 +158,24 @@ namespace Torch.Managers.PatchManager /// public MethodRewriteSet Suffixes { get; } + /// + /// Should the resulting MSIL of this patch operation be printed. + /// + public bool PrintMsil + { + get => _parent?.PrintMsil ?? _printMsilBacking; + set + { + if (_parent != null) + _parent.PrintMsil = value; + else + _printMsilBacking = value; + } + } + + private bool _printMsilBacking; + private readonly MethodRewritePattern _parent; + /// /// /// @@ -167,6 +185,7 @@ namespace Torch.Managers.PatchManager Prefixes = new MethodRewriteSet(parentPattern?.Prefixes); Transpilers = new MethodRewriteSet(parentPattern?.Transpilers); Suffixes = new MethodRewriteSet(parentPattern?.Suffixes); + _parent = parentPattern; } } } diff --git a/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs b/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs index ac0d209..ea820d5 100644 --- a/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs +++ b/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Reflection.Emit; +using System.Text; using NLog; using Torch.Managers.PatchManager.MSIL; @@ -11,7 +13,9 @@ namespace Torch.Managers.PatchManager.Transpile { public static readonly Logger _log = LogManager.GetCurrentClassLogger(); - internal static void Transpile(MethodBase baseMethod, Func localCreator, IEnumerable transpilers, LoggingIlGenerator output, Label? retLabel) + internal static void Transpile(MethodBase baseMethod, Func localCreator, + IEnumerable transpilers, LoggingIlGenerator output, Label? retLabel, + bool logMsil) { var context = new MethodContext(baseMethod); context.Read(); @@ -40,8 +44,107 @@ namespace Torch.Managers.PatchManager.Transpile methodContent = (IEnumerable)transpiler.Invoke(null, paramList.ToArray()); } methodContent = FixBranchAndReturn(methodContent, retLabel); - foreach (var k in methodContent) - k.Emit(output); + if (logMsil) + { + var list = methodContent.ToList(); + IntegrityAnalysis(list); + foreach (var k in list) + k.Emit(output); + } + else + { + foreach (var k in methodContent) + k.Emit(output); + } + } + + /// + /// Analyzes the integrity of a set of instructions. + /// + /// instructions + private static void IntegrityAnalysis(List instructions) + { + var targets = new Dictionary(); + for (var i = 0; i < instructions.Count; i++) + foreach (var label in instructions[i].Labels) + { + if (targets.TryGetValue(label, out var other)) + _log.Warn($"Label {label} is applied to ({i}: {instructions[i]}) and ({other}: {instructions[other]})"); + targets[label] = i; + } + + var reparsed = new HashSet(); + var labelStackSize = new Dictionary>(); + var stack = 0; + var unreachable = false; + var data = new StringBuilder[instructions.Count]; + for (var i = 0; i < instructions.Count; i++) + { + var k = instructions[i]; + var line = (data[i] ?? (data[i] = new StringBuilder())).Clear(); + if (!unreachable) + { + foreach (var label in k.Labels) + { + if (!labelStackSize.TryGetValue(label, out Dictionary otherStack)) + labelStackSize[label] = otherStack = new Dictionary(); + + otherStack.Add(i - 1, stack); + if (otherStack.Values.Distinct().Count() > 1 || (otherStack.Count == 1 && !otherStack.ContainsValue(stack))) + { + string otherDesc = string.Join(", ", otherStack.Select(x => $"{x.Key:X4}=>{x.Value}")); + line.AppendLine($"WARN// | Label {label} has multiple entry stack sizes ({otherDesc})"); + } + } + } + foreach (var label in k.Labels) + { + if (!labelStackSize.TryGetValue(label, out var entry)) + continue; + string desc = string.Join(", ", entry.Select(x => $"{x.Key:X4}=>{x.Value}")); + line.AppendLine($"// \\/ Label {label} has stack sizes {desc}"); + if (unreachable && entry.Any()) + { + stack = entry.Values.First(); + unreachable = false; + } + } + stack += k.StackChange(); + line.AppendLine($"{i:X4} S:{stack:D2} dS:{k.StackChange():+0;-#}\t{k}" + (unreachable ? "\t// UNREACHABLE" : "")); + if (k.Operand is MsilOperandBrTarget br) + { + if (!targets.ContainsKey(br.Target)) + line.AppendLine($"WARN// ^ Unknown target {br.Target}"); + + if (!labelStackSize.TryGetValue(br.Target, out Dictionary otherStack)) + labelStackSize[br.Target] = otherStack = new Dictionary(); + + otherStack[i] = stack; + if (otherStack.Values.Distinct().Count() > 1 || (otherStack.Count == 1 && !otherStack.ContainsValue(stack))) + { + string otherDesc = string.Join(", ", otherStack.Select(x => $"{x.Key:X4}=>{x.Value}")); + line.AppendLine($"WARN// ^ Label {br.Target} has multiple entry stack sizes ({otherDesc})"); + } + if (targets.TryGetValue(br.Target, out var target) && target < i && reparsed.Add(br.Target)) + { + i = target - 1; + unreachable = false; + continue; + } + } + if (k.OpCode == OpCodes.Br || k.OpCode == OpCodes.Br_S) + unreachable = true; + } + foreach (var k in data) + foreach (var line in k.ToString().Split('\n')) + { + if (string.IsNullOrWhiteSpace(line)) + continue; + if (line.StartsWith("WARN", StringComparison.OrdinalIgnoreCase)) + _log.Warn(line.Substring(4).Trim()); + else + _log.Info(line.Trim()); + } } internal static void Emit(IEnumerable input, LoggingIlGenerator output)