Value does not fall within the expected range when weaving IL code

Issue

This Content is from Stack Overflow. Question asked by z3nth10n

Well, I’m modifying assemblies using a code weaver called Weaver. The fact is that I managed to insert the IL opcodes successfully.

But when I execute I see a weird error message:

ArgumentException: Value does not fall within the expected range.

The thing I managed to do is convert this code:

public void InjectHere0()
{
    Thread.Sleep(50);
    InjectHere1();
    Thread.Sleep(50);
}

public void InjectHere1()
{
    Thread.Sleep(50);
}

To:

public static void InjectHere0(NTree<TraceData> parentNode)
{
    var action = (Action<NTree<TraceData>>)InjectHere0;
    var data = Profiler.Begin(action, Profiler.Tree);

    Thread.Sleep(10);
    InjectHere1(data.CurrentNode);
    Thread.Sleep(10);

    Profiler.End(data);
}

public static void call1(NTree<TraceData> parentNode)
{
    Action<NTree<TraceData>> @var = InjectHere1;

    var data = Profiler.Begin(@var, parentNode);
    Thread.Sleep(10);

    Profiler.End(data);
}

Note how the InjectHere0 parameters signature is also modified.

The unknown types are:

  • NTree a class/struct type with a node tree implementation.
  • TraceData a struct to store the time spent by method.
  • Profiler static class which executes Begin/End methods, described below:
public static class Profiler
{
    public static Tree<TraceData> Tree { get; } =
        new Tree<TraceData>(new TraceData(new Action(ProfilerInit)));

        public static void ProfilerInit()
    {
    }
    
    public struct ProfilerData
    {
        public Tree<TraceData> CurrentNode;
        public DateTime Now;
    }

    public static ProfilerData Begin(Delegate currentMethod, Tree<TraceData> parentNode)
    {
        return new ProfilerData(); // whatever
    }

    public static float End(ProfilerData currentData)
    {
        return 0; // whatever
    }
}

As you can see on the Begin method we receive a Delegate type in order to have information about the caller, and a Tree<TraceData> to append a new child when profiling begins.

All this, supposedly ok, I don’t know because I didn’t manage to arrive here executing code.

So, as you see we have defined a tree structure defined in the code flow, as long as we start visiting sub calls on the call stack we trace new child methods/nodes on the tree.

Now, the point is that the root node also needs a Tree<TraceData> as you can see on the first param of the method InjectHere0. This led me to modify all the calling references for that method, and I supposedly managed to do it, but I’m having a very weird error (isn’t about IL code).

The way I managed to modify the calling references is by looping all the instructions on the rest of the methods, looking for call opcodes, then checking if any of the root nodes on my trees are the ones being inspected. If it’s right, the following patch applies:

private void FixCallingOperands()
{
    var rootMethods = new HashSet<NTree<MethodTrace>>(MethodTree.Children.AsEnumerable());

    foreach (var methodDefinition in AllMethods)
    {
        AddCallInstructions(methodDefinition, instruction =>
        {
            return rootMethods.Any(m =>
                ((MethodReference)instruction.Operand).FullName.Contains(m.Data.MethodReference.FullName));
        });
    }
}

private void AddCallInstructions(MethodDefinition methodDefinition, [NotNull] Predicate<Instruction> instructionResolver)
{
    if (instructionResolver == null) throw new ArgumentNullException(nameof(instructionResolver));

    var matchingInstructions = new List<Instruction>();
    foreach (var bodyInstruction in methodDefinition.Body.Instructions)
    {
        if (!(bodyInstruction.OpCode == OpCodes.Call || bodyInstruction.OpCode == OpCodes.Callvirt))
            continue;

        if (!instructionResolver(bodyInstruction))
            continue;

        matchingInstructions.Add(bodyInstruction);
    }

    var body = methodDefinition.Body;
    var bodyProcessor = body.GetILProcessor();

    //Debug.Log($"{methodDefinition.FullName} [{matchingInstructions.Count}]nn{string.Join(Environment.NewLine, matchingInstructions.Select(DebugInstruction))}");

    var get = methodDefinition.Module.ImportReference(typeof(Profiler).GetProperty("Tree")?.GetMethod);
    foreach (var instruction in matchingInstructions)
    {
        var _00 = Instruction.Create(OpCodes.Call, get);
        bodyProcessor.InsertBefore(instruction, _00);
    }
}

Which, as you can see, look for the calling instruction for the InjectHere0 method then, pass as an argument for the Tree<TraceData> parentNode parameter, the Profiler.Tree property getter.

But then, the following error appears:

ArgumentException: Value does not fall within the expected range.

