Fixed issues with operand replacing, and branch instructions.

This commit is contained in:
Westin Miller
2017-09-11 05:18:07 -07:00
parent b42d43c0e1
commit 0073e101dd
8 changed files with 155 additions and 47 deletions

View File

@@ -4,6 +4,7 @@ using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using Torch.Managers.PatchManager.Transpile;
using Torch.Utils;
using Label = System.Windows.Controls.Label;
namespace Torch.Managers.PatchManager.MSIL
@@ -13,8 +14,6 @@ namespace Torch.Managers.PatchManager.MSIL
/// </summary>
public class MsilInstruction
{
private MsilOperand _operandBacking;
/// <summary>
/// Creates a new instruction with the given opcode.
/// </summary>
@@ -97,16 +96,7 @@ namespace Torch.Managers.PatchManager.MSIL
/// <summary>
/// The operand for this instruction, or null.
/// </summary>
public MsilOperand Operand
{
get => _operandBacking;
set
{
if (_operandBacking != null && value.GetType() != _operandBacking.GetType())
throw new ArgumentException($"Operand for {OpCode.Name} must be {_operandBacking.GetType().Name}");
_operandBacking = value;
}
}
public MsilOperand Operand { get; }
/// <summary>
/// Labels pointing to this instruction.
@@ -128,11 +118,12 @@ namespace Torch.Managers.PatchManager.MSIL
/// <summary>
/// Makes a copy of the instruction with a new opcode.
/// </summary>
/// <param name="code">The new opcode</param>
/// <param name="newOpcode">The new opcode</param>
/// <returns>The copy</returns>
public MsilInstruction CopyWith(OpCode code)
public MsilInstruction CopyWith(OpCode newOpcode)
{
var result = new MsilInstruction(code) { Operand = this.Operand };
var result = new MsilInstruction(newOpcode);
Operand?.CopyTo(result.Operand);
foreach (MsilLabel x in Labels)
result.Labels.Add(x);
return result;
@@ -172,5 +163,28 @@ namespace Torch.Managers.PatchManager.MSIL
sb.Append(OpCode.Name).Append("\t").Append(Operand);
return sb.ToString();
}
#pragma warning disable 169
[ReflectedMethod(Name = "StackChange")]
private static Func<OpCode, int> _stackChange;
#pragma warning restore 169
public int StackChange()
{
int num = _stackChange.Invoke(OpCode);
if ((OpCode == OpCodes.Call || OpCode == OpCodes.Callvirt || OpCode == OpCodes.Newobj) &&
Operand is MsilOperandInline<MethodBase> inline)
{
MethodBase op = inline.Value;
if (op is MethodInfo mi && mi.ReturnType != typeof(void))
num++;
num -= op.GetParameters().Length;
if (!op.IsStatic && OpCode != OpCodes.Newobj)
num--;
}
return num;
}
}
}

View File

@@ -18,6 +18,8 @@ namespace Torch.Managers.PatchManager.MSIL
/// </summary>
public MsilInstruction Instruction { get; }
internal abstract void CopyTo(MsilOperand operand);
internal abstract void Read(MethodContext context, BinaryReader reader);
internal abstract void Emit(LoggingIlGenerator generator);

View File

