2

我正在尝试合并两种方法的 IL:一种方法来自已经存在的程序集 ( MyAssembly.dll),另一种方法与我的 dnlib 代码在同一个项目中。我正在尝试阅读这两种方法,合并它们的指令,然后将其写入新程序集。这可行,但是当我将 if 语句(可能还有其他语句)添加到新方法(所以我的项目中的那个)时,在将指令写入新程序集时出现异常:

Unhandled Exception: dnlib.DotNet.Writer.ModuleWriterException: Found some other method's instruction or a removed instruction. You probably removed an instruction that is the target of a branch instruction or an instruction that's the first/last inst
ruction in an exception handler.
   at dnlib.DotNet.DummyLogger.Log(Object sender, LoggerEvent loggerEvent, String format, Object[] args)
   at dnlib.DotNet.Writer.ModuleWriterBase.dnlib.DotNet.ILogger.Log(Object sender, LoggerEvent loggerEvent, String format, Object[] args)
   at dnlib.DotNet.Writer.MetaData.Error(String message, Object[] args)
   at dnlib.DotNet.Writer.MetaData.dnlib.DotNet.Writer.IWriterError.Error(String message)
   at dnlib.DotNet.Writer.MethodBodyWriter.ErrorImpl(String message)
   at dnlib.DotNet.Writer.MethodBodyWriterBase.Error(String message)
   at dnlib.DotNet.Writer.MethodBodyWriterBase.GetOffset(Instruction instr)
   at dnlib.DotNet.Writer.MethodBodyWriterBase.WriteShortInlineBrTarget(BinaryWriter writer, Instruction instr)
   at dnlib.DotNet.Writer.MethodBodyWriterBase.WriteOperand(BinaryWriter writer, Instruction instr)
   at dnlib.DotNet.Writer.MethodBodyWriterBase.WriteInstruction(BinaryWriter writer, Instruction instr)
   at dnlib.DotNet.Writer.MethodBodyWriterBase.WriteInstructions(BinaryWriter writer)
   at dnlib.DotNet.Writer.MethodBodyWriter.WriteFatHeader()
   at dnlib.DotNet.Writer.MethodBodyWriter.Write()
   at dnlib.DotNet.Writer.MetaData.WriteMethodBodies()
   at dnlib.DotNet.Writer.MetaData.Create()
   at dnlib.DotNet.Writer.MetaData.CreateTables()
   at dnlib.DotNet.Writer.ModuleWriter.WriteImpl()
   at dnlib.DotNet.Writer.ModuleWriterBase.Write(Stream dest)
   at dnlib.DotNet.Writer.ModuleWriterBase.Write(String fileName)
   at dnlib.DotNet.ModuleDef.Write(String filename, ModuleWriterOptions options)
   at dnlib.DotNet.ModuleDef.Write(String filename)

当原始代码(来自 MyAssembly.dll 的代码)包含 if 语句时,它不会引发异常。

MyAssembly.dll 的代码:

string str = GameManager.m_GameVersionString;
if (GameManager.m_Changelist != string.Empty)
{
    str = str + " (" + GameManager.m_Changelist + ")";
}
return str + " Release ";

MyAssembly.dll 的 IL:

IL_0000: ldsfld System.String GameManager::m_GameVersionString
IL_0005: stloc.0
IL_0006: ldsfld System.String GameManager::m_Changelist
IL_000B: ldsfld System.String System.String::Empty
IL_0010: call System.Boolean System.String::op_Inequality(System.String,System.String)
IL_0015: brfalse IL_0030
IL_001A: ldloc.0
IL_001B: ldstr " ("
IL_0020: ldsfld System.String GameManager::m_Changelist
IL_0025: ldstr ")"
IL_002A: call System.String System.String::Concat(System.String,System.String,System.String,System.String)
IL_002F: stloc.0
IL_0030: ldloc.0
IL_0031: ldstr " Release "
IL_0036: call System.String System.String::Concat(System.String,System.String)
IL_003B: stloc.0
IL_003C: ldloc.0
IL_003D: ret

我的项目代码:

string check = "Hello".Trim();

if (check == "Hello")
{
    Console.WriteLine("Hello");
}

我的项目的 IL

IL_0000: ldstr "Hello"
IL_0005: call System.String System.String::Trim()
IL_000A: ldstr "Hello"
IL_000F: call System.Boolean System.String::op_Equality(System.String,System.String)
IL_0014: brfalse.s IL_0020
IL_0016: ldstr "Hello"
IL_001B: call System.Void System.Console::WriteLine(System.String)
IL_0020: ret

合并 IL

