Mono.Cecil weaver 错误的分支目标

问题描述 投票:0回答:1

我正在尝试使用 Mono.Cecil 将一些检测代码编织到现有方法中 - 基本上只是所有序列点之前的日志。这很简单,我可以迭代所有序列点并插入

Ldstr
来加载日志文本和
Call
指令,例如
Console.WriteLine
。为了确保分支目标不会在此日志记录中丢失,我再次查看说明,并修补每个分支目标以确保它们现在指向这些前面的日志指令。从理论上讲,这听起来很简单,这是我最终得到的编织器代码:

using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;

internal sealed class InstrumentationWeaver
{
    public static void WeaveInstrumentationCode(string assemblyLocation)
    {
        var readerParameters = new ReaderParameters { ReadSymbols = true };
        using var assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyLocation, readerParameters);
        var weaver = new InstrumentationWeaver(assemblyDefinition.MainModule);
        weaver.WeaveModule();
        assemblyDefinition.Write("WeaveOutput.dll");
    }

    private readonly ModuleDefinition weavedModule;

    private InstrumentationWeaver(ModuleDefinition weavedModule)
    {
        this.weavedModule = weavedModule;
    }

    private void WeaveModule()
    {
        foreach (var type in this.weavedModule.GetTypes()) this.WeaveType(type);
    }

    private void WeaveType(TypeDefinition type)
    {
        foreach (var method in type.Methods) this.WeaveMethod(method);
    }

    private void WeaveMethod(MethodDefinition method)
    {
        if (method.IsAbstract || method.IsPInvokeImpl || method.IsRuntime || !method.HasBody) return;

        var jumpTargetPatches = new Dictionary<int, Instruction>();
        var ilProcessor = method.Body.GetILProcessor();

        // Weave each instruction for instrumentation
        var sequencePoints = method.DebugInformation.SequencePoints;
        foreach (var sequencePoint in sequencePoints)
        {
            var instruction = this.WeaveInstruction(ilProcessor, sequencePoint);
            if (instruction is not null) jumpTargetPatches.Add(sequencePoint.Offset, instruction);
        }

        // Patch jump targets
        foreach (var instruction in ilProcessor.Body.Instructions)
        {
            this.PatchJumpTarget(instruction, jumpTargetPatches);
        }

        // Patch exception handlers
        foreach (var exceptionHandler in method.Body.ExceptionHandlers)
        {
            this.PatchExceptionHandler(exceptionHandler, jumpTargetPatches);
        }

        method.Body.OptimizeMacros();
    }

    private Instruction? WeaveInstruction(ILProcessor ilProcessor, SequencePoint sequencePoint)
    {
        if (sequencePoint.IsHidden) return null;

        // Get the instruction at the sequence point
        var instruction = ilProcessor.Body.Instructions.FirstOrDefault(i => i.Offset == sequencePoint.Offset);
        if (instruction is null) return null;

        // Insert the instrumentation code before the instruction
        return this.AddInstrumentationCode(ilProcessor, instruction, sequencePoint);
    }

    private Instruction AddInstrumentationCode(ILProcessor ilProcessor, Instruction before, SequencePoint sequencePoint)
    {
        // For now we just write a message to the console
        var message = $"Sequence point at {sequencePoint.Document.Url}:{sequencePoint.StartLine}";
        var writeLineMethod = this.weavedModule.ImportReference(typeof(System.Console).GetMethod("WriteLine", [typeof(string)]));
        var instructions = new[]
        {
            Instruction.Create(OpCodes.Ldstr, message),
            Instruction.Create(OpCodes.Call, writeLineMethod),
        };
        foreach (var instruction in instructions) ilProcessor.InsertBefore(before, instruction);
        return instructions[0];
    }

    private void PatchExceptionHandler(ExceptionHandler exceptionHandler, IReadOnlyDictionary<int, Instruction> jumpTargetPatches)
    {
        this.PatchJumpTarget(exceptionHandler.TryStart, jumpTargetPatches);
        this.PatchJumpTarget(exceptionHandler.TryEnd, jumpTargetPatches);
        this.PatchJumpTarget(exceptionHandler.HandlerStart, jumpTargetPatches);
        this.PatchJumpTarget(exceptionHandler.HandlerEnd, jumpTargetPatches);
        this.PatchJumpTarget(exceptionHandler.FilterStart, jumpTargetPatches);
    }

    private void PatchJumpTarget(Instruction instruction, IReadOnlyDictionary<int, Instruction> jumpTargetPatches)
    {
        if (instruction.Operand is Instruction targetInstruction)
        {
            if (jumpTargetPatches.TryGetValue(targetInstruction.Offset, out var newTarget))
            {
                instruction.Operand = newTarget;
            }
        }
        else if (instruction.Operand is Instruction[] targetInstructions)
        {
            for (var i = 0; i < targetInstructions.Length; i++)
            {
                if (jumpTargetPatches.TryGetValue(targetInstructions[i].Offset, out var newTarget))
                {
                    targetInstructions[i] = newTarget;
                }
            }
        }
    }
}

它适用于循环、if-else 语句,但由于某种原因,当我在 for 循环中使用 if-else 时,跳转目标会完全混乱。我能找到的最小的例子就是编织这个:

public static class Class1
{
    public static void TestMethod()
    {
        for (var i = 0; i < 10; i++)
        {
            if (i == 0)
            {
                Console.WriteLine(i);
            }
            else
            {
            }
        }
    }
}

虽然生成的 IL 中的某些分支目标似乎是正确的,但最后的跳转指向比最后一条指令更高的地址(图片来自 ILSpy): enter image description here

由于循环和 if-else 各自起作用,我不认为它们本身特别向后跳转会扰乱逻辑,但我无法弄清楚它是什么。

cil mono.cecil
1个回答
0
投票

事实证明,Cecil 不会处理更改短格式跳转,以防您注入太多代码。因此,首先您需要在注入前调用

method.Body.SimplifyMacros();
,并在完成后调用
method.Body.OptimizeMacros();
。奇怪的是,后一个代码已经存在,这是反复试验的结果。

© www.soinside.com 2019 - 2024. All rights reserved.