Hi,

I noticed that most of the time, Cecil correctly updates branch offset 
targets when weaving in new IL instructions. For instance, adding a simple 
"ldstr" followed by a call to Console.WriteLine will correctly offset the 
branches below to the new instruction offsets.


However, Cecil doesn't seem to correctly update offsets if the target 
instruction is changed.

Consider a brtrue_s that points to the final instruction of a method. In 
order to wrap the method in a try/finally, I am adding a new final ret 
instruction, and then changing all previous returns into new leave 
instructions to the new final instruction (else you branch out of a try 
block, which is invalid).

In the above case, the branch offset will remain what it was originally: no 
correction is made. Of course, the original instruction is no longer in the 
list of instructions; it was replaced with a new one.


I tried with a direct assignation (Instructions[index] = newInstruction) 
and also with (ilProcessor.Replace(Instructions[index], newInstruction). 
Both will create the bug.

The workaround is to change the instruction to ensure that the object 
reference is the same. so: Instructions[index].OpCode = 
newInstruction.OpCode; Instructions[index].Operand = newInstruction.Operand;


My question is: Shouldn't this be what .Replace does? Is there another way 
to deal with this behavior?


Thanks!


Note: I included a text file with my weaving code and the problematic 
method. The problematic branch is the first one (brtrue.s IL_009b on my 
machine).

-- 
-- 
--
mono-cecil
--- 
You received this message because you are subscribed to the Google Groups 
"mono-cecil" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.
// THE TEST METHOD
public void Test()
{
    AssemblyDefinition targetAssembly = 
AssemblyDefinition.ReadAssembly(m_TestAssembly, new ReaderParameters { 
ReadSymbols = true });
    MethodDefinition methodToWeave = 
targetAssembly.MainModule.GetType("InTest.UT.WeaverTemplates", 
"Templates").Methods.Single(m => m.Name == "ProblematicBranching").Resolve();

    string className = 
methodToWeave.DeclaringType.FullName.Replace(methodToWeave.DeclaringType.Namespace
 + ".", "");
    string methodName = className + "." + methodToWeave.Name;

    MethodReference callWriteLine = 
targetAssembly.MainModule.Import(typeof(Console).GetMethod("WriteLine", new 
Type[] { typeof(string) }));
    Instruction first = methodToWeave.Body.Instructions.First();

    var il = methodToWeave.Body.GetILProcessor();
    il.InsertBefore(first, il.Create(OpCodes.Ldstr, "--> " + methodName));
    il.InsertBefore(first, il.Create(OpCodes.Call, callWriteLine));

    var originalReturn = il.Body.Instructions.Last();           // original 
return
    var finallyStart = Instruction.Create(OpCodes.Nop);         // first 
instruction in finally block (and last try)
    il.Append(finallyStart);
    var finallyEnd = Instruction.Create(OpCodes.Endfinally);    // last 
instruction in finally block
    il.Append(finallyEnd);
    var finalReturn = Instruction.Create(OpCodes.Ret);          // the final 
return
    il.Append(finalReturn);

    // Find all returns and change them to leaves to the final return
    for (var index = 0; index < il.Body.Instructions.Count - 1; index++) {
        var instruction = il.Body.Instructions[index];
        if (instruction.OpCode == OpCodes.Ret) {
            // Change return into leave

            // Bug Method 1:
            var leaveInstruction = il.Create(OpCodes.Leave, finalReturn);
            il.Replace(il.Body.Instructions[index], leaveInstruction);

            // Bug Method 2:
            //var leaveInstruction = il.Create(OpCodes.Leave, finalReturn);
            //il.Body.Instructions[index] = leaveInstruction;

            // Workaround method:
            //instruction.OpCode = OpCodes.Leave;
            //instruction.Operand = finalReturn;
        }
    }

    Instruction ldArg1 = il.Create(OpCodes.Ldstr, " << " + methodName);
    Instruction callOnExit = il.Create(OpCodes.Call, callWriteLine);
    il.InsertBefore(finallyEnd, ldArg1);
    il.InsertBefore(finallyEnd, callOnExit);

    var handler = new ExceptionHandler(ExceptionHandlerType.Finally) {
        TryStart = first,
        TryEnd = finallyStart,
        HandlerStart = finallyStart,
        HandlerEnd = finalReturn
    };

    il.Body.ExceptionHandlers.Add(handler);
    il.Body.InitLocals = true;

    targetAssembly.Write(m_TestAssembly, new WriterParameters() { WriteSymbols 
= true });
}
    
    
// THE METHOD WE WEAVE INTO
void ProblematicBranching(string p_Path, Dictionary<string, string> p_Changes, 
bool p_ErasePatchList)
{
    string path = p_Path + "\\";
    string patchFileList = path + "PatchFileList.txt";
    if (File.Exists(patchFileList)) {
        using (TextReader tr = new StreamReader(patchFileList)) {
            // Read a filename.
            string filename;
            while ((filename = tr.ReadLine()) != null) {
                filename = filename.Trim();
                if (filename.Length != 0) {
                    // Patch the corresponding file.
                    ProblematicBranching(path + filename, p_Changes, true);
                }
            }
        }
        // Erase the list
        if (p_ErasePatchList) {
            File.Delete(patchFileList);
        }
    }
}

Reply via email to