IL_0000: ldstr "Hello"
IL_0005: call System.String System.String::Trim()
IL_000A: ldstr "Hello"
IL_000F: call System.Boolean System.String::op_Equality(System.String,System.String)
IL_0014: brfalse.s IL_0020
IL_0016: ldstr "Hello"
IL_001B: call System.Void System.Console::WriteLine(System.String)
IL_0020: ldsfld System.String GameManager::m_GameVersionString
IL_0025: stloc.0
IL_0026: ldsfld System.String GameManager::m_Changelist
IL_002B: ldsfld System.String System.String::Empty
IL_0030: call System.Boolean System.String::op_Inequality(System.String,System.String)
IL_0035: brfalse.s IL_004D
IL_0037: ldloc.0
IL_0038: ldstr " ("
IL_003D: ldsfld System.String GameManager::m_Changelist
IL_0042: ldstr ")"
IL_0047: call System.String System.String::Concat(System.String,System.String,System.String,System.String)
IL_004C: stloc.0
IL_004D: ldloc.0
IL_004E: ldstr " Release "
IL_0053: call System.String System.String::Concat(System.String,System.String)
IL_0058: stloc.0
IL_0059: ldloc.0
IL_005A: ret

合并后的 IL 看起来与我使用 dnSpy 生成的 IL 相同:https ://owo.whats-th.is/a0783e.png

但是,将合并的 IL 写入文件时会引发错误。我的代码:

//Patch method

...

var patchModule = ModuleDefMD.Load(patchClass.Module);
var patchMethod = FindMethod(patchModule, patchClass, method.Name);

var assemblyModule = ModuleDefMD.Load(assemblyPath);
var assemblyMethod = FindMethod(assemblyModule, attribute.Type, attribute.MethodName,  attribute.Parameters);

assemblyMethod.Body = MergeMethods(patchMethod, assemblyMethod, attribute.CodeMode, attribute.CustomPos);

assemblyModule.Write(finalPath); //Error is thrown here

...

private static CilBody MergeMethods(MethodDef original, MethodDef target, AmityPatch.Mode mode, int mergeLoc = 0)
{

    original.FreeMethodBody();
    target.FreeMethodBody();

    var originalBody = original.Body;
    var targetBody = target.Body;

    var targetModule = target.Module;

    var originalInstructions = originalBody.Instructions;
    var targetInstructions = targetBody.Instructions;

    Console.WriteLine("=original method=");
    Console.WriteLine();
    foreach (var originalInstruction in originalInstructions)
    {
        Console.WriteLine(originalInstruction);
    }
    Console.WriteLine();
    Console.WriteLine("=target method=");
    Console.WriteLine();
    foreach (var targetInstruction in targetInstructions)
    {
        Console.WriteLine(targetInstruction);
    }

    RemoveReturn(ref originalInstructions, true);
    if (mode == AmityPatch.Mode.Postfix) mergeLoc = targetInstructions.Count - 1;

    var localOffset = targetBody.Variables.Count;

    for (var i = 0; i < originalBody.Variables.Count; i++)
    {
        targetBody.Variables.Add(
            new Local(originalBody.Variables[i].Type, originalBody.Variables[i].Name));
    }

    for (var i = originalInstructions.Count - 1; i >= 0; i--)
    {
        var o = originalInstructions[i];
        var c = new Instruction(o.OpCode, o.Operand);

        switch (o.Operand)
        {
            case IType type:
                c.Operand = targetModule.Import(type);
                break;
            case IMethod method:
                c.Operand = targetModule.Import(method);
                break;
            case IField field:
                c.Operand = targetModule.Import(field);
                break;
        }

        if (IsStloc(o.OpCode))
        {
            c.OpCode = OpCodes.Stloc;
            c.Operand = targetBody.Variables[StlocIndex(o) + localOffset];
        }
        else if (IsLdloc(o.OpCode))
        {
            c.OpCode = OpCodes.Ldloc;
            c.Operand = targetBody.Variables[LdlocIndex(o) + localOffset];
        }
        else if (IsLdloca(o.OpCode))
        {
            c.OpCode = OpCodes.Ldloca;
            c.Operand = targetBody.Variables[LdlocIndex(o) + localOffset];
        }

        targetInstructions.Insert(mergeLoc, c);
    }

    targetBody.OptimizeMacros();
    targetBody.OptimizeBranches();

    Console.WriteLine();
    Console.WriteLine("=merged method=");
    Console.WriteLine();

    foreach (var instruction in targetBody.Instructions)
    {
        Console.WriteLine(instruction);
    }

    Console.WriteLine(targetBody.Variables.Count);

    return targetBody;
}

我不知道是什么导致了这个问题。IL 中的所有标签似乎都是正确的。删除 if 语句时,dnlib 将 IL 写入程序集就好了。

4

1 回答 1

0

如果要合并的方法来自不同的程序集,则可能需要创建/转换标记。

于 2018-01-01T02:23:32.250 回答