From 8b98deafcac0e2099f582efbddf8612bb9fbec85 Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Thu, 2 Nov 2017 22:43:59 -0700 Subject: [PATCH] Fixed patch manager to emit try-catch-finally blocks. Solves issue with PBs not running --- .../Managers/PatchManager/DecoratedMethod.cs | 4 +- .../PatchManager/MSIL/MsilInstruction.cs | 20 ++---- .../MSIL/MsilTryCatchOperation.cs | 54 +++++++++++++++ Torch/Managers/PatchManager/PatchUtilities.cs | 2 +- .../Transpile/LoggingILGenerator.cs | 47 ++++++++++++- .../PatchManager/Transpile/MethodContext.cs | 64 ++++++++++------- .../Transpile/MethodTranspiler.cs | 68 +++++++++++++++---- Torch/Torch.csproj | 1 + 8 files changed, 203 insertions(+), 57 deletions(-) create mode 100644 Torch/Managers/PatchManager/MSIL/MsilTryCatchOperation.cs diff --git a/Torch/Managers/PatchManager/DecoratedMethod.cs b/Torch/Managers/PatchManager/DecoratedMethod.cs index 91fcbed..6171a13 100644 --- a/Torch/Managers/PatchManager/DecoratedMethod.cs +++ b/Torch/Managers/PatchManager/DecoratedMethod.cs @@ -53,9 +53,9 @@ namespace Torch.Managers.PatchManager _log.Debug( $"Done patching {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})"); } - catch + catch (Exception exception) { - _log.Fatal($"Error patching {_method.DeclaringType?.FullName}#{_method}"); + _log.Fatal(exception, $"Error patching {_method.DeclaringType?.FullName}#{_method}"); throw; } } diff --git a/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs b/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs index f39072d..85fa4a6 100644 --- a/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs +++ b/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs @@ -104,6 +104,11 @@ namespace Torch.Managers.PatchManager.MSIL /// public HashSet Labels { get; } = new HashSet(); + /// + /// The try catch operation that is performed here. + /// + public MsilTryCatchOperation TryCatchOperation { get; set; } = null; + private static readonly ConcurrentDictionary _setterInfoForInlines = new ConcurrentDictionary(); @@ -147,6 +152,7 @@ namespace Torch.Managers.PatchManager.MSIL Operand?.CopyTo(result.Operand); foreach (MsilLabel x in Labels) result.Labels.Add(x); + result.TryCatchOperation = TryCatchOperation; return result; } @@ -172,20 +178,6 @@ namespace Torch.Managers.PatchManager.MSIL return this; } - /// - /// Emits this instruction to the given generator - /// - /// Emit target - public void Emit(LoggingIlGenerator target) - { - foreach (MsilLabel label in Labels) - target.MarkLabel(label.LabelFor(target)); - if (Operand != null) - Operand.Emit(target); - else - target.Emit(OpCode); - } - /// public override string ToString() { diff --git a/Torch/Managers/PatchManager/MSIL/MsilTryCatchOperation.cs b/Torch/Managers/PatchManager/MSIL/MsilTryCatchOperation.cs new file mode 100644 index 0000000..d9aecae --- /dev/null +++ b/Torch/Managers/PatchManager/MSIL/MsilTryCatchOperation.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Torch.Managers.PatchManager.MSIL +{ + /// + /// Represents a try/catch block operation type + /// + public enum MsilTryCatchOperationType + { + // TryCatchBlockIL: + // var exBlock = ILGenerator.BeginExceptionBlock(); + // try{ + // ILGenerator.BeginCatchBlock(typeof(Exception)); + // } catch(Exception e) { + // ILGenerator.BeginCatchBlock(null); + // } catch { + // ILGenerator.BeginFinallyBlock(); + // }finally { + // ILGenerator.EndExceptionBlock(); + // } + BeginExceptionBlock, + BeginCatchBlock, + BeginFinallyBlock, + EndExceptionBlock + } + + /// + /// Represents a try catch operation. + /// + public class MsilTryCatchOperation + { + /// + /// Operation type + /// + public readonly MsilTryCatchOperationType Type; + /// + /// Type caught by this operation, or null if none. + /// + public readonly Type CatchType; + + public MsilTryCatchOperation(MsilTryCatchOperationType op, Type caughtType = null) + { + Type = op; + if (caughtType != null && op != MsilTryCatchOperationType.BeginCatchBlock) + throw new ArgumentException($"Can't use caught type with operation type {op}", nameof(caughtType)); + CatchType = caughtType; + } + } +} diff --git a/Torch/Managers/PatchManager/PatchUtilities.cs b/Torch/Managers/PatchManager/PatchUtilities.cs index e35021d..0320742 100644 --- a/Torch/Managers/PatchManager/PatchUtilities.cs +++ b/Torch/Managers/PatchManager/PatchUtilities.cs @@ -34,7 +34,7 @@ namespace Torch.Managers.PatchManager /// Output public static void EmitInstructions(IEnumerable insn, LoggingIlGenerator generator) { - MethodTranspiler.Emit(insn, generator); + MethodTranspiler.EmitMethod(insn.ToList(), generator); } } } diff --git a/Torch/Managers/PatchManager/Transpile/LoggingILGenerator.cs b/Torch/Managers/PatchManager/Transpile/LoggingILGenerator.cs index 1835ac2..3f25a4e 100644 --- a/Torch/Managers/PatchManager/Transpile/LoggingILGenerator.cs +++ b/Torch/Managers/PatchManager/Transpile/LoggingILGenerator.cs @@ -114,7 +114,7 @@ namespace Torch.Managers.PatchManager.Transpile #pragma warning disable 649 - [ReflectedGetter(Name="m_label")] + [ReflectedGetter(Name = "m_label")] private static Func _labelID; #pragma warning restore 649 @@ -146,6 +146,51 @@ namespace Torch.Managers.PatchManager.Transpile Backing.Emit(op, arg); } + + #region Exceptions + /// + public Label BeginExceptionBlock() + { + _log?.Trace($"BeginExceptionBlock"); + return Backing.BeginExceptionBlock(); + } + + /// + public void BeginCatchBlock(Type caught) + { + _log?.Trace($"BeginCatchBlock {caught}"); + Backing.BeginCatchBlock(caught); + } + + /// + public void BeginExceptFilterBlock() + { + _log?.Trace($"BeginExceptFilterBlock"); + Backing.BeginExceptFilterBlock(); + } + + /// + public void BeginFaultBlock() + { + _log?.Trace($"BeginFaultBlock"); + Backing.BeginFaultBlock(); + } + + /// + public void BeginFinallyBlock() + { + _log?.Trace($"BeginFinallyBlock"); + Backing.BeginFinallyBlock(); + } + + /// + public void EndExceptionBlock() + { + _log?.Trace($"EndExceptionBlock"); + Backing.EndExceptionBlock(); + } + #endregion + /// public void MarkLabel(Label label) { diff --git a/Torch/Managers/PatchManager/Transpile/MethodContext.cs b/Torch/Managers/PatchManager/Transpile/MethodContext.cs index 0b81506..edde57f 100644 --- a/Torch/Managers/PatchManager/Transpile/MethodContext.cs +++ b/Torch/Managers/PatchManager/Transpile/MethodContext.cs @@ -16,6 +16,7 @@ namespace Torch.Managers.PatchManager.Transpile private static readonly Logger _log = LogManager.GetCurrentClassLogger(); public readonly MethodBase Method; + public readonly MethodBody MethodBody; private readonly byte[] _msilBytes; internal Dictionary Labels { get; } = new Dictionary(); @@ -35,7 +36,9 @@ namespace Torch.Managers.PatchManager.Transpile public MethodContext(MethodBase method) { Method = method; - _msilBytes = Method.GetMethodBody().GetILAsByteArray(); + MethodBody = method.GetMethodBody(); + Debug.Assert(MethodBody != null, "Method body is null"); + _msilBytes = MethodBody.GetILAsByteArray(); TokenResolver = new NormalTokenResolver(method); } @@ -43,6 +46,7 @@ namespace Torch.Managers.PatchManager.Transpile { ReadInstructions(); ResolveLabels(); + ResolveCatchClauses(); } private void ReadInstructions() @@ -53,7 +57,7 @@ namespace Torch.Managers.PatchManager.Transpile using (var reader = new BinaryReader(memory)) while (memory.Length > memory.Position) { - var opcodeOffset = (int) memory.Position; + var opcodeOffset = (int)memory.Position; var instructionValue = (short)memory.ReadByte(); if (Prefixes.Contains(instructionValue)) { @@ -72,35 +76,47 @@ namespace Torch.Managers.PatchManager.Transpile } } + private void ResolveCatchClauses() + { + foreach (ExceptionHandlingClause clause in MethodBody.ExceptionHandlingClauses) + { + var beginInstruction = FindInstruction(clause.TryOffset); + var catchInstruction = FindInstruction(clause.HandlerOffset); + var finalInstruction = FindInstruction(clause.HandlerOffset + clause.HandlerLength); + beginInstruction.TryCatchOperation = new MsilTryCatchOperation(MsilTryCatchOperationType.BeginExceptionBlock); + if ((clause.Flags & ExceptionHandlingClauseOptions.Clause) != 0) + catchInstruction.TryCatchOperation = new MsilTryCatchOperation(MsilTryCatchOperationType.BeginCatchBlock, clause.CatchType); + else if ((clause.Flags & ExceptionHandlingClauseOptions.Finally) != 0) + catchInstruction.TryCatchOperation = new MsilTryCatchOperation(MsilTryCatchOperationType.BeginFinallyBlock); + finalInstruction.TryCatchOperation = new MsilTryCatchOperation(MsilTryCatchOperationType.EndExceptionBlock); + _log.Info($"Init try catch ({clause.Flags} {clause.Flags}):\n{beginInstruction}\n{catchInstruction}\n{finalInstruction}"); + } + } + + private MsilInstruction FindInstruction(int offset) + { + int min = 0, max = _instructions.Count; + while (min != max) + { + int mid = (min + max) / 2; + if (_instructions[mid].Offset < offset) + min = mid + 1; + else + max = mid; + } + return min >= 0 && min < _instructions.Count ? _instructions[min] : null; + } + private void ResolveLabels() { foreach (var label in Labels) { - int min = 0, max = _instructions.Count; - while (min != max) - { - int mid = (min + max) / 2; - if (_instructions[mid].Offset < label.Key) - min = mid + 1; - else - max = mid; - } -#if DEBUG - if (min >= _instructions.Count || min < 0) - { - _log.Trace( - $"Want offset {label.Key} for {label.Value}, instruction offsets at\n {string.Join("\n", _instructions.Select(x => $"IL_{x.Offset:X4} {x}"))}"); - } - MsilInstruction prevInsn = min > 0 ? _instructions[min - 1] : null; - if ((prevInsn == null || prevInsn.Offset >= label.Key) || - _instructions[min].Offset < label.Key) - _log.Error($"Label {label.Value} wanted {label.Key} but instruction is at {_instructions[min].Offset}. Previous instruction is at {prevInsn?.Offset ?? -1}"); -#endif - _instructions[min]?.Labels?.Add(label.Value); + MsilInstruction target = FindInstruction(label.Key); + target.Labels?.Add(label.Value); } } - + public string ToHumanMsil() { return string.Join("\n", _instructions.Select(x => $"IL_{x.Offset:X4}: {x.StackChange():+0;-#} {x}")); diff --git a/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs b/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs index 5973a92..38bd697 100644 --- a/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs +++ b/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Text; +using System.Windows.Documents; using NLog; using Torch.Managers.PatchManager.MSIL; @@ -42,17 +43,60 @@ namespace Torch.Managers.PatchManager.Transpile methodContent = (IEnumerable)transpiler.Invoke(null, paramList.ToArray()); } methodContent = FixBranchAndReturn(methodContent, retLabel); + var list = methodContent.ToList(); if (logMsil) { - var list = methodContent.ToList(); IntegrityAnalysis(LogLevel.Info, list); - foreach (var k in list) - k.Emit(output); } - else + EmitMethod(list, output); + } + + internal static void EmitMethod(IReadOnlyList instructions, LoggingIlGenerator target) + { + for (var i = 0; i < instructions.Count; i++) { - foreach (var k in methodContent) - k.Emit(output); + MsilInstruction il = instructions[i]; + if (il.TryCatchOperation != null) + switch (il.TryCatchOperation.Type) + { + case MsilTryCatchOperationType.BeginExceptionBlock: + target.BeginExceptionBlock(); + break; + case MsilTryCatchOperationType.BeginCatchBlock: + target.BeginCatchBlock(il.TryCatchOperation.CatchType); + break; + case MsilTryCatchOperationType.BeginFinallyBlock: + target.BeginFinallyBlock(); + break; + case MsilTryCatchOperationType.EndExceptionBlock: + target.EndExceptionBlock(); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + foreach (MsilLabel label in il.Labels) + target.MarkLabel(label.LabelFor(target)); + + MsilInstruction ilNext = i < instructions.Count - 1 ? instructions[i + 1] : null; + + // Leave opcodes emitted by these: + if (il.OpCode == OpCodes.Endfilter && ilNext?.TryCatchOperation?.Type == + MsilTryCatchOperationType.BeginCatchBlock) + continue; + if ((il.OpCode == OpCodes.Leave || il.OpCode == OpCodes.Leave_S) && + (ilNext?.TryCatchOperation?.Type == MsilTryCatchOperationType.EndExceptionBlock || + ilNext?.TryCatchOperation?.Type == MsilTryCatchOperationType.BeginCatchBlock || + ilNext?.TryCatchOperation?.Type == MsilTryCatchOperationType.BeginFinallyBlock)) + continue; + if ((il.OpCode == OpCodes.Leave || il.OpCode == OpCodes.Leave_S) && + ilNext?.TryCatchOperation?.Type == MsilTryCatchOperationType.EndExceptionBlock) + continue; + + if (il.Operand != null) + il.Operand.Emit(target); + else + target.Emit(il.OpCode); } } @@ -109,6 +153,8 @@ namespace Torch.Managers.PatchManager.Transpile } } stack += k.StackChange(); + if (k.TryCatchOperation != null) + line.AppendLine($"// .{k.TryCatchOperation.Type} ({k.TryCatchOperation.CatchType})"); line.AppendLine($"{i:X4} S:{stack:D2} dS:{k.StackChange():+0;-#}\t{k}" + (unreachable ? "\t// UNREACHABLE" : "")); if (k.Operand is MsilOperandBrTarget br) { @@ -146,21 +192,13 @@ namespace Torch.Managers.PatchManager.Transpile } } - internal static void Emit(IEnumerable input, LoggingIlGenerator output) - { - foreach (MsilInstruction k in FixBranchAndReturn(input, null)) - k.Emit(output); - } - private static IEnumerable FixBranchAndReturn(IEnumerable insn, Label? retTarget) { foreach (MsilInstruction i in insn) { if (retTarget.HasValue && i.OpCode == OpCodes.Ret) { - MsilInstruction j = new MsilInstruction(OpCodes.Br).InlineTarget(new MsilLabel(retTarget.Value)); - foreach (MsilLabel l in i.Labels) - j.Labels.Add(l); + var j = i.CopyWith(OpCodes.Br).InlineTarget(new MsilLabel(retTarget.Value)); _log.Trace($"Replacing {i} with {j}"); yield return j; } diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj index 5e01277..a77ba4a 100644 --- a/Torch/Torch.csproj +++ b/Torch/Torch.csproj @@ -184,6 +184,7 @@ +