Injecting a an assembly reference /w Mono Cecil does not work

Viewed 230

I'm currently playing with Mono.Cecil to add an assembly reference into an already compiled assembly.

I'm using this code to add the reference:

this.tracerReference = this.module.ImportReference(typeof(Tracer).GetMethod("Trace", new Type[]
            {
                typeof(String)
            }));

Tracer is a class from another project. Once I'm done with my modifications to the loaded assembly, I'm saving it like this:

var newAssemblyName =
                Path.GetFileNameWithoutExtension(this._assemblyPath) + ".modified" + Path.GetExtension(_assemblyPath);
            var directory = Path.GetDirectoryName(_assemblyPath);
            Console.WriteLine(newAssemblyName);

            module.Write(Path.Join(directory, newAssemblyName));

Once I try to run the new dll, I see this:

$ dotnet instrospect.TestCli.dll                                                                               
Unhandled exception. System.IO.FileNotFoundException: Could not load file or assembly 'instrospect.types, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.

File name: 'instrospect.types, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
[1]    4555 abort      dotnet instrospect.TestCli.dll

Even though the dll in question, introspect.types is in the same directory as introspectTestCli.dll it does not find it. Am I missing anything?

1 Answers

Assembly probing works differently on dotnet core.

The following sample works as expected once I update the app.deps.json file accordingly.

using System;
using System.IO;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace mod
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length > 0)
            {
                DoIt(args[0]);
            }
            else
            {
                using var assemblyToReference = AssemblyDefinition.ReadAssembly("xxx.dll");                

                var assemblyPath = typeof(Program).Assembly.Location;
                using var a = AssemblyDefinition.ReadAssembly(assemblyPath);
                var t = a.MainModule.GetType("mod.Program");
                var m = t.Methods.Single(m => m.Name == "DoIt");

                var il = m.Body.GetILProcessor();

                m.Body.Instructions.Clear();
                
                il.Emit(OpCodes.Ldarg_0);
                var method = assemblyToReference.MainModule.GetType("Class1").Methods.Single(m => m.Name == "Show");
                il.Emit(OpCodes.Call, a.MainModule.ImportReference(method));
                il.Emit(OpCodes.Ret);

                a.Write(assemblyPath + ".new");
                System.Console.WriteLine($"New assembly saved to {assemblyPath}.new");

                System.Console.WriteLine("Now you need to:");
                System.Console.WriteLine($"\t1 - Copy xxx.dll to {Path.GetDirectoryName(assemblyPath)}");
                System.Console.WriteLine($"\t2 - Update {Path.GetDirectoryName(assemblyPath)}/mod.deps.json");
                System.Console.WriteLine($"\t3 - Delete {assemblyPath}");
                System.Console.WriteLine($"\t4 - Rename {assemblyPath}.new to {assemblyPath}");
                System.Console.WriteLine($"\t5 - Run: dotnet {assemblyPath} 'some string'");
            }
        }

        static void DoIt(string msg)
        {            
        }
    }
}
{
    "runtimeTarget": {
      "name": ".NETCoreApp,Version=v5.0",
      "signature": ""
    },
    "compilationOptions": {},
    "targets": {
      ".NETCoreApp,Version=v5.0": {
        "mod/1.0.0": {
          "dependencies": {
            "Mono.Cecil": "0.11.4",
            "xxx": "1.0.0.0"
          },
          "runtime": {
            "mod.dll": {}
          }
        },
        "Mono.Cecil/0.11.4": {
          "runtime": {
            "lib/netstandard2.0/Mono.Cecil.Mdb.dll": {
              "assemblyVersion": "0.11.4.0",
              "fileVersion": "0.11.4.0"
            },
            "lib/netstandard2.0/Mono.Cecil.Pdb.dll": {
              "assemblyVersion": "0.11.4.0",
              "fileVersion": "0.11.4.0"
            },
            "lib/netstandard2.0/Mono.Cecil.Rocks.dll": {
              "assemblyVersion": "0.11.4.0",
              "fileVersion": "0.11.4.0"
            },
            "lib/netstandard2.0/Mono.Cecil.dll": {
              "assemblyVersion": "0.11.4.0",
              "fileVersion": "0.11.4.0"
            }
          }
        },
        "xxx/1.0.0.0": {
          "runtime": {
            "xxx.dll": {
              "assemblyVersion": "1.0.0.0",
              "fileVersion": "1.0.0.0"
            }
          }
        }
      }
    },
    "libraries": {
      "mod/1.0.0": {
        "type": "project",
        "serviceable": false,
        "sha512": ""
      },
      "Mono.Cecil/0.11.4": {
        "type": "package",
        "serviceable": true,
        "sha512": "sha512-IC1h5g0NeJGHIUgzM1P82ld57knhP0IcQfrYITDPXlNpMYGUrsG5TxuaWTjaeqDNQMBDNZkB8L0rBnwsY6JHuQ==",
        "path": "mono.cecil/0.11.4",
        "hashPath": "mono.cecil.0.11.4.nupkg.sha512"
      },      
      "xxx/1.0.0.0": {
        "type": "reference",
        "serviceable": false,
        "sha512": ""
      }
    }
  }

Notes:

  1. xxx.dll is a simple assembly, containing a class named Class1 wit a static method Show(string).
  2. The simplest way to figure out the changes needed in the app.deps.json file is to manually add a dependency in the project file and compare the generated app.deps.json file (I've included the modified deps.json file above. You can simply search for xxx and you'll see the places you need to update)
Related