Test.InjectHere0 (NTree`1[T] parentNode) (at Assets/Assemblies/Test/Test.cs:47)

Test.Start () (at Assets/Assemblies/Test/Test.cs:24)

Here two problems come, I’m unsure if the error is inside the Start or the InjectHere0 methods as I’m unable to see the weaved code.

I can suppose the problem isn’t on the calling method (Start), so, I suppose the problem is on the Profiler.Begin method?

If correct, then which is the expected value? I don’t have any throw ArgumentException

Maybe is the Delegate cast?

Or maybe is the NTree?

This is the decompiled code:

{
    InjectHere0(Profiler.get_Tree());
}
    instance void Start () cil managed 
{
    // Method begins at RVA 0x2050
    // Header size: 12
    // Code size: 148 (0x94)
    .maxstack 3
    .locals init (
        [0] class [uzProfiler]Stats,
        [1] class [uzProfiler]Stats
    )

    // {
    IL_0000: nop
// .........................
    // InjectHere0(Profiler.get_Tree());
    IL_006b: nop
    IL_006c: ldarg.0
    IL_006d: call class [uzProfiler]NTree`1<valuetype [uzProfiler]TraceData> [uzProfiler]Profiler::get_Tree()
    IL_0072: call instance void Test::InjectHere0(class [uzProfiler]NTree`1<valuetype [uzProfiler]TraceData>)
// .........................
    // }
    IL_0092: nop
    IL_0093: ret
} // end of method Test::Start
    instance void InjectHere0 (
        class [uzProfiler]NTree`1<valuetype [uzProfiler]TraceData> parentNode
    ) cil managed 
{
    // Method begins at RVA 0x2140
    // Header size: 12
    // Code size: 73 (0x49)
    .maxstack 2
    .locals (
        [0] class [mscorlib]System.Action`1<class [uzProfiler]NTree`1<valuetype [uzProfiler]TraceData>>,
        [1] valuetype [uzProfiler]Profiler/ProfilerData
    )

    // Action<NTree<TraceData>> action = InjectHere0;
    IL_0000: ldnull
    IL_0001: ldftn instance void Test::InjectHere0(class [uzProfiler]NTree`1<valuetype [uzProfiler]TraceData>)
    IL_0007: newobj instance void class [mscorlib]System.Action`1<class [uzProfiler]NTree`1<valuetype [uzProfiler]TraceData>>::.ctor(object, native int)
    IL_000c: stloc 0
    // ProfilerData val = Profiler.Begin((Delegate)action, (NTree<TraceData>)(object)this);
    IL_0010: ldloc 0
    IL_0014: ldarg.0
    IL_0015: call valuetype [uzProfiler]Profiler/ProfilerData [uzProfiler]Profiler::Begin(class [mscorlib]System.Delegate, class [uzProfiler]NTree`1<valuetype [uzProfiler]TraceData>)
    IL_001a: stloc 1
    // Thread.Sleep(50);
    IL_001e: nop
    IL_001f: ldc.i4.s 50
    IL_0021: call void [netstandard]System.Threading.Thread::Sleep(int32)
    // InjectHere1(val.CurrentNode);
    IL_0026: nop
    IL_0027: ldarg.0
    IL_0028: ldloc 1
    IL_002c: ldfld class [uzProfiler]NTree`1<valuetype [uzProfiler]TraceData> [uzProfiler]Profiler/ProfilerData::CurrentNode
    IL_0031: call instance void Test::InjectHere1(class [uzProfiler]NTree`1<valuetype [uzProfiler]TraceData>)
    // Thread.Sleep(50);
    IL_0036: nop
    IL_0037: ldc.i4.s 50
    IL_0039: call void [netstandard]System.Threading.Thread::Sleep(int32)
    // Profiler.End(val);
    IL_003e: nop
    IL_003f: ldloc 1
    IL_0043: call float32 [uzProfiler]Profiler::End(valuetype [uzProfiler]Profiler/ProfilerData)
    // }
    IL_0048: ret
} // end of method Test::InjectHere0```

I'm unsure which is the cause of the problem.

``` // ProfilerData val = Profiler.Begin((Delegate)action, (NTree<TraceData>)(object)this);
    IL_0010: ldloc 0
    IL_0014: ldarg.0
    IL_0015: call valuetype [uzProfiler]Profiler/ProfilerData [uzProfiler]Profiler::Begin(class [mscorlib]System.Delegate, class [uzProfiler]NTree`1<valuetype [uzProfiler]TraceData>)
    IL_001a: stloc 1

In theory, ldarg.0 translates into this because parentNode argument doesn’t have static flag.



Solution

This question is not yet answered, be the first one who answer using the comment. Later the confirmed answer will be published as the solution.

This Question and Answer are collected from stackoverflow and tested by JTuto community, is licensed under the terms of CC BY-SA 2.5. - CC BY-SA 3.0. - CC BY-SA 4.0.

people found this article helpful. What about you?