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.Reflection.Emit;
using System.Text; using System.Text;
using Torch.Managers.PatchManager.Transpile; using Torch.Managers.PatchManager.Transpile;
using Torch.Utils;
using Label = System.Windows.Controls.Label; using Label = System.Windows.Controls.Label;
namespace Torch.Managers.PatchManager.MSIL namespace Torch.Managers.PatchManager.MSIL
@@ -13,8 +14,6 @@ namespace Torch.Managers.PatchManager.MSIL
/// </summary> /// </summary>
public class MsilInstruction public class MsilInstruction
{ {
private MsilOperand _operandBacking;
/// <summary> /// <summary>
/// Creates a new instruction with the given opcode. /// Creates a new instruction with the given opcode.
/// </summary> /// </summary>
@@ -97,16 +96,7 @@ namespace Torch.Managers.PatchManager.MSIL
/// <summary> /// <summary>
/// The operand for this instruction, or null. /// The operand for this instruction, or null.
/// </summary> /// </summary>
public MsilOperand Operand public MsilOperand Operand { get; }
{
get => _operandBacking;
set
{
if (_operandBacking != null && value.GetType() != _operandBacking.GetType())
throw new ArgumentException($"Operand for {OpCode.Name} must be {_operandBacking.GetType().Name}");
_operandBacking = value;
}
}
/// <summary> /// <summary>
/// Labels pointing to this instruction. /// Labels pointing to this instruction.
@@ -128,11 +118,12 @@ namespace Torch.Managers.PatchManager.MSIL
/// <summary> /// <summary>
/// Makes a copy of the instruction with a new opcode. /// Makes a copy of the instruction with a new opcode.
/// </summary> /// </summary>
/// <param name="code">The new opcode</param> /// <param name="newOpcode">The new opcode</param>
/// <returns>The copy</returns> /// <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) foreach (MsilLabel x in Labels)
result.Labels.Add(x); result.Labels.Add(x);
return result; return result;
@@ -172,5 +163,28 @@ namespace Torch.Managers.PatchManager.MSIL
sb.Append(OpCode.Name).Append("\t").Append(Operand); sb.Append(OpCode.Name).Append("\t").Append(Operand);
return sb.ToString(); 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> /// </summary>
public MsilInstruction Instruction { get; } public MsilInstruction Instruction { get; }
internal abstract void CopyTo(MsilOperand operand);
internal abstract void Read(MethodContext context, BinaryReader reader); internal abstract void Read(MethodContext context, BinaryReader reader);
internal abstract void Emit(LoggingIlGenerator generator); 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 System.Reflection.Emit;
using Torch.Managers.PatchManager.Transpile; using Torch.Managers.PatchManager.Transpile;
@@ -22,7 +23,7 @@ namespace Torch.Managers.PatchManager.MSIL
{ {
int val = Instruction.OpCode.OperandType == OperandType.InlineBrTarget int val = Instruction.OpCode.OperandType == OperandType.InlineBrTarget
? reader.ReadInt32() ? reader.ReadInt32()
: reader.ReadByte(); : reader.ReadSByte();
Target = context.LabelAt((int)reader.BaseStream.Position + val); Target = context.LabelAt((int)reader.BaseStream.Position + val);
} }
@@ -31,6 +32,14 @@ namespace Torch.Managers.PatchManager.MSIL
generator.Emit(Instruction.OpCode, Target.LabelFor(generator)); 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 /> /// <inheritdoc />
public override string ToString() public override string ToString()
{ {

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Reflection.Emit; using System.Reflection.Emit;
using Torch.Managers.PatchManager.Transpile; using Torch.Managers.PatchManager.Transpile;
@@ -22,6 +23,15 @@ namespace Torch.Managers.PatchManager.MSIL
/// </summary> /// </summary>
public T Value { get; set; } 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 /> /// <inheritdoc />
public override string ToString() public override string ToString()
{ {
@@ -209,16 +219,19 @@ namespace Torch.Managers.PatchManager.MSIL
internal override void Read(MethodContext context, BinaryReader reader) internal override void Read(MethodContext context, BinaryReader reader)
{ {
Value = int paramID =
context.Method.GetParameters()[
Instruction.OpCode.OperandType == OperandType.ShortInlineVar Instruction.OpCode.OperandType == OperandType.ShortInlineVar
? reader.ReadByte() ? 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) 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.Linq;
using System.Reflection.Emit; using System.Reflection.Emit;
using Torch.Managers.PatchManager.Transpile; using Torch.Managers.PatchManager.Transpile;
@@ -19,6 +20,20 @@ namespace Torch.Managers.PatchManager.MSIL
/// </summary> /// </summary>
public MsilLabel[] Labels { get; set; } 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) internal override void Read(MethodContext context, BinaryReader reader)
{ {
int length = reader.ReadInt32(); int length = reader.ReadInt32();

View File

@@ -52,7 +52,7 @@ namespace Torch.Managers.PatchManager.Transpile
/// <inheritdoc cref="ILGenerator.Emit(OpCode, LocalBuilder)"/> /// <inheritdoc cref="ILGenerator.Emit(OpCode, LocalBuilder)"/>
public void Emit(OpCode op, LocalBuilder arg) 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); Backing.Emit(op, arg);
} }

View File

@@ -7,6 +7,7 @@ using System.Reflection;
using System.Reflection.Emit; using System.Reflection.Emit;
using NLog; using NLog;
using Torch.Managers.PatchManager.MSIL; using Torch.Managers.PatchManager.MSIL;
using Torch.Utils;
namespace Torch.Managers.PatchManager.Transpile namespace Torch.Managers.PatchManager.Transpile
{ {
@@ -52,20 +53,19 @@ namespace Torch.Managers.PatchManager.Transpile
using (var reader = new BinaryReader(memory)) using (var reader = new BinaryReader(memory))
while (memory.Length > memory.Position) while (memory.Length > memory.Position)
{ {
var count = 1; var opcodeOffset = (int) memory.Position;
var instructionValue = (short)memory.ReadByte(); var instructionValue = (short)memory.ReadByte();
if (Prefixes.Contains(instructionValue)) if (Prefixes.Contains(instructionValue))
{ {
instructionValue = (short)((instructionValue << 8) | memory.ReadByte()); instructionValue = (short)((instructionValue << 8) | memory.ReadByte());
count++;
} }
if (!OpCodeLookup.TryGetValue(instructionValue, out OpCode opcode)) if (!OpCodeLookup.TryGetValue(instructionValue, out OpCode opcode))
throw new Exception($"Unknown opcode {instructionValue:X}"); throw new Exception($"Unknown opcode {instructionValue:X}");
if (opcode.Size != count) if (opcode.Size != memory.Position - opcodeOffset)
throw new Exception($"Opcode said it was {opcode.Size} but we read {count}"); throw new Exception($"Opcode said it was {opcode.Size} but we read {memory.Position - opcodeOffset}");
var instruction = new MsilInstruction(opcode) var instruction = new MsilInstruction(opcode)
{ {
Offset = (int)memory.Position Offset = opcodeOffset
}; };
_instructions.Add(instruction); _instructions.Add(instruction);
instruction.Operand?.Read(this, reader); instruction.Operand?.Read(this, reader);
@@ -76,22 +76,74 @@ namespace Torch.Managers.PatchManager.Transpile
{ {
foreach (var label in Labels) foreach (var label in Labels)
{ {
int min = 0, max = _instructions.Count - 1; int min = 0, max = _instructions.Count;
while (min <= max) while (min != max)
{ {
var mid = min + ((max - min) / 2); int mid = (min + max) / 2;
if (label.Key < _instructions[mid].Offset) if (_instructions[mid].Offset < label.Key)
max = mid - 1;
else
min = mid + 1; 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); _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() 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; private static readonly Dictionary<short, OpCode> OpCodeLookup;

View File

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