@@ -1,4 +1,5 @@
using System.IO;
using System;
using System.IO;
using System.Reflection.Emit;
using Torch.Managers.PatchManager.Transpile;
@@ -22,7 +23,7 @@ namespace Torch.Managers.PatchManager.MSIL
{
int val = Instruction.OpCode.OperandType == OperandType.InlineBrTarget
? reader.ReadInt32()
: reader.ReadByte();
: reader.ReadSByte();
Target = context.LabelAt((int)reader.BaseStream.Position + val);
}
@@ -31,6 +32,14 @@ namespace Torch.Managers.PatchManager.MSIL
generator.Emit(Instruction.OpCode, Target.LabelFor(generator));
}
internal override void CopyTo(MsilOperand operand)
{
var lt = operand as MsilOperandBrTarget;
if (lt == null)
throw new ArgumentException($"Target {operand?.GetType().Name} must be of same type {GetType().Name}", nameof(operand));
lt.Target = Target;
}
/// <inheritdoc />
public override string ToString()
{

View File

@@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using Torch.Managers.PatchManager.Transpile;
@@ -22,6 +23,15 @@ namespace Torch.Managers.PatchManager.MSIL
/// </summary>
public T Value { get; set; }
internal override void CopyTo(MsilOperand operand)
{
var lt = operand as MsilOperandInline<T>;
if (lt == null)
throw new ArgumentException($"Target {operand?.GetType().Name} must be of same type {GetType().Name}", nameof(operand));
lt.Value = Value;
;
}
/// <inheritdoc />
public override string ToString()
{
@@ -209,16 +219,19 @@ namespace Torch.Managers.PatchManager.MSIL
internal override void Read(MethodContext context, BinaryReader reader)
{
Value =
context.Method.GetParameters()[
int paramID =
Instruction.OpCode.OperandType == OperandType.ShortInlineVar
? reader.ReadByte()
: reader.ReadUInt16()];
: reader.ReadUInt16();
if (paramID == 0 && !context.Method.IsStatic)
throw new ArgumentException("Haven't figured out how to ldarg with the \"this\" argument");
Value = context.Method.GetParameters()[paramID - (context.Method.IsStatic ? 0 : 1)];
}
internal override void Emit(LoggingIlGenerator generator)
{
generator.Emit(Instruction.OpCode, Value.Position);
var methodInfo = Value.Member as MethodBase;
generator.Emit(Instruction.OpCode, Value.Position + (methodInfo != null && methodInfo.IsStatic ? 0 : 1));
}
}

View File

@@ -1,4 +1,5 @@
using System.IO;
using System;
using System.IO;
using System.Linq;
using System.Reflection.Emit;
using Torch.Managers.PatchManager.Transpile;
@@ -19,6 +20,20 @@ namespace Torch.Managers.PatchManager.MSIL
/// </summary>
public MsilLabel[] Labels { get; set; }
internal override void CopyTo(MsilOperand operand)
{
var lt = operand as MsilOperandSwitch;
if (lt == null)
throw new ArgumentException($"Target {operand?.GetType().Name} must be of same type {GetType().Name}", nameof(operand));
if (Labels == null)
lt.Labels = null;
else
{
lt.Labels = new MsilLabel[Labels.Length];
Array.Copy(Labels, lt.Labels, Labels.Length);
}
}
internal override void Read(MethodContext context, BinaryReader reader)
{
int length = reader.ReadInt32();

View File

@@ -52,7 +52,7 @@ namespace Torch.Managers.PatchManager.Transpile
/// <inheritdoc cref="ILGenerator.Emit(OpCode, LocalBuilder)"/>
public void Emit(OpCode op, LocalBuilder arg)
{
_log?.Trace($"Emit\t{op,_opcodePadding} L:{arg.LocalIndex} {arg.LocalType}");
_log?.Trace($"Emit\t{op,_opcodePadding} Local:{arg.LocalIndex}/{arg.LocalType}");
Backing.Emit(op, arg);
}

View File

@@ -7,6 +7,7 @@ using System.Reflection;
using System.Reflection.Emit;
using NLog;
using Torch.Managers.PatchManager.MSIL;
using Torch.Utils;
namespace Torch.Managers.PatchManager.Transpile
{
@@ -52,20 +53,19 @@ namespace Torch.Managers.PatchManager.Transpile
using (var reader = new BinaryReader(memory))
while (memory.Length > memory.Position)
{
var count = 1;
var opcodeOffset = (int) memory.Position;
var instructionValue = (short)memory.ReadByte();
if (Prefixes.Contains(instructionValue))
{
instructionValue = (short)((instructionValue << 8) | memory.ReadByte());
count++;
}
if (!OpCodeLookup.TryGetValue(instructionValue, out OpCode opcode))
throw new Exception($"Unknown opcode {instructionValue:X}");
if (opcode.Size != count)
throw new Exception($"Opcode said it was {opcode.Size} but we read {count}");
if (opcode.Size != memory.Position - opcodeOffset)
throw new Exception($"Opcode said it was {opcode.Size} but we read {memory.Position - opcodeOffset}");
var instruction = new MsilInstruction(opcode)
{
Offset = (int)memory.Position
Offset = opcodeOffset
};
_instructions.Add(instruction);
instruction.Operand?.Read(this, reader);
@@ -76,22 +76,74 @@ namespace Torch.Managers.PatchManager.Transpile
{
foreach (var label in Labels)
{
int min = 0, max = _instructions.Count - 1;
while (min <= max)
int min = 0, max = _instructions.Count;
while (min != max)
{
var mid = min + ((max - min) / 2);
if (label.Key < _instructions[mid].Offset)
max = mid - 1;
else
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);
}
}
[Conditional("DEBUG")]
public void CheckIntegrity()
{
var entryStackCount = new Dictionary<MsilLabel, Dictionary<MsilInstruction, int>>();
var currentStackSize = 0;
foreach (MsilInstruction insn in _instructions)
{
// I don't want to deal with this, so I won't
if (insn.OpCode == OpCodes.Br || insn.OpCode == OpCodes.Br_S || insn.OpCode == OpCodes.Jmp ||
insn.OpCode == OpCodes.Leave || insn.OpCode == OpCodes.Leave_S)
break;
foreach (MsilLabel label in insn.Labels)
if (entryStackCount.TryGetValue(label, out Dictionary<MsilInstruction, int> dict))
dict.Add(insn, currentStackSize);
else
(entryStackCount[label] = new Dictionary<MsilInstruction, int>()).Add(insn, currentStackSize);
currentStackSize += insn.StackChange();
if (insn.Operand is MsilOperandBrTarget br)
if (entryStackCount.TryGetValue(br.Target, out Dictionary<MsilInstruction, int> dict))
dict.Add(insn, currentStackSize);
else
(entryStackCount[br.Target] = new Dictionary<MsilInstruction, int>()).Add(insn, currentStackSize);
}
foreach (KeyValuePair<MsilLabel, Dictionary<MsilInstruction, int>> label in entryStackCount)
{
if (label.Value.Values.Aggregate(new HashSet<int>(), (a, b) =>
{
a.Add(b);
return a;
}).Count > 1)
{
_log.Warn($"Label {label.Key} has multiple entry stack counts");
foreach (KeyValuePair<MsilInstruction, int> kv in label.Value)
_log.Warn($"{kv.Key.Offset:X4} {kv.Key} => {kv.Value}");
}
}
}
public string ToHumanMsil()
{
return string.Join("\n", _instructions.Select(x => $"IL_{x.Offset:X4}: {x}"));
return string.Join("\n", _instructions.Select(x => $"IL_{x.Offset:X4}: {x.StackChange()} {x}"));
}
private static readonly Dictionary<short, OpCode> OpCodeLookup;

View File

@@ -9,12 +9,13 @@ namespace Torch.Managers.PatchManager.Transpile
{
internal class MethodTranspiler
{
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
public static readonly Logger _log = LogManager.GetCurrentClassLogger();
internal static void Transpile(MethodBase baseMethod, IEnumerable<MethodInfo> transpilers, LoggingIlGenerator output, Label? retLabel)
{
var context = new MethodContext(baseMethod);
context.Read();
context.CheckIntegrity();
_log.Trace("Input Method:");
_log.Trace(context.ToHumanMsil);
@@ -35,14 +36,16 @@ namespace Torch.Managers.PatchManager.Transpile
MsilInstruction j = new MsilInstruction(OpCodes.Br).InlineTarget(new MsilLabel(retTarget.Value));
foreach (MsilLabel l in i.Labels)
j.Labels.Add(l);
_log.Trace($"Replacing {i} with {j}");
yield return j;
continue;
}
if (_opcodeReplaceRule.TryGetValue(i.OpCode, out OpCode replaceOpcode))
else if (_opcodeReplaceRule.TryGetValue(i.OpCode, out OpCode replaceOpcode))
{
yield return i.CopyWith(replaceOpcode);
continue;
var result = i.CopyWith(replaceOpcode);
_log.Trace($"Replacing {i} with {result}");
yield return result;
}
else
yield return i;
}
}