From f7d45ca33883dda6a3deb2c092476b5e4469ec67 Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Sun, 7 Oct 2018 00:44:36 -0700 Subject: [PATCH] Add more advanced debugging to patcher Support multiple try-catch operations on a single instruction Replace long branch with short branch instructions where possible Dump generated IL at three stages to a file. --- Torch.Tests/PatchTest.cs | 161 +++++++++++-- .../Managers/PatchManager/DecoratedMethod.cs | 120 ++++++++-- .../PatchManager/MSIL/MsilInstruction.cs | 34 ++- .../Managers/PatchManager/MSIL/MsilOperand.cs | 5 + .../PatchManager/MSIL/MsilOperandBrTarget.cs | 2 + .../PatchManager/MSIL/MsilOperandInline.cs | 18 ++ .../PatchManager/MSIL/MsilOperandSwitch.cs | 1 + .../MSIL/MsilTryCatchOperation.cs | 2 + .../PatchManager/MethodRewritePattern.cs | 59 ++++- Torch/Managers/PatchManager/PatchUtilities.cs | 14 +- .../PatchManager/Transpile/MethodContext.cs | 109 +++++++-- .../Transpile/MethodTranspiler.cs | 222 +++++++++++++----- 12 files changed, 616 insertions(+), 131 deletions(-) diff --git a/Torch.Tests/PatchTest.cs b/Torch.Tests/PatchTest.cs index 1ee4225..fdb5671 100644 --- a/Torch.Tests/PatchTest.cs +++ b/Torch.Tests/PatchTest.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Text; +using System.Threading.Tasks; using Torch.Managers.PatchManager; using Torch.Managers.PatchManager.MSIL; using Torch.Utils; @@ -17,6 +19,7 @@ namespace Torch.Tests public class PatchTest { #region TestRunner + private static readonly PatchManager _patchContext = new PatchManager(null); [Theory] @@ -48,6 +51,133 @@ namespace Torch.Tests } + [Fact] + public void TestTryCatchNop() + { + var ctx = _patchContext.AcquireContext(); + ctx.GetPattern(TryCatchTest._target).Transpilers.Add(_nopTranspiler); + _patchContext.Commit(); + Assert.False(TryCatchTest.Target()); + Assert.True(TryCatchTest.FinallyHit); + _patchContext.FreeContext(ctx); + _patchContext.Commit(); + } + + [Fact] + public void TestTryCatchCancel() + { + var ctx = _patchContext.AcquireContext(); + ctx.GetPattern(TryCatchTest._target).Transpilers.Add(TryCatchTest._removeThrowTranspiler); + ctx.GetPattern(TryCatchTest._target).DumpTarget = @"C:\tmp\dump.txt"; + ctx.GetPattern(TryCatchTest._target).DumpMode = MethodRewritePattern.PrintModeEnum.Original | MethodRewritePattern.PrintModeEnum.Patched; + _patchContext.Commit(); + Assert.True(TryCatchTest.Target()); + Assert.True(TryCatchTest.FinallyHit); + _patchContext.FreeContext(ctx); + _patchContext.Commit(); + } + + private static readonly MethodInfo _nopTranspiler = typeof(PatchTest).GetMethod(nameof(NopTranspiler), BindingFlags.Static | BindingFlags.NonPublic); + + private static IEnumerable NopTranspiler(IEnumerable input) + { + return input; + } + + private class TryCatchTest + { + public static readonly MethodInfo _removeThrowTranspiler = + typeof(TryCatchTest).GetMethod(nameof(RemoveThrowTranspiler), BindingFlags.Static | BindingFlags.NonPublic); + + private static IEnumerable RemoveThrowTranspiler(IEnumerable input) + { + foreach (var i in input) + if (i.OpCode == OpCodes.Throw) + yield return i.CopyWith(OpCodes.Pop); + else + yield return i; + } + + public static readonly MethodInfo _target = typeof(TryCatchTest).GetMethod(nameof(Target), BindingFlags.Public | BindingFlags.Static); + + public static bool FinallyHit = false; + + public static bool Target() + { + FinallyHit = false; + try + { + try + { + // shim to prevent compiler optimization + if ("test".Length > "".Length) + throw new Exception(); + return true; + } + catch (IOException ioe) + { + return false; + } + catch (Exception e) + { + return false; + } + finally + { + FinallyHit = true; + } + } + catch (Exception e) + { + throw; + } + } + } + + [Fact] + public void TestAsyncNop() + { + var candidates = new List(); + var nestedTypes = typeof(PatchTest).GetNestedTypes(BindingFlags.NonPublic | BindingFlags.Static); + foreach (var nested in nestedTypes) + if (nested.Name.StartsWith("<" + nameof(TestAsyncMethod) + ">")) + { + var good = false; + foreach (var itf in nested.GetInterfaces()) + if (itf.FullName == typeof(IAsyncStateMachine).FullName) + { + good = true; + break; + } + + if (good) + candidates.Add(nested); + } + + if (candidates.Count != 1) + throw new Exception("Couldn't find async worker"); + + var method = candidates[0].GetMethod("MoveNext", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + if (method == null) + throw new Exception("Failed to find state machine move next instruction, cannot proceed"); + + var ctx = _patchContext.AcquireContext(); + ctx.GetPattern(method).Transpilers.Add(_nopTranspiler); + ctx.GetPattern(method).DumpTarget = @"C:\tmp\dump.txt"; + ctx.GetPattern(method).DumpMode = MethodRewritePattern.PrintModeEnum.Original | MethodRewritePattern.PrintModeEnum.Patched; + _patchContext.Commit(); + + Assert.Equal("TEST", TestAsyncMethod().Result); + _patchContext.FreeContext(ctx); + _patchContext.Commit(); + } + + private async Task TestAsyncMethod() + { + var first = await Task.Run(() => "TE"); + var last = await Task.Run(() => "ST"); + return await Task.Run(() => first + last); + } public class TestBootstrap { @@ -82,7 +212,7 @@ namespace Torch.Tests if (_targetAssert == null) throw new Exception($"{t.FullName} must have a method named AssertNormal"); _instance = !_targetMethod.IsStatic ? Activator.CreateInstance(t) : null; - _targetParams = (object[])t.GetField("_targetParams", flags)?.GetValue(null) ?? new object[0]; + _targetParams = (object[]) t.GetField("_targetParams", flags)?.GetValue(null) ?? new object[0]; } private void Invoke(MethodBase i, params object[] args) @@ -185,10 +315,11 @@ namespace Torch.Tests _patchTest.Add(new TestBootstrap(type)); } - public static IEnumerable Prefixes => _patchTest.Where(x => x.HasPrefix).Select(x => new object[] { x }); - public static IEnumerable Transpilers => _patchTest.Where(x => x.HasTranspile).Select(x => new object[] { x }); - public static IEnumerable Suffixes => _patchTest.Where(x => x.HasSuffix).Select(x => new object[] { x }); - public static IEnumerable Combo => _patchTest.Where(x => x.HasPrefix || x.HasTranspile || x.HasSuffix).Select(x => new object[] { x }); + public static IEnumerable Prefixes => _patchTest.Where(x => x.HasPrefix).Select(x => new object[] {x}); + public static IEnumerable Transpilers => _patchTest.Where(x => x.HasTranspile).Select(x => new object[] {x}); + public static IEnumerable Suffixes => _patchTest.Where(x => x.HasSuffix).Select(x => new object[] {x}); + public static IEnumerable Combo => _patchTest.Where(x => x.HasPrefix || x.HasTranspile || x.HasSuffix).Select(x => new object[] {x}); + #endregion #region PatchTests @@ -220,7 +351,8 @@ namespace Torch.Tests { yield return new MsilInstruction(OpCodes.Ldnull); yield return new MsilInstruction(OpCodes.Ldc_I4_1); - yield return new MsilInstruction(OpCodes.Stfld).InlineValue(typeof(StaticNoRetNoParm).GetField("_transpileHit", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)); + yield return new MsilInstruction(OpCodes.Stfld).InlineValue(typeof(StaticNoRetNoParm).GetField("_transpileHit", + BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)); foreach (MsilInstruction i in instructions) yield return i; } @@ -255,7 +387,7 @@ namespace Torch.Tests private class StaticNoRetParam { private static bool _prefixHit, _normalHit, _suffixHit; - private static readonly object[] _targetParams = { "test", 1, new StringBuilder("test1") }; + private static readonly object[] _targetParams = {"test", 1, new StringBuilder("test1")}; [MethodImpl(MethodImplOptions.NoInlining)] public static void Prefix(string str, int i, StringBuilder o) @@ -306,8 +438,8 @@ namespace Torch.Tests private class StaticNoRetParamReplace { private static bool _prefixHit, _normalHit, _suffixHit; - private static readonly object[] _targetParams = { "test", 1, new StringBuilder("stest1") }; - private static readonly object[] _replacedParams = { "test2", 2, new StringBuilder("stest2") }; + private static readonly object[] _targetParams = {"test", 1, new StringBuilder("stest1")}; + private static readonly object[] _replacedParams = {"test2", 2, new StringBuilder("stest2")}; private static object[] _calledParams; [MethodImpl(MethodImplOptions.NoInlining)] @@ -316,16 +448,16 @@ namespace Torch.Tests Assert.Equal(_targetParams[0], str); Assert.Equal(_targetParams[1], i); Assert.Equal(_targetParams[2], o); - str = (string)_replacedParams[0]; - i = (int)_replacedParams[1]; - o = (StringBuilder)_replacedParams[2]; + str = (string) _replacedParams[0]; + i = (int) _replacedParams[1]; + o = (StringBuilder) _replacedParams[2]; _prefixHit = true; } [MethodImpl(MethodImplOptions.NoInlining)] public static void Target(string str, int i, StringBuilder o) { - _calledParams = new object[] { str, i, o }; + _calledParams = new object[] {str, i, o}; _normalHit = true; } @@ -380,7 +512,8 @@ namespace Torch.Tests Assert.True(_prefixHit, "Failed to prefix"); } } + #endregion } #pragma warning restore 414 -} +} \ No newline at end of file diff --git a/Torch/Managers/PatchManager/DecoratedMethod.cs b/Torch/Managers/PatchManager/DecoratedMethod.cs index 635f026..010a315 100644 --- a/Torch/Managers/PatchManager/DecoratedMethod.cs +++ b/Torch/Managers/PatchManager/DecoratedMethod.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel.Design; using System.Diagnostics; +using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; @@ -44,7 +45,7 @@ namespace Torch.Managers.PatchManager if (Prefixes.Count == 0 && Suffixes.Count == 0 && Transpilers.Count == 0 && PostTranspilers.Count == 0) return; - _log.Log(PrintMsil ? LogLevel.Info : LogLevel.Debug, + _log.Log(PrintMode != 0 ? LogLevel.Info : LogLevel.Debug, $"Begin patching {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})"); var patch = ComposePatchedMethod(); @@ -52,7 +53,7 @@ namespace Torch.Managers.PatchManager var newAddress = AssemblyMemory.GetMethodBodyStart(patch); _revertData = AssemblyMemory.WriteJump(_revertAddress, newAddress); _pinnedPatch = GCHandle.Alloc(patch); - _log.Log(PrintMsil ? LogLevel.Info : LogLevel.Debug, + _log.Log(PrintMode != 0 ? LogLevel.Info : LogLevel.Debug, $"Done patching {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})"); } catch (Exception exception) @@ -104,36 +105,110 @@ namespace Torch.Managers.PatchManager public const string PREFIX_SKIPPED_PARAMETER = "__prefixSkipped"; public const string LOCAL_PARAMETER = "__local"; + private void SavePatchedMethod(string target) + { + var asmBuilder = + AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("SomeName"), AssemblyBuilderAccess.RunAndSave, Path.GetDirectoryName(target)); + var moduleBuilder = asmBuilder.DefineDynamicModule(Path.GetFileNameWithoutExtension(target), Path.GetFileName(target)); + var typeBuilder = moduleBuilder.DefineType("Test", TypeAttributes.Public); + + + var methodName = _method.Name + $"_{_patchSalt}"; + var returnType = _method is MethodInfo meth ? meth.ReturnType : typeof(void); + var parameters = _method.GetParameters(); + var parameterTypes = (_method.IsStatic ? Enumerable.Empty() : new[] {_method.DeclaringType}) + .Concat(parameters.Select(x => x.ParameterType)).ToArray(); + + var patchMethod = typeBuilder.DefineMethod(methodName, MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, + returnType, parameterTypes); + if (!_method.IsStatic) + patchMethod.DefineParameter(0, ParameterAttributes.None, INSTANCE_PARAMETER); + for (var i = 0; i < parameters.Length; i++) + patchMethod.DefineParameter((patchMethod.IsStatic ? 0 : 1) + i, parameters[i].Attributes, parameters[i].Name); + + var generator = new LoggingIlGenerator(patchMethod.GetILGenerator(), LogLevel.Trace); + List il = EmitPatched((type, pinned) => new MsilLocal(generator.DeclareLocal(type, pinned))).ToList(); + + MethodTranspiler.EmitMethod(il, generator); + + Type res = typeBuilder.CreateType(); + asmBuilder.Save(Path.GetFileName(target)); + foreach (var method in res.GetMethods(BindingFlags.Public | BindingFlags.Static)) + _log.Info($"Information " + method); + } public DynamicMethod ComposePatchedMethod() { DynamicMethod method = AllocatePatchMethod(); - var generator = new LoggingIlGenerator(method.GetILGenerator(), PrintMsil ? LogLevel.Info : LogLevel.Trace); + var generator = new LoggingIlGenerator(method.GetILGenerator(), + PrintMode.HasFlag(PrintModeEnum.EmittedReflection) ? LogLevel.Info : LogLevel.Trace); List il = EmitPatched((type, pinned) => new MsilLocal(generator.DeclareLocal(type, pinned))).ToList(); - if (PrintMsil) - { - lock (_log) - { - MethodTranspiler.IntegrityAnalysis(LogLevel.Info, il); - } - } - - MethodTranspiler.EmitMethod(il, generator); + var dumpTarget = DumpTarget != null ? File.CreateText(DumpTarget) : null; try { - PatchUtilities.Compile(method); - } - catch - { - lock (_log) + const string gap = "\n\n\n\n\n"; + + void LogTarget(PrintModeEnum mode, bool err, string msg) { - var ctx = new MethodContext(method); - ctx.Read(); - MethodTranspiler.IntegrityAnalysis(LogLevel.Warn, ctx.Instructions); + if (DumpMode.HasFlag(mode)) + dumpTarget?.WriteLine((err ? "ERROR " : "") + msg); + if (!PrintMode.HasFlag(mode)) return; + if (err) + _log.Error(msg); + else + _log.Info(msg); } - throw; + if (PrintMsil || DumpTarget != null) + { + lock (_log) + { + var ctx = new MethodContext(_method); + ctx.Read(); + LogTarget(PrintModeEnum.Original, false, "========== Original method =========="); + MethodTranspiler.IntegrityAnalysis((a, b) => LogTarget(PrintModeEnum.Original, a, b), ctx.Instructions, true); + LogTarget(PrintModeEnum.Original, false, gap); + + LogTarget(PrintModeEnum.Emitted, false, "========== Desired method =========="); + MethodTranspiler.IntegrityAnalysis((a, b) => LogTarget(PrintModeEnum.Emitted, a, b), il); + LogTarget(PrintModeEnum.Emitted, false, gap); + } + } + + MethodTranspiler.EmitMethod(il, generator); + + try + { + PatchUtilities.Compile(method); + } + catch + { + lock (_log) + { + var ctx = new MethodContext(method); + ctx.Read(); + MethodTranspiler.IntegrityAnalysis((err, msg) => _log.Warn(msg), ctx.Instructions); + } + + throw; + } + + if (PrintMsil || DumpTarget != null) + { + lock (_log) + { + var ctx = new MethodContext(method); + ctx.Read(); + LogTarget(PrintModeEnum.Patched, false, "========== Patched method =========="); + MethodTranspiler.IntegrityAnalysis((a, b) => LogTarget(PrintModeEnum.Patched, a, b), ctx.Instructions, true); + LogTarget(PrintModeEnum.Patched, false, gap); + } + } + } + finally + { + dumpTarget?.Close(); } return method; @@ -274,7 +349,8 @@ namespace Torch.Managers.PatchManager ? param.ParameterType.GetElementType() : param.ParameterType; if (retType == null || !retType.IsAssignableFrom(specialVariables[RESULT_PARAMETER].Type)) - throw new Exception($"Return type {specialVariables[RESULT_PARAMETER].Type} can't be assigned to result parameter type {retType}"); + throw new Exception( + $"Return type {specialVariables[RESULT_PARAMETER].Type} can't be assigned to result parameter type {retType}"); yield return new MsilInstruction(param.ParameterType.IsByRef ? OpCodes.Ldloca : OpCodes.Ldloc) .InlineValue(specialVariables[RESULT_PARAMETER]); break; diff --git a/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs b/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs index a85e265..6ae1002 100644 --- a/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs +++ b/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs @@ -2,9 +2,11 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Text; +using System.Windows.Documents; using Torch.Managers.PatchManager.Transpile; using Torch.Utils; @@ -103,8 +105,21 @@ namespace Torch.Managers.PatchManager.MSIL /// /// The try catch operation that is performed here. /// - public MsilTryCatchOperation TryCatchOperation { get; set; } = null; + [Obsolete("Since instructions can have multiple try catch operations you need to be using TryCatchOperations")] + public MsilTryCatchOperation TryCatchOperation + { + get => TryCatchOperations.FirstOrDefault(); + set + { + TryCatchOperations.Clear(); + TryCatchOperations.Add(value); + } + } + /// + /// The try catch operations performed here, in order from first to last. + /// + public readonly List TryCatchOperations = new List(); private static readonly ConcurrentDictionary _setterInfoForInlines = new ConcurrentDictionary(); @@ -125,15 +140,18 @@ namespace Torch.Managers.PatchManager.MSIL target = genType.GetProperty(nameof(MsilOperandInline.Value)); _setterInfoForInlines[type] = target; } + Debug.Assert(target?.DeclaringType != null); if (target.DeclaringType.IsInstanceOfType(Operand)) { target.SetValue(Operand, o); return this; } + type = type.BaseType; } - ((MsilOperandInline)Operand).Value = o; + + ((MsilOperandInline) Operand).Value = o; return this; } @@ -148,7 +166,8 @@ namespace Torch.Managers.PatchManager.MSIL Operand?.CopyTo(result.Operand); foreach (MsilLabel x in Labels) result.Labels.Add(x); - result.TryCatchOperation = TryCatchOperation; + foreach (var op in TryCatchOperations) + result.TryCatchOperations.Add(op); return result; } @@ -170,7 +189,7 @@ namespace Torch.Managers.PatchManager.MSIL /// This instruction public MsilInstruction InlineTarget(MsilLabel label) { - ((MsilOperandBrTarget)Operand).Target = label; + ((MsilOperandBrTarget) Operand).Target = label; return this; } @@ -185,7 +204,6 @@ namespace Torch.Managers.PatchManager.MSIL } - #pragma warning disable 649 [ReflectedMethod(Name = "StackChange")] private static Func _stackChange; @@ -210,7 +228,13 @@ namespace Torch.Managers.PatchManager.MSIL if (!op.IsStatic && OpCode != OpCodes.Newobj) num--; } + return num; } + + /// + /// Gets the maximum amount of space this instruction will use. + /// + public int MaxBytes => 2 + (Operand?.MaxBytes ?? 0); } } \ No newline at end of file diff --git a/Torch/Managers/PatchManager/MSIL/MsilOperand.cs b/Torch/Managers/PatchManager/MSIL/MsilOperand.cs index 0df66c4..63f3276 100644 --- a/Torch/Managers/PatchManager/MSIL/MsilOperand.cs +++ b/Torch/Managers/PatchManager/MSIL/MsilOperand.cs @@ -18,6 +18,11 @@ namespace Torch.Managers.PatchManager.MSIL /// public MsilInstruction Instruction { get; } + /// + /// Gets the maximum amount of space this operand will use. + /// + public abstract int MaxBytes { get; } + internal abstract void CopyTo(MsilOperand operand); internal abstract void Read(MethodContext context, BinaryReader reader); diff --git a/Torch/Managers/PatchManager/MSIL/MsilOperandBrTarget.cs b/Torch/Managers/PatchManager/MSIL/MsilOperandBrTarget.cs index 8e1c934..dd87294 100644 --- a/Torch/Managers/PatchManager/MSIL/MsilOperandBrTarget.cs +++ b/Torch/Managers/PatchManager/MSIL/MsilOperandBrTarget.cs @@ -57,6 +57,8 @@ namespace Torch.Managers.PatchManager.MSIL } + public override int MaxBytes => 4; // Long branch + internal override void CopyTo(MsilOperand operand) { var lt = operand as MsilOperandBrTarget; diff --git a/Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs b/Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs index 755af14..5709cc4 100644 --- a/Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs +++ b/Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs @@ -54,6 +54,8 @@ namespace Torch.Managers.PatchManager.MSIL { } + public override int MaxBytes => Instruction.OpCode.OperandType == OperandType.InlineI ? 4 : 1; + internal override void Read(MethodContext context, BinaryReader reader) { // ReSharper disable once SwitchStatementMissingSomeCases @@ -98,6 +100,8 @@ namespace Torch.Managers.PatchManager.MSIL { } + public override int MaxBytes => 4; + internal override void Read(MethodContext context, BinaryReader reader) { // ReSharper disable once SwitchStatementMissingSomeCases @@ -136,6 +140,8 @@ namespace Torch.Managers.PatchManager.MSIL { } + public override int MaxBytes => 8; + internal override void Read(MethodContext context, BinaryReader reader) { // ReSharper disable once SwitchStatementMissingSomeCases @@ -174,6 +180,8 @@ namespace Torch.Managers.PatchManager.MSIL { } + public override int MaxBytes => 8; + internal override void Read(MethodContext context, BinaryReader reader) { // ReSharper disable once SwitchStatementMissingSomeCases @@ -212,6 +220,8 @@ namespace Torch.Managers.PatchManager.MSIL { } + public override int MaxBytes => 4; + internal override void Read(MethodContext context, BinaryReader reader) { // ReSharper disable once SwitchStatementMissingSomeCases @@ -250,6 +260,8 @@ namespace Torch.Managers.PatchManager.MSIL { } + public override int MaxBytes => throw new NotImplementedException(); + internal override void Read(MethodContext context, BinaryReader reader) { // ReSharper disable once SwitchStatementMissingSomeCases @@ -286,6 +298,8 @@ namespace Torch.Managers.PatchManager.MSIL { } + public override int MaxBytes => Instruction.OpCode.OperandType == OperandType.ShortInlineVar ? 1 : 2; + internal override void Read(MethodContext context, BinaryReader reader) { int id; @@ -339,6 +353,8 @@ namespace Torch.Managers.PatchManager.MSIL { } + public override int MaxBytes => 2; + internal override void Read(MethodContext context, BinaryReader reader) { int id; @@ -389,6 +405,8 @@ namespace Torch.Managers.PatchManager.MSIL internal MsilOperandReflected(MsilInstruction instruction) : base(instruction) { } + + public override int MaxBytes => 4; internal override void Read(MethodContext context, BinaryReader reader) { diff --git a/Torch/Managers/PatchManager/MSIL/MsilOperandSwitch.cs b/Torch/Managers/PatchManager/MSIL/MsilOperandSwitch.cs index 2f28297..55c37af 100644 --- a/Torch/Managers/PatchManager/MSIL/MsilOperandSwitch.cs +++ b/Torch/Managers/PatchManager/MSIL/MsilOperandSwitch.cs @@ -20,6 +20,7 @@ namespace Torch.Managers.PatchManager.MSIL /// public MsilLabel[] Labels { get; set; } + public override int MaxBytes => 4 + (Labels?.Length * 4 ?? 0); internal override void CopyTo(MsilOperand operand) { diff --git a/Torch/Managers/PatchManager/MSIL/MsilTryCatchOperation.cs b/Torch/Managers/PatchManager/MSIL/MsilTryCatchOperation.cs index 5ea653e..7f555f4 100644 --- a/Torch/Managers/PatchManager/MSIL/MsilTryCatchOperation.cs +++ b/Torch/Managers/PatchManager/MSIL/MsilTryCatchOperation.cs @@ -24,6 +24,8 @@ namespace Torch.Managers.PatchManager.MSIL /// public class MsilTryCatchOperation { + internal int NativeOffset; + /// /// Operation type /// diff --git a/Torch/Managers/PatchManager/MethodRewritePattern.cs b/Torch/Managers/PatchManager/MethodRewritePattern.cs index 83ce8eb..73de002 100644 --- a/Torch/Managers/PatchManager/MethodRewritePattern.cs +++ b/Torch/Managers/PatchManager/MethodRewritePattern.cs @@ -168,18 +168,71 @@ namespace Torch.Managers.PatchManager /// /// Should the resulting MSIL of the transpile operation be printed. /// + [Obsolete] public bool PrintMsil { - get => _parent?.PrintMsil ?? _printMsilBacking; + get => PrintMode != 0; + set => PrintMode = PrintModeEnum.Emitted; + } + + private PrintModeEnum _printMsilBacking; + + /// + /// Types of IL to print to log + /// + public PrintModeEnum PrintMode + { + get => _parent?.PrintMode ?? _printMsilBacking; set { if (_parent != null) - _parent.PrintMsil = value; + _parent.PrintMode = value; else _printMsilBacking = value; } } - private bool _printMsilBacking; + + [Flags] + public enum PrintModeEnum + { + Original = 1, + Emitted = 2, + Patched = 4, + EmittedReflection = 8 + } + + /// + /// File to dump the emitted MSIL to. + /// + public string DumpTarget + { + get => _parent?.DumpTarget ?? _dumpTargetBacking; + set + { + if (_parent != null) + _parent.DumpTarget = value; + else + _dumpTargetBacking = value; + } + } + + /// + /// Types of IL to dump to file + /// + public PrintModeEnum DumpMode + { + get => _parent?.DumpMode ?? _dumpTargetMode; + set + { + if (_parent != null) + _parent.DumpMode = value; + else + _dumpTargetMode = value; + } + } + + private PrintModeEnum _dumpTargetMode; + private string _dumpTargetBacking; private readonly MethodRewritePattern _parent; diff --git a/Torch/Managers/PatchManager/PatchUtilities.cs b/Torch/Managers/PatchManager/PatchUtilities.cs index 46d2a29..50e9f0e 100644 --- a/Torch/Managers/PatchManager/PatchUtilities.cs +++ b/Torch/Managers/PatchManager/PatchUtilities.cs @@ -40,21 +40,25 @@ namespace Torch.Managers.PatchManager MethodTranspiler.EmitMethod(insn.ToList(), generator); } + public delegate void DelPrintIntegrityInfo(bool error, string msg); + /// /// Analyzes the integrity of a set of instructions. /// - /// default logging level + /// Logger /// instructions - public static void IntegrityAnalysis(LogLevel level, IReadOnlyList instructions) + public static void IntegrityAnalysis(DelPrintIntegrityInfo handler, IReadOnlyList instructions) { - MethodTranspiler.IntegrityAnalysis(level, instructions); + MethodTranspiler.IntegrityAnalysis(handler, instructions); } #pragma warning disable 649 - [ReflectedStaticMethod(Type = typeof(RuntimeHelpers), Name = "_CompileMethod", OverrideTypeNames = new[] { "System.IRuntimeMethodInfo" })] + [ReflectedStaticMethod(Type = typeof(RuntimeHelpers), Name = "_CompileMethod", OverrideTypeNames = new[] {"System.IRuntimeMethodInfo"})] private static Action _compileDynamicMethod; + [ReflectedMethod(Name = "GetMethodInfo")] private static Func _getMethodInfo; + [ReflectedMethod(Name = "GetMethodDescriptor")] private static Func _getMethodHandle; #pragma warning restore 649 @@ -70,4 +74,4 @@ namespace Torch.Managers.PatchManager _compileDynamicMethod.Invoke(runtimeMethodInfo); } } -} +} \ No newline at end of file diff --git a/Torch/Managers/PatchManager/Transpile/MethodContext.cs b/Torch/Managers/PatchManager/Transpile/MethodContext.cs index 52bd002..14713bd 100644 --- a/Torch/Managers/PatchManager/Transpile/MethodContext.cs +++ b/Torch/Managers/PatchManager/Transpile/MethodContext.cs @@ -8,6 +8,7 @@ using System.Reflection.Emit; using NLog; using Torch.Managers.PatchManager.MSIL; using Torch.Utils; +using VRage.Game.VisualScripting.Utils; namespace Torch.Managers.PatchManager.Transpile { @@ -44,14 +45,47 @@ namespace Torch.Managers.PatchManager.Transpile #pragma warning disable 649 - [ReflectedMethod(Name = "BakeByteArray")] private static Func _ilGeneratorBakeByteArray; + [ReflectedMethod(Name = "BakeByteArray")] + private static Func _ilGeneratorBakeByteArray; + + [ReflectedMethod(Name = "GetExceptions")] + private static Func _ilGeneratorGetExceptionHandlers; + + private const string InternalExceptionInfo = "System.Reflection.Emit.__ExceptionInfo, mscorlib"; + + [ReflectedMethod(Name = "GetExceptionTypes", TypeName = InternalExceptionInfo)] + private static Func _exceptionHandlerGetTypes; + + [ReflectedMethod(Name = "GetStartAddress", TypeName = InternalExceptionInfo)] + private static Func _exceptionHandlerGetStart; + + [ReflectedMethod(Name = "GetEndAddress", TypeName = InternalExceptionInfo)] + private static Func _exceptionHandlerGetEnd; + + [ReflectedMethod(Name = "GetFinallyEndAddress", TypeName = InternalExceptionInfo)] + private static Func _exceptionHandlerGetFinallyEnd; + + [ReflectedMethod(Name = "GetNumberOfCatches", TypeName = InternalExceptionInfo)] + private static Func _exceptionHandlerGetCatchCount; + + [ReflectedMethod(Name = "GetCatchAddresses", TypeName = InternalExceptionInfo)] + private static Func _exceptionHandlerGetCatchAddrs; + + [ReflectedMethod(Name = "GetCatchEndAddresses", TypeName = InternalExceptionInfo)] + private static Func _exceptionHandlerGetCatchEndAddrs; + + [ReflectedMethod(Name = "GetFilterAddresses", TypeName = InternalExceptionInfo)] + private static Func _exceptionHandlerGetFilterAddrs; #pragma warning restore 649 + private readonly Array _dynamicExceptionTable; + public MethodContext(DynamicMethod method) { Method = null; MethodBody = null; _msilBytes = _ilGeneratorBakeByteArray(method.GetILGenerator()); + _dynamicExceptionTable = _ilGeneratorGetExceptionHandlers(method.GetILGenerator()); TokenResolver = new DynamicMethodTokenResolver(method); } @@ -76,6 +110,7 @@ namespace Torch.Managers.PatchManager.Transpile { instructionValue = (short) ((instructionValue << 8) | memory.ReadByte()); } + if (!OpCodeLookup.TryGetValue(instructionValue, out OpCode opcode)) { var msg = $"Unknown opcode {instructionValue:X}"; @@ -83,6 +118,7 @@ namespace Torch.Managers.PatchManager.Transpile Debug.Assert(false, msg); continue; } + if (opcode.Size != memory.Position - opcodeOffset) throw new Exception( $"Opcode said it was {opcode.Size} but we read {memory.Position - opcodeOffset}"); @@ -97,28 +133,56 @@ namespace Torch.Managers.PatchManager.Transpile private void ResolveCatchClauses() { - if (MethodBody == null) - return; - 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.Fault) != 0) - catchInstruction.TryCatchOperation = - new MsilTryCatchOperation(MsilTryCatchOperationType.BeginFaultBlock); - else if ((clause.Flags & ExceptionHandlingClauseOptions.Finally) != 0) - catchInstruction.TryCatchOperation = - new MsilTryCatchOperation(MsilTryCatchOperationType.BeginFinallyBlock); - else - catchInstruction.TryCatchOperation = - new MsilTryCatchOperation(MsilTryCatchOperationType.BeginClauseBlock, clause.CatchType); + if (MethodBody != null) + foreach (var clause in MethodBody.ExceptionHandlingClauses) + { + AddEhHandler(clause.TryOffset, MsilTryCatchOperationType.BeginExceptionBlock); + if ((clause.Flags & ExceptionHandlingClauseOptions.Fault) != 0) + AddEhHandler(clause.HandlerOffset, MsilTryCatchOperationType.BeginFaultBlock); + else if ((clause.Flags & ExceptionHandlingClauseOptions.Finally) != 0) + AddEhHandler(clause.HandlerOffset, MsilTryCatchOperationType.BeginFinallyBlock); + else + AddEhHandler(clause.HandlerOffset, MsilTryCatchOperationType.BeginClauseBlock, clause.CatchType); + AddEhHandler(clause.HandlerOffset + clause.HandlerLength, MsilTryCatchOperationType.EndExceptionBlock); + } - finalInstruction.TryCatchOperation = - new MsilTryCatchOperation(MsilTryCatchOperationType.EndExceptionBlock); - } + if (_dynamicExceptionTable != null) + foreach (var eh in _dynamicExceptionTable) + { + var catchCount = _exceptionHandlerGetCatchCount(eh); + var exTypes = _exceptionHandlerGetTypes(eh); + var exCatches = _exceptionHandlerGetCatchAddrs(eh); + var exCatchesEnd = _exceptionHandlerGetCatchEndAddrs(eh); + var exFilters = _exceptionHandlerGetFilterAddrs(eh); + var tryAddr = _exceptionHandlerGetStart(eh); + var endAddr = _exceptionHandlerGetEnd(eh); + var endFinallyAddr = _exceptionHandlerGetFinallyEnd(eh); + for (var i = 0; i < catchCount; i++) + { + var flags = (ExceptionHandlingClauseOptions) exTypes[i]; + var endAddress = (flags & ExceptionHandlingClauseOptions.Finally) != 0 ? endFinallyAddr : endAddr; + + var catchAddr = exCatches[i]; + var catchEndAddr = exCatchesEnd[i]; + var filterAddr = exFilters[i]; + + AddEhHandler(tryAddr, MsilTryCatchOperationType.BeginExceptionBlock); + if ((flags & ExceptionHandlingClauseOptions.Fault) != 0) + AddEhHandler(catchAddr, MsilTryCatchOperationType.BeginFaultBlock); + else if ((flags & ExceptionHandlingClauseOptions.Finally) != 0) + AddEhHandler(catchAddr, MsilTryCatchOperationType.BeginFinallyBlock); + else + AddEhHandler(catchAddr, MsilTryCatchOperationType.BeginClauseBlock); + AddEhHandler(catchEndAddr, MsilTryCatchOperationType.EndExceptionBlock); + } + } + } + + private void AddEhHandler(int offset, MsilTryCatchOperationType op, Type type = null) + { + var instruction = FindInstruction(offset); + instruction.TryCatchOperations.Add(new MsilTryCatchOperation(op, type) {NativeOffset = offset}); + instruction.TryCatchOperations.Sort((a, b) => a.NativeOffset.CompareTo(b.NativeOffset)); } public MsilInstruction FindInstruction(int offset) @@ -132,6 +196,7 @@ namespace Torch.Managers.PatchManager.Transpile else max = mid; } + return min >= 0 && min < _instructions.Count ? _instructions[min] : null; } diff --git a/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs b/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs index 3c5bc94..6806642 100644 --- a/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs +++ b/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs @@ -14,8 +14,8 @@ namespace Torch.Managers.PatchManager.Transpile { public static readonly Logger _log = LogManager.GetCurrentClassLogger(); - internal static IEnumerable Transpile(MethodBase baseMethod, Func localCreator, - IEnumerable transpilers, MsilLabel retLabel) + internal static IEnumerable Transpile(MethodBase baseMethod, Func localCreator, IEnumerable transpilers, + MsilLabel retLabel) { var context = new MethodContext(baseMethod); context.Read(); @@ -24,9 +24,8 @@ namespace Torch.Managers.PatchManager.Transpile } internal static IEnumerable Transpile(MethodBase baseMethod, IEnumerable methodContent, - Func localCreator, - IEnumerable transpilers, MsilLabel retLabel) - { + Func localCreator, IEnumerable transpilers, MsilLabel retLabel) + { foreach (MethodInfo transpiler in transpilers) { var paramList = new List(); @@ -44,24 +43,61 @@ namespace Torch.Managers.PatchManager.Transpile throw new ArgumentException( $"Bad transpiler parameter type {parameter.ParameterType.FullName} {parameter.Name}"); } - methodContent = (IEnumerable)transpiler.Invoke(null, paramList.ToArray()); + + methodContent = (IEnumerable) transpiler.Invoke(null, paramList.ToArray()); } + return FixBranchAndReturn(methodContent, retLabel); } - internal static void EmitMethod(IReadOnlyList instructions, LoggingIlGenerator target) + internal static void EmitMethod(IReadOnlyList source, LoggingIlGenerator target) { - for (var i = 0; i < instructions.Count; i++) + var instructions = source.ToArray(); + var offsets = new int[instructions.Length]; + // Calc worst case offsets + { + var j = 0; + for (var i = 0; i < instructions.Length; i++) + { + offsets[i] = j; + j += instructions[i].MaxBytes; + } + } + + // Perform label markup + var targets = new Dictionary(); + for (var i = 0; i < instructions.Length; 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; + } + + // Simplify branch instructions + for (var i = 0; i < instructions.Length; i++) + { + var existing = instructions[i]; + if (existing.Operand is MsilOperandBrTarget brOperand && _longToShortBranch.TryGetValue(existing.OpCode, out var shortOpcode)) + { + var targetIndex = targets[brOperand.Target]; + var delta = offsets[targetIndex] - offsets[i]; + if (sbyte.MinValue < delta && delta < sbyte.MaxValue) + instructions[i] = instructions[i].CopyWith(shortOpcode); + } + } + + for (var i = 0; i < instructions.Length; i++) { MsilInstruction il = instructions[i]; - if (il.TryCatchOperation != null) - switch (il.TryCatchOperation.Type) + foreach (var tro in il.TryCatchOperations) + switch (tro.Type) { case MsilTryCatchOperationType.BeginExceptionBlock: target.BeginExceptionBlock(); break; case MsilTryCatchOperationType.BeginClauseBlock: - target.BeginCatchBlock(il.TryCatchOperation.CatchType); + target.BeginCatchBlock(tro.CatchType); break; case MsilTryCatchOperationType.BeginFaultBlock: target.BeginFaultBlock(); @@ -79,22 +115,25 @@ namespace Torch.Managers.PatchManager.Transpile foreach (MsilLabel label in il.Labels) target.MarkLabel(label.LabelFor(target)); - MsilInstruction ilNext = i < instructions.Count - 1 ? instructions[i + 1] : null; + MsilInstruction ilNext = i < instructions.Length - 1 ? instructions[i + 1] : null; // Leave opcodes emitted by these: - if (il.OpCode == OpCodes.Endfilter && ilNext?.TryCatchOperation?.Type == - MsilTryCatchOperationType.BeginClauseBlock) + if (il.OpCode == OpCodes.Endfilter && ilNext != null && + ilNext.TryCatchOperations.Any(x => x.Type == MsilTryCatchOperationType.BeginClauseBlock)) continue; - if ((il.OpCode == OpCodes.Leave || il.OpCode == OpCodes.Leave_S) && - (ilNext?.TryCatchOperation?.Type == MsilTryCatchOperationType.EndExceptionBlock || - ilNext?.TryCatchOperation?.Type == MsilTryCatchOperationType.BeginClauseBlock || - ilNext?.TryCatchOperation?.Type == MsilTryCatchOperationType.BeginFaultBlock || - ilNext?.TryCatchOperation?.Type == MsilTryCatchOperationType.BeginFinallyBlock)) + if ((il.OpCode == OpCodes.Leave || il.OpCode == OpCodes.Leave_S) && ilNext != null && + ilNext.TryCatchOperations.Any(x => x.Type == MsilTryCatchOperationType.EndExceptionBlock || + x.Type == MsilTryCatchOperationType.BeginClauseBlock || + x.Type == MsilTryCatchOperationType.BeginFaultBlock || + x.Type == MsilTryCatchOperationType.BeginFinallyBlock)) continue; if ((il.OpCode == OpCodes.Leave || il.OpCode == OpCodes.Leave_S || il.OpCode == OpCodes.Endfinally) && - ilNext?.TryCatchOperation?.Type == MsilTryCatchOperationType.EndExceptionBlock) + ilNext != null && ilNext.TryCatchOperations.Any(x => x.Type == MsilTryCatchOperationType.EndExceptionBlock)) continue; - + if (il.OpCode == OpCodes.Endfinally && ilNext != null && + ilNext.TryCatchOperations.Any(x => x.Type == MsilTryCatchOperationType.EndExceptionBlock)) + continue; + if (il.Operand != null) il.Operand.Emit(target); else @@ -107,7 +146,7 @@ namespace Torch.Managers.PatchManager.Transpile /// /// default logging level /// instructions - public static void IntegrityAnalysis(LogLevel level, IReadOnlyList instructions) + public static void IntegrityAnalysis(PatchUtilities.DelPrintIntegrityInfo log, IReadOnlyList instructions, bool offests = false) { var targets = new Dictionary(); for (var i = 0; i < instructions.Count; i++) @@ -118,15 +157,46 @@ namespace Torch.Managers.PatchManager.Transpile targets[label] = i; } + var simpleLabelNames = new Dictionary(); + foreach (var lbl in targets.OrderBy(x => x.Value)) + simpleLabelNames.Add(lbl.Key, "L" + simpleLabelNames.Count); + 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 tryCatchDepth = new int[instructions.Count]; + + for (var i = 0; i < instructions.Count - 1; i++) { + var k = instructions[i]; + var prevDepth = i > 0 ? tryCatchDepth[i] : 0; + var currentDepth = prevDepth; + foreach (var tro in k.TryCatchOperations) + if (tro.Type == MsilTryCatchOperationType.BeginExceptionBlock) + currentDepth++; + else if (tro.Type == MsilTryCatchOperationType.EndExceptionBlock) + currentDepth--; + tryCatchDepth[i + 1] = currentDepth; + } + + for (var i = 0; i < instructions.Count; i++) + { + var tryCatchDepthSelf = tryCatchDepth[i]; var k = instructions[i]; var line = (data[i] ?? (data[i] = new StringBuilder())).Clear(); + foreach (var tro in k.TryCatchOperations) + { + if (tro.Type == MsilTryCatchOperationType.BeginExceptionBlock) + tryCatchDepthSelf++; + line.AppendLine($"{new string(' ', (tryCatchDepthSelf - 1) * 2)}// {tro.Type} ({tro.CatchType}) ({tro.NativeOffset:X4})"); + if (tro.Type == MsilTryCatchOperationType.EndExceptionBlock) + tryCatchDepthSelf--; + } + + var tryCatchIndent = new string(' ', tryCatchDepthSelf * 2); + if (!unreachable) { foreach (var label in k.Labels) @@ -138,60 +208,84 @@ namespace Torch.Managers.PatchManager.Transpile 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})"); + line.AppendLine($"WARN{tryCatchIndent}// | Label {simpleLabelNames[label]} has multiple entry stack sizes ({otherDesc})"); } } } + foreach (var label in k.Labels) { if (!labelStackSize.TryGetValue(label, out var entry)) + { + line.AppendLine($"{tryCatchIndent}// \\/ Label {simpleLabelNames[label]}"); continue; + } string desc = string.Join(", ", entry.Select(x => $"{x.Key:X4}=>{x.Value}")); - line.AppendLine($"// \\/ Label {label} has stack sizes {desc}"); + line.AppendLine($"{tryCatchIndent}// \\/ Label {simpleLabelNames[label]} has stack sizes {desc}"); if (unreachable && entry.Any()) { stack = entry.Values.First(); unreachable = false; } } + + if (k.TryCatchOperations.Any(x => x.Type == MsilTryCatchOperationType.BeginClauseBlock)) + stack++; // Exception info 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" : "")); + line.Append($"{tryCatchIndent}{(offests ? k.Offset : i):X4} S:{stack:D2} dS:{k.StackChange():+0;-#}\t{k.OpCode}\t"); + if (k.Operand is MsilOperandBrTarget bri) + line.Append(simpleLabelNames[bri.Target]); + else + line.Append(k.Operand); + line.AppendLine($"\t{(unreachable ? "\t// UNREACHABLE" : "")}"); + MsilLabel[] branchTargets = null; if (k.Operand is MsilOperandBrTarget br) + branchTargets = new[] {br.Target}; + else if (k.Operand is MsilOperandSwitch swi) + branchTargets = swi.Labels; + + if (branchTargets != null) { - 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))) + var foundUnprocessed = false; + foreach (var brTarget in branchTargets) { - 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 (!labelStackSize.TryGetValue(brTarget, out Dictionary otherStack)) + labelStackSize[brTarget] = 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{tryCatchIndent}// ^ Label {simpleLabelNames[brTarget]} has multiple entry stack sizes ({otherDesc})"); + } + + if (targets.TryGetValue(brTarget, out var target) && target < i && reparsed.Add(brTarget)) + { + i = target - 1; + unreachable = false; + foundUnprocessed = true; + break; + } } - if (targets.TryGetValue(br.Target, out var target) && target < i && reparsed.Add(br.Target)) - { - i = target - 1; - unreachable = false; + + if (foundUnprocessed) continue; - } } + if (k.OpCode == OpCodes.Br || k.OpCode == OpCodes.Br_S || k.OpCode == OpCodes.Leave || k.OpCode == OpCodes.Leave_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.Log(level, line.Trim()); - } + foreach (var line in k.ToString().Split('\n')) + { + if (string.IsNullOrWhiteSpace(line)) + continue; + if (line.StartsWith("WARN", StringComparison.OrdinalIgnoreCase)) + log(true, line.Substring(4).Trim()); + else + log(false, line.Trim('\n', '\r')); + } } private static IEnumerable FixBranchAndReturn(IEnumerable insn, MsilLabel retTarget) @@ -204,7 +298,7 @@ namespace Torch.Managers.PatchManager.Transpile _log.Trace($"Replacing {i} with {j}"); yield return j; } - else if (_opcodeReplaceRule.TryGetValue(i.OpCode, out OpCode replaceOpcode)) + else if (_shortToLongBranch.TryGetValue(i.OpCode, out OpCode replaceOpcode)) { var result = i.CopyWith(replaceOpcode); _log.Trace($"Replacing {i} with {result}"); @@ -215,23 +309,31 @@ namespace Torch.Managers.PatchManager.Transpile } } - private static readonly Dictionary _opcodeReplaceRule; + private static readonly Dictionary _shortToLongBranch; + private static readonly Dictionary _longToShortBranch; + static MethodTranspiler() { - _opcodeReplaceRule = new Dictionary(); + _shortToLongBranch = new Dictionary(); + _longToShortBranch = new Dictionary(); foreach (var field in typeof(OpCodes).GetFields(BindingFlags.Static | BindingFlags.Public)) { - var opcode = (OpCode)field.GetValue(null); + var opcode = (OpCode) field.GetValue(null); if (opcode.OperandType == OperandType.ShortInlineBrTarget && opcode.Name.EndsWith(".s", StringComparison.OrdinalIgnoreCase)) { - var other = (OpCode?)typeof(OpCodes).GetField(field.Name.Substring(0, field.Name.Length - 2), + var other = (OpCode?) typeof(OpCodes).GetField(field.Name.Substring(0, field.Name.Length - 2), BindingFlags.Static | BindingFlags.Public)?.GetValue(null); if (other.HasValue && other.Value.OperandType == OperandType.InlineBrTarget) - _opcodeReplaceRule.Add(opcode, other.Value); + { + _shortToLongBranch.Add(opcode, other.Value); + _longToShortBranch.Add(other.Value, opcode); + } } } - _opcodeReplaceRule[OpCodes.Leave_S] = OpCodes.Leave; + + _shortToLongBranch[OpCodes.Leave_S] = OpCodes.Leave; + _longToShortBranch[OpCodes.Leave] = OpCodes.Leave_S; } } -} +} \ No newline at end of file