Fixed patch manager to emit try-catch-finally blocks.

Solves issue with PBs not running
This commit is contained in:
Westin Miller
2017-11-02 22:43:59 -07:00
parent b3ab0cbd74
commit 8b98deafca
8 changed files with 203 additions and 57 deletions

View File

@@ -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;
}
}

View File

@@ -104,6 +104,11 @@ namespace Torch.Managers.PatchManager.MSIL
/// </summary>
public HashSet<MsilLabel> Labels { get; } = new HashSet<MsilLabel>();
/// <summary>
/// The try catch operation that is performed here.
/// </summary>
public MsilTryCatchOperation TryCatchOperation { get; set; } = null;
private static readonly ConcurrentDictionary<Type, PropertyInfo> _setterInfoForInlines = new ConcurrentDictionary<Type, PropertyInfo>();
@@ -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;
}
/// <summary>
/// Emits this instruction to the given generator
/// </summary>
/// <param name="target">Emit target</param>
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);
}
/// <inheritdoc />
public override string ToString()
{

View File

@@ -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
{
/// <summary>
/// Represents a try/catch block operation type
/// </summary>
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
}
/// <summary>
/// Represents a try catch operation.
/// </summary>
public class MsilTryCatchOperation
{
/// <summary>
/// Operation type
/// </summary>
public readonly MsilTryCatchOperationType Type;
/// <summary>
/// Type caught by this operation, or null if none.
/// </summary>
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;
}
}
}

View File

@@ -34,7 +34,7 @@ namespace Torch.Managers.PatchManager
/// <param name="generator">Output</param>
public static void EmitInstructions(IEnumerable<MsilInstruction> insn, LoggingIlGenerator generator)
{
MethodTranspiler.Emit(insn, generator);
MethodTranspiler.EmitMethod(insn.ToList(), generator);
}
}
}

View File

@@ -146,6 +146,51 @@ namespace Torch.Managers.PatchManager.Transpile
Backing.Emit(op, arg);
}
#region Exceptions
/// <inheritdoc cref="ILGenerator.BeginExceptionBlock"/>
public Label BeginExceptionBlock()
{
_log?.Trace($"BeginExceptionBlock");
return Backing.BeginExceptionBlock();
}
/// <inheritdoc cref="ILGenerator.BeginCatchBlock"/>
public void BeginCatchBlock(Type caught)
{
_log?.Trace($"BeginCatchBlock {caught}");
Backing.BeginCatchBlock(caught);
}
/// <inheritdoc cref="ILGenerator.BeginExceptFilterBlock"/>
public void BeginExceptFilterBlock()
{
_log?.Trace($"BeginExceptFilterBlock");
Backing.BeginExceptFilterBlock();
}
/// <inheritdoc cref="ILGenerator.BeginFaultBlock"/>
public void BeginFaultBlock()
{
_log?.Trace($"BeginFaultBlock");
Backing.BeginFaultBlock();
}
/// <inheritdoc cref="ILGenerator.BeginFinallyBlock"/>
public void BeginFinallyBlock()
{
_log?.Trace($"BeginFinallyBlock");
Backing.BeginFinallyBlock();
}
/// <inheritdoc cref="ILGenerator.EndExceptionBlock"/>
public void EndExceptionBlock()
{
_log?.Trace($"EndExceptionBlock");
Backing.EndExceptionBlock();
}
#endregion
/// <inheritdoc cref="ILGenerator.MarkLabel(Label)"/>
public void MarkLabel(Label label)
{

View File

@@ -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<int, MsilLabel> Labels { get; } = new Dictionary<int, MsilLabel>();
@@ -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()
@@ -72,31 +76,43 @@ namespace Torch.Managers.PatchManager.Transpile
}
}
private void ResolveLabels()
private void ResolveCatchClauses()
{
foreach (var label in Labels)
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 < label.Key)
if (_instructions[mid].Offset < offset)
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}"))}");
return min >= 0 && min < _instructions.Count ? _instructions[min] : null;
}
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);
private void ResolveLabels()
{
foreach (var label in Labels)
{
MsilInstruction target = FindInstruction(label.Key);
target.Labels?.Add(label.Value);
}
}

View File

@@ -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<MsilInstruction>)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<MsilInstruction> instructions, LoggingIlGenerator target)
{
foreach (var k in methodContent)
k.Emit(output);
for (var i = 0; i < instructions.Count; i++)
{
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<MsilInstruction> input, LoggingIlGenerator output)
{
foreach (MsilInstruction k in FixBranchAndReturn(input, null))
k.Emit(output);
}
private static IEnumerable<MsilInstruction> FixBranchAndReturn(IEnumerable<MsilInstruction> 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;
}

View File

@@ -184,6 +184,7 @@
<Compile Include="Managers\PatchManager\MSIL\MsilOperandInline.cs" />
<Compile Include="Managers\PatchManager\MSIL\MsilOperandSwitch.cs" />
<Compile Include="Managers\PatchManager\MethodRewritePattern.cs" />
<Compile Include="Managers\PatchManager\MSIL\MsilTryCatchOperation.cs" />
<Compile Include="Managers\PatchManager\PatchShimAttribute.cs" />
<Compile Include="Managers\PatchManager\PatchContext.cs" />
<Compile Include="Managers\PatchManager\PatchManager.cs" />