简体   繁体   中英

dnlib.DotNet.Writer.ModuleWriterException when merging IL of two methods

I am trying to merge the IL of two methods: one method is from an already existing assembly ( MyAssembly.dll ) and one is in the same project as my dnlib code. I'm trying to read both methods, merge their instructions and then write it to a new assembly. This works, however when I add if statements (probably also other statements) to the new method (so the one from my project) I get an exception when writing the instructions to the new assembly:

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)

It does not throw an exception when the original code (the code from MyAssembly.dll) contains an if statement.

MyAssembly.dll's code:

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

MyAssembly.dll's 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

My project's code:

string check = "Hello".Trim();

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

My project's 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

Merged 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

The merged IL looks identical to the IL I generated with dnSpy: https://owo.whats-th.is/a0783e.png

However, an error is thrown when writing the merged IL to a file. My code:

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

I don't know what causes this issue. All labels in the IL seem to be correct. When removing the if statement dnlib writes the IL to the assembly just fine.

如果要合并的方法来自其他程序集,则可能需要创建/转换令牌